How to switch between views using DataTemplate + Triggers - c#

I have a requirement where a where user can switch to view hierarchical data either as tree or as a text in datagrid or as FlowChart.
The user can do this by clicking a Toggle Button which say: Switch Mode. I want to do all this in such a way that it can be handled within the View only as ViewModel in all the three cases is the same.
How do I apply View to my ViewModel based on Trigger.

If the state of which view to show is saved in some enum property you could use a ContentControl and DataTriggers for example:
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding ViewMode}" Value="TreeMode">
<Setter Property="Content">
<Setter.Value>
<uc:TreeModeView />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ViewMode}" Value="GridMode">
<Setter Property="Content">
<Setter.Value>
<uc:GridModeView />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
(As the style is only used in one place, by setting it directly as ContentControl.Style this will work, if you want to use it in more than one place you should set the ContentTemplate instead, because otherwise there will only be one view instance shared by all controls with the style which is not allowed by WPF (of course Content needs to be set to something for the template to be applied))
You could also bind directly to IsChecked of the ToggleButton using ElementName of course. The relevant values would then be True, False and {x:Null}.

H.B.'s answer is good, but there are scenarios where it's not quite so good.
If constructing the views (and their underlying view models) is expensive, then toggling the Content property will pay this expense every time the user changes the view.
In some scenarios, it makes sense to create both views in the same container (e.g. a Grid), and toggle their Visibility instead. If you use lazy evaluation in your view models, the expensive operations won't be conducted until the view becomes visible, and - importantly - they'll only be conducted the first time the view becomes visible. Once both views have been displayed, the user can toggle back and forth between views without reconstructing the underlying view models.
Edit:
I stand corrected, sort of: H.B.'s answer is not as good as it looked .
You can't use a style to set the Content property of a ContentControl to a UIElement. See this blog post for full details, but the long and short of it is that if you use H.B.'s approach, you'll get a runtime error.
You can set the ContentTemplate property, instead, e.g.:
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding ViewMode}"
Value="TreeMode">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<uc:TreeModeView/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ViewMode}"
Value="GridMode">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<uc:GridModeView/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>

Related

Dynamic content of a Grid

My data model has a property of the enumeration type. I wonder if there is way to place dynamically a user control based on the value of the enumeration type?
I am currently investigating in the following direction:
<Grid Name ="AdjustmentsArea" DockPanel.Dock ="Right" MinWidth ="100" Visibility ="Collapsed" >
<ContentControl DataContext ="{Binding AjustmentView}">
<Style TargetType ="model:AjustmentViews">
<Style.Triggers>
<DataTrigger Binding ="{Binding}" Value ="Settings">
/// is it possible in principle to point a user control using a Setter ???
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl>
</Grid>
May be also I am on a wrong path. But I would like to know (learn) if it is possible to implement this requirement for dynamic content in user control, but not using hide/show exised element approach.
What would you recommend?
you can set different template depending on trigger binding value
<ContentControl DataContext ="{Binding AjustmentView}">
<ContentControl.Style>
<Style TargetType ="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value ="Settings">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate> <!--template with UserControl here--> </ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
e.g. WPF Slider uses this approach when Orientation changes (Horizontal or Vertical)

Having trouble setting visibility of control through DataTemplates

So, I'm building an order tracking app with different user accounts, some of whom have less need-to-know than others. This means that certain controls are displayed for some accounts, and hidden for others.
The datacontext for the Window is set to my Order class, and the data binding within the text fields works perfectly in regards to displaying properties from the specific Order. However, the DataTemplates and Triggers I've made don't seem to be doing anything at all, and I'm not entirely sure why. I've looked all over the web and I can't seem to find why it's not working. Here's the XAML:
<Label Name="StatusLabelText" Content="Status:" FontSize="15" DockPanel.Dock="Top">
<Label.Resources>
<DataTemplate DataType="x:Type local:Order">
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=selectedAccount}" Value="Color Correct">
<Setter Property="Visibility" Value="Hidden"></Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Label.Resources>
</Label>
I suspect you want to hide label in case selectedAccount value is Color Correct.
You need Style to do that and not a template if my assumption is correct which can be done like this:
<Label Name="StatusLabelText" Content="Status:" FontSize="15"
DockPanel.Dock="Top">
<Label.Style>
<Style TargetType="Label">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=selectedAccount}"
Value="Color Correct">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
On a side note, you should use Collapsed instead of Hidden to set visibility of control in case you don't want the label to take the size even when it's not visible on GUI. Read more about it here.

TreeViewItem to notify ViewModel when it is hovered

