I have a user control that has a list of items.
If an item is selected, I want to show/replace the view with a panel and some buttons and what not.
When the user has finished with this view by pressing a button or something, I want to revert back to the list again and continue the process.
Essentially, this is a wizard.
What to do?
Sorry, I forgot to mention that I am using MVVM.
Solution:
Thanks to all for the help. My test application wasn't working which prompted me to ask SO.
My test application wasn't working because I had missed the {x:type} in the DataTemplate.
To simply:
I created different datatemplates in the resources with the {x:Type}
My viewmodel:
contained a compositecollection of IWizardPageViewModel.
contained a currentPage property.
contained NextCommand/BackCommand to change the currentPage
I bind the currentPage property to the control and the datatemplates take over.
Because of the {x:Type} it wasn't working.
I don't know whether this is right or wrong, but it works and is mostly controlled by the viewmodel rather than triggers on the view.
Consider a list of usercontrols - one for each page of your wizard. The top level usercontrol (the wizard control) will own this list. For navigation, you can;
Have buttons on the top level wizard usercontrol. When pressed, these notify the children of the navigation so that they can finish their work or cancel the navigation. You will want a common interface for the pages. IWizardPage perhaps?
Use a routed commands to notify the wizard usercontrol http://msdn.microsoft.com/en-us/library/ms752308.aspx#Four_main_Concepts
You can use Triggers and Selectors to update the view (DataTemplate) based on user actions.
Let me know if you need code snippet for the same.
First thing that comes to my mind (and easiest) is to use Visibility property and bind it to some boolean flags in ViewModel that will indicate current UI state. Of course in this case you should apply a Converter to properly convert bool value to Visibility. There are dozens of examples of such kind of convertors.
But this is relevant only in for of small amount of such Controls. In case of really lots of UI elements that should be shown and replaced on a view it's better to use framework like Prism. From scratch it will be not so straightforward, but then you'll feel all power of flexibility.
In case you're not following MVVM culture (or you don't like having such backing properties) you can bind Visibility property of control A that should be shown to Booleaen property of element B which stands for show/hide logic. To make it clear:
<TextBlock x:Name="A" Visibility="{Binding IsChecked, ElementName=B, Converter={StaticResource boolToVisibilityConverter}}" Text="Some text."/>
<ToggleButton x:Name="B" IsChecked="False"/>
Related
in several Views we have container components - for example Grid or ScrollViewer- that are bound to a property on their own ViewModel or ViewModelItem.
In some cases the View (parent) containing these components can be shown without the ViewModel/ViewModelItem of the components being initialized. To avoid showing a user empty datagrids we are using FallBackValue=Hidden to make sure the container is only shown when it's ViewModel/ViewModelItem is initialized/loaded.
A hypothetical example would be a Window containing a DataGrid and a more detailed view to the right. The detailed view would be its own UserControl with its own ViewModel and the DataGrid would have its own ViewModel as well. When opening said Window the ViewModel of the DataGrid would be loaded immediately - but since no row has been selected the ViewModel that belongs to the detailed View would not be intialized, which means that the Visibility Binding of the detail View would fail and the binding's FallbackValue would be used to hide the detail View.
The issue with this approach is that the Visual Studio WPF/XAML designer will not show the content of the affected containers since they are hidden due to their FallbackValue.
Question: Is there a way to get the designer to show specific controls/components that have the FallBackValue of a Visibility binding set to Hidden? Clicking into the XAML code that is inside these hidden containers does not show them.
Edit 1
I found this answer. Setting d:IsHidden="false" does not help. Regardless of if it's set before or after the Visibility property.
When it comes to the designer you would have to set the Design Data Context.
To do that you would typically do the following in your xaml file:
<Control, Page or Window
xmlns:vm="clr-namespace:VM.ViewModel"
d:DataContext="{d:DesignInstance {x:Type vm:YourViewModelNameHere}, IsDesignTimeCreatable=True}"
.../>
However, sometimes like in your case, more flexibility is needed for the complex ViewModel. For this I would use Blend and it's functionality, what that is it will generate dummy data for you and set the data context of the Control for you.
For more information refer to this MSDN article.
I've been working to improve scrolling performance for an ItemsControl. Initially each item is a simple row of information and when an item is clicked, a detail UserControl is expanded below the item. I'm trying to eliminate the processing being done on the detail UserControl during scrolling. I eventually achieved this with a CustomControl, but I feel like I must be missing a simpler way to do it using the existing framework controls.
Initially, this detail view was a UserControl with Visibility.Collapsed. I should note that Virtualizing and Recycling are enabled for this ItemsControl. Therefore, scrolling performance was poor since each detail view was being bound to the data as the item scrolled into view, even though the detail was not visible.
I then tried using a ContentPresenter where the Content was bound to a DetailViewModel property and the ContentTemplate was set to a keyed DataTemplate. That DetailViewModel property was initialized to Nothing and then set to the appropriate ViewModel object when the item was clicked. This improved scrolling performance because there was no data binding, but I found that the detail UserControl was still getting constructed for each item, and I presume there was some rendering going on as well.
I eventually got the desired behavior with a custom ContentControl that provides dependency properties for GatedContentTemplate and IsGateOpen. When IsGateOpen goes True, the GatedContentTemplate is passed to the ContentTemplate property and the Content is set to the DataContext of the control, which causes the content to then get constructed, rendered, and bound.
<my:GatedContentControl IsGateOpen="{Binding IsDetailVisible}">
<my:GatedContentControl.GatedContentTemplate>
<DataTemplate>
<my:DetailUserControl/>
</DataTemplate>
</my:GatedContentControl.GatedContentTemplate>
</my:GatedContentControl>
I can live with this solution, but it's a little janky, and I wonder if I'm missing some way that WPF intends for this to be done.
Looks like I wasn't missing anything and this is a need that Microsoft is addressing in .net 4.6.
The Content Deferral feature is discussed in this video, starting at 26:30.
http://channel9.msdn.com/Events/dotnetConf/2015/WPF-in-46-and-beyond
Thanks for the comment HighCore.
I am creating a resource-intensive dashboard application that will have many areas of data visualization. I am thinking that it would be best to use a frame and load the pages needed one at a time using WPF pages. These pages will also have different data contexts, security restrictions, etc. But, another developer says I can accomplish the same thing using a TabControl.
Does a TabControl load all the items in all the tabs at once, on application startup? Or, can I load them lazily as needed like with WPF pages (page only loads content when navigated to)? Also, can you have different data contexts per each item in a TabControl?
In WPF you can use UI Virtualization which means that only the visible controls are initialized and rendered. As far as I know, the TabControl does not support UI Virtualization by default but maybe you can add it manually or use another control. Maybe you want to have a look at the following article which presents some performance tips. There is also mentioned that there is a difference between UI and Data Virtualization. Not showing the controls does not mean that the underlying data are not in memory. All your binding targets will be loaded, but the controls will not be rendered.
To your second question: Yes, every TabItem can have its own DataContext. If you use a TabControls ItemsSource to bind a list of items, the DataContext for every TabItem will be one item of the list. If you manually add TabItems, you can set the DataContext like that:
<TabControl>
<TabItem DataContext="{Binding Context1}" />
<TabItem DataContext="{Binding Context2}" />
</TabControl>
It is more complex than you would guess. If you bind to Tab Collection (think MVVM) then the tab only get created when it is selected. And with a Collection if you leave a tab and come back it gets built AGAIN. If you create the tabs in XAML then the tabs are all built when the windows loads. Yes you can have different DataContext for each tab. What I do for lazy loads is bind to the TabItem property IsSelected and if it is false all the Properties in the class just return a (fast) static type compliant value. If IsSelected is changed to true then I load the real values and call NotifyPropertyChanged (and I save the real values).
I use the heck out of this were I load a big objects and one tab is a summary. Tabs do not virtualize but if you have big lists then for sure use virtualization in the tab. You can use BackgroundWorker to create properites but once it returns and you bind that returned value the UI locked until the UI control is rendered. For me reuse of a single frame versus tabs is a UI thing. Just to break up code I typically load a tab with a frame and a page (and I typically pass data to the page in the ctor to load dynamic content).
FindAncestor RelativeSource only supports 'Self' and 'TemplatedParent',
but I have to bind the width of a popup to the width of the page.
Giving the page a name causes problems because sometimes it will
throw exceptions saying a control with that name is already present in the visual tree.
<Popup IsOpen="True"
Width="{Binding ElementName=BordPage, Path=Width}"
Height="{Binding ElementName=BordPage, Path=Height}">
Background information:
I'm using a SL4 navigation based application here. BordPage is a navigation page,
which I'm using multiple times within the application. So giving it a name in the page itself is not really a good idea,
but I don't know how else I can bind to the width and height of the page.
What I'm trying to do is have a black border (with opacity 0.8) cover the entire screen,
(including the controls of the MainPage). Then on top of that I want to display some other controls.
Since the application is touch controlled, providing the user with a ComboBox to select a value doesn't really work wel. Instead I want to show this black overlay window with a listbox taking up most of the screen so the user can simply touch the value he wants with a single click.
Update: I just realized I can use the ChildWindow class to do this.
But my original question remains.
My general solution for this problem is by writing a custom behavior. It's not a pure XAML solution but it gives you a lot more flexibility.
Create a behavior that searches up the VisualTree to find the right item and then have it set the width of the Popup correctly.
It may be a little more complicated than a straight binding but it avoids all the naming issues.
Put the following in the constructor of your control so you can avoid naming it:
DataContext = this;
I want to create a WPF window that behaves as a modal dialogue box while at the same time facilitating selected operations on certain other windows of the same application. An example of this behaviour can be seen in Adobe Photoshop, which offers several dialogues that allow the user to use an eyedropper tool to make selections from an image while disabling virtually all other application features.
I'm guessing that the way forward is to create a non-modal, always-on-top dialogue and programmatically disable those application features that are not applicable to the dialogue. Is there an easy way to achieve this in WPF? Or perhaps there's a design pattern I could adopt.
Yes, there is the traditional approach you describe where you programmatically enable/disable features, but WPF also opens up several new possiblities that were really not possible in WinForms and older technologies.
I will explain four WPF-specific ways to do this:
You can secretly and automatically replace a window's contents with a picture of its contents using a Rectangle with a VisualBrush, thereby effectively disabling it. To the user it will look as if the window is unchanged, but the actual contents will be there underneath the picture, so you can use it for hit-testing and even forward selected events to it.
You can add a MergedDictionary to your window's ResourceDictionary that causes all TextBoxes to become TextBlocks, all Buttons to become disabled, etc, except as explicitly overridden using custom attached properties. So instead of looping through all your UI selectively enabling/disabling, you simply add or remove an object from the MergedDictionaries collection.
You can use the InputManager to programmatically generate and process real mouse events in particular parts of a disabled window, disallowing any mouse events that don't hit-test to something "approved."
Use data binding and styles to enable/disable individual controls rather than iterating through them
Details on replacing window with picture of window
For this solution, iterate your app windows and replace each content with a Grid containing the original Content and a Rectangle, like this:
<Window ...>
<Grid>
<ContentPresenter x:Name="OriginalContent" />
<Rectangle>
<Rectangle.Fill>
<VisualBrush Visual="{Binding ElementName=OriginalContent}" />
</Rectangle.Fill>
</Rectangle>
</Grid>
</Window>
This can be done programmatically or by using a template on the Window, but my preference is to create a custom control and create the above structure using its template. If this is done, you can code your windows as simply this:
<Window ...>
<my:SelectiveDisabler>
<Grid x:Name="LayoutRoot"> ... </Grid> <!-- Original content -->
</my:SelectiveDisabler>
</Window>
By adding mouse event handlers to the Rectangle and calling VisualTreeHelper.HitTest on the ContentPresenter to determine what object was clicked in the original content. From this point you can choose to ignore the mouse event, forward it to the original content for processing, or in the case of an eyedropper control or an object selection feature, simply extract the desired objects/information.
Details on MergedDictionary approach
Obviously you can restyle your whole UI using a ResourceDictionary merged into your window's resources.
A naiive way to do this is to simply create implicit styles in the merged ResourceDictionary to make all TextBoxes appear as TextBlocks, all Buttons appear as Borders, etc. This does not work very well because any TextBox with its own style or ControlTemplate explicitly set may miss the updates. In addition, you may not get all objects as desired, and there is no way to easily remove the Commands or Click events from buttons because they are explicitly specified and the style won't override that.
A better way to work this is to have the styles in the merged ResourceDictionary set an attached property, then use code-behind in the PropertyChangedCallback to update the properties you really want to change. Your attached "ModalMode" property, if set to true, would save all the local values and bindings for a number of properties (Template, Command, Click, IsEnabled, etc) in a private DependencyProperty on the object, then overwrite these with standard values. For example a button's Command property would be set to null temporarily. When the attached "ModalMode" property goes false, all the original local values and bindings are copied back from the temporary storage and the temporary storage is cleared.
This method provides a convenient way to selectively enable/disable portions of your UI by simply adding another attached property "IgnoreModalMode". You can manually set this to True on any UIElements that you don't want the ModalMode changes to apply to. Your ModalMode PropertyChangedCallback then checks this and if is true, it does nothing.
Details on InputManager approach
If you capture the mouse, you can get mouse coordinates no matter where it is moved. Translate these to screen coordinates using CompositionTarget.TransformToDevice(), then use CompositionTarget.TransformFromDevice() on each candidate window. If the mouse coordinates are in bounds, hit-test the disabled window (this can still be done even when a window is disabled), and if you like the object the user clicked on, use InputManager.ProcesInput to cause the mouse event to be processed in the other window exactly as if it was not disabled.
Details on using data binding
You can use a styles to bind the IsEnabled property of Buttons, MenuItems, etc to a static value like this:
<Setter Property="IsEnabled" Value="{Binding NonModal, Source={x:Static local:ModalModeTracker.Instance}}" />
Now by default all items with these styles will automatically disable when your NonModal property goes false. However any individual control can override with IsEnabled="true" to stay enabled even in your modal mode. More complex bindings can be done with MultiBinding and EDF ExpressionBinding to set whatever rules you want.
None of these approaches require iterating through your visual interface, enabling and disabling functionality. Which of these you actually select is a matter of what functionality you actually want to provide during modal mode, and how the rest of your UI is designed.
In any case, WPF makes this much easier than it was in WinForms days. Don't you just love WPF's power?
What you're looking for is similar to a Multiple Document Interface. This isn't available by default in WPF but there are some efforts out there to support this, both free and commercial.
It will be up to you to identify the current state of the application and enable/disable UI elements in response to this.
I think an always-on-top windows that programmatically disables certain app features is the way to do this. It might be easier to keep a "white list" of features that can be enabled while this form is open, and then disable everything that isn't on the list (as opposed to trying to maintain a "black list" of everything that can't be enabled).
I believe the best approach to solve this is using the InputManager approach mentioned previously. This design pattern allows you to connect commands to your toolbar buttons/menu items etc and each will call a CanExecute handler you specify for your command. In this handler, you would set the command to not enable if your always-on-top non-modal window was open.
http://msdn.microsoft.com/en-us/library/ms752308.aspx