I am currently using the code from this blogpost in order to have my TreeView highlight those items, which are currently hovered by the mouse. This is working as intended, however now I want the TreeViewItems to notify their attached ViewModels when they are hovered / not hovered.
However I'm at a loss on how I can achieve this. The corresponding XAML code looks like the following:
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<Trigger Property="Controls:TreeViewHelper.IsMouseDirectlyOverItem" Value="True">
<Setter Property="Background" Value="Green" />
</Trigger>
</Stile.Triggers>
</Style>
How can I bind the property from my ViewModel, named TreeNodeModel.IsHovered to the TreeViewItem (or probably the attached dependency property IsMouseDirectlyOverItem) so that I can react on those changes from within my code?
All the examples I found via google only explained how the set the background color. Thanks in advance for your time on the probably trivial answer.
In your Style, try adding a Setter which binds IsMouseDirectlyOverItem to IsHovered, and use the OneWayToSource binding mode to push the value the right way:
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Controls:TreeViewHelper.IsMouseDirectlyOverItem"
Value="{Binding Path=IsHovered, Mode=OneWayToSource}" />
<Style.Triggers>
...
</Style>
EDIT: As IsMouseDirectlyOver is read-only, and read-only DPs can't be the target of any bindings, Fredrik Hedblad's PushBinding may be a possible workaround: OneWayToSource Binding for ReadOnly Dependency Property
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="pb:PushBindingManager.StylePushBindings">
<Setter.Value>
<pb:PushBindingCollection>
<pb:PushBinding TargetDependencyProperty="Controls:TreeViewHelper.IsMouseDirectlyOverItem"
Path="IsHovered" />
</pb:PushBindingCollection>
</Setter.Value>
</Setter>
<Style.Triggers>
...
</Style>

Dealing with WPF Menu HeaderStringFormat and Access Keys among other things

Okay. So I want my application to display in its main menu the "Save" and "Save As..." items just like Visual Studio does; i.e. "Save {current file}" and "Save {current file} As..."
I would also like to have the normal access keys ("S" and "A", respectively).
I've come up with two solutions, but neither is very desirable.
Instead of creating the main menu exclusively in xaml, I could
create it all in the MainWindowViewModel so I'd have full control over what goes into the generated MenuItems. However, I feel that this would be a violation of MVVM (which I'm attempting to abide by very strictly this time around) as I would have to include references to each MenuItem's Icon in the ViewModel. Plus it seems a little messy.
I can stipulate the header of just these two specific MenuItems (and perhaps future ones) like so, but then I end up getting a MenuItem that not only has a underscore in the header, but also does not contain an access key.
<MenuItem Header="{Binding CurrentFileName}"
HeaderStringFormat="Save {0} _As...">
What should I do?
Whelp, figured it out. At least about how to get it done with the whole main menu described in XAML. Just made the header content an AccessText control instead of a string and it works like a charm.
<MenuItem>
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding HasSelection}" Value="false">
<Setter Property="IsEnabled" Value="false"/>
<Setter Property="Header">
<Setter.Value>
<AccessText Text="Save Selected File _As..."/>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding HasSelection}" Value="true">
<Setter Property="IsEnabled" Value="true"/>
<Setter Property="Header">
<Setter.Value>
<AccessText Text="{Binding SelectedFile.Filename, StringFormat=Save {0} _As...}"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>

How to dynamically inject a user control based on user selection

I have a TreeView control on my window that depending on which selection the user picks should display a user control (figuring out which user control to display is complete). I'm having issues figuring out how to actually display the user control. Essentially, the user would select item from the TreeView and based on the selection a user control would appear (in a ContentControl control I'm assuming).
Currently, for opening new windows I have a window adapter where I can create new windows on the fly and set the parent.
How can I accomplish this in my view model?
Edit
Here is what I believe Rachel was talking about when she mentioned using DataTemplate's instead. Don't be worried of my DataTemplates instead the DataType attribute. That's simply the name of my project.
<Window.Resources>
<DataTemplate DataType="{x:Type DataTemplates:FooEditorViewModel}">
<DataTemplates:FooControl></DataTemplates:FooControl>
</DataTemplate>
<DataTemplate DataType="{x:Type DataTemplates:BarEditorViewModel}">
<DataTemplates:BarControl></DataTemplates:BarControl>
</DataTemplate>
</Window.Resources>
And here is a sample view model.
public class ViewModel
{
public IEditorViewModel Editor
{
get
{
return new BarEditorViewModel();
}
}
}
And glue it all together with
<ContentControl Content="{Binding Editor}" />
I had to create a blank interface called IEditorViewModel in order to return different user control editors. Not sure if there's any way around it.
Hopes this helps someone.
Your SelectedTreeViewItem would be stored in your ViewModel, and that value would be used to determine which item to display.
An example would be something like:
<ContentControl Content="{Binding SelectedItem}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem}" Value="{x:Type local:ItemA}">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateA}" />
</DataTrigger>
<DataTrigger Binding="{Binding SelectedItem}" Value="{x:Type local:ItemB}">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
A better alternative is to use DataTemplates for different Items. Then you just set your Content="{Binding SelectedItem}" and WPF will resolve the correct DataTemplate to use. I just showed the above example first because it can be used to base your Template off a Property of SelectedItem

Categories