Different views / data template based on member variable - wpf

I have a view model called
ViewModelClass
which contains a boolean.
I have another view model which contains
ObservableCollection<ViewModelClass> m_allProjects;
Then I have this in my view:
<DataTemplate>
<views:ProjectInfoView x:Key="ProjectInfoDetailTemplate"/>
</DataTemplate>
<ItemsControl Grid.Row="1" Grid.Column="0"
ItemsSource="{Binding AllProjects}"
ItemTemplate="{StaticResource ProjectInfoDetailTemplate}"
Margin="10,28.977,10,10">
</ItemsControl >
I want, based on the boolean in the AllProjects-collection, to use a different datatemplate. What is the best way to do this?
I know I can do this with different ViewModels and use a kind of ViewModel-base object, but I prefer just to use 1 view model.
EDIT:
I want to do this with data triggers. Can someone provide me with some code please?

I usually use a ContentControl to display the data, and swap out the ContentTemplate in a trigger based on the property that changes.
Here's an example I have posted on my blog that swaps a template based on a bound property
<DataTemplate x:Key="PersonTemplate" DataType="{x:Type local:ConsumerViewModel}">
<TextBlock Text="I'm a Person" />
</DataTemplate>
<DataTemplate x:Key="BusinessTemplate" DataType="{x:Type local:ConsumerViewModel}">
<TextBlock Text="I'm a Business" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ConsumerViewModel}">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource PersonTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ConsumerType}" Value="Business">
<Setter Property="ContentTemplate" Value="{StaticResource BusinessTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
A DataTemplateSelector will also work, but only if the property that determines which template to show doesn't change since DataTemplateSelectors don't respond to change notifications. I usually avoid them if possible since I also prefer my view selection logic in my view so I can see whats going on.

Related

DataTrigger in Style in HierarchicalDataTemplate not working -- TreeView

I have a DataTrigger to set a TextBox's Background based on a bound property.
Here's a streamlined version of the xaml:
<TreeView >
<TreeViewItem Header="Things" >
<TreeViewItem.Resources>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsDirty}" Value="True">
<Setter Property="Background" Value="LightGray" />
</DataTrigger>
</Style.Triggers>
</Style>
<HierarchicalDataTemplate DataType="{x:Type local:Type1}" ItemsSource="{Binding Children, Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Margin="6,0,6,0" />
<TextBlock Text="{Binding IsDirty}" Margin="6,0,6,0" />
<i:Interaction.Behaviors>
<dragDrop:FrameworkElementDropBehavior DragEffect="Move" />
</i:Interaction.Behaviors>
</StackPanel>
</HierarchicalDataTemplate>
</TreeViewItem.Resources>
</TreeViewItem>
I added a TextBlock to display the value of the IsDirty property; when that is true, the Background remains unchanged.
I have tried moving the Style to the HierarchicalDataTemplate.Resources, but that made no difference.
What am I overlooking?
Thanks --
That's because implicit styles targeting types not derived from Control do not cross the template boundary, i.e. are not applied inside templates unless they're defined within that template's scope. Here's a good post explaining how it works and why does it work this way.
In order to cross the template boundary, you should use a type deriving from Control (e.g. a Label) instead of a TextBlock and define implicit style targeting that type.
Otherwise, you could put your style in scope of the template in question by moving it into the template's resources dictionary:
<HierarchicalDataTemplate (...)>
<HierarchicalDataTemplate.Resources>
<Style TargetType="{x:Type TextBlock}">
(...)
</Style>
</HierarchicalDataTemplate.Resources>
(...)
</HierarchicalDataTemplate>

TreeView HierarchicalDataTemplate does not apply ItemContainerStyle

I try do display hierarchical data with a TreeView and I would like to set different DataTemplates for my different Children types.
But the thing is, that my style does not get applied.
Maybe its a very simple mistake but i really do not find it.
<TreeView ItemsSource="{Binding List}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Main}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Property1}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Type2}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<TextBlock Text="{Binding Property2}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Type3}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="False"/>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Ok, I know what is going wrong. HierarchicalDataTemplate.ItemContainerStyle contains a style which is applied to the ItemsContainer where the children for the current node are stored. Try this as an experiment, change your style to:
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
<Setter Property="Foreground" Value="Navy" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
You will notice that the node that you put this style onto continues to have a black foreground, but all it's children will now have a foreground of Navy.
It is a little counter-intuitive, but when you think about it, I guess it makes sense. So, bearing this in mind, I think the best solution is to bind IsExpanded for all TreeViewItems to a variable in the VM and then pick different values based on types there.
I had a similar issue maybe. In case Main, Type2 and Type3 are interfaces the selection in XAML won't work, I had to use classes. If you want to use interfaces you could implement a template selector.

WPF - MVVM - Conditionally include user controls to view

In WPF my MVVM application I need to create an account search view with 2 options simple search by account # or Advanced search (by name, email, etc.)
In my AccountSearchViewModel I have a bool property IsAdvancedMode.
Also I have created 2 UserControls for each mode: SimpleSearchView and AdvancedSearchView
Now I need to show either one based on IsAdvancedMode property.
What is the best way to do it?
Also as a general solution what if I have SearchMode property that is enum. How whould you switch between multiple controls in that case?
I think you need to use Data Templating, to do that you need to create three classes:
public class Search
{
//Your Code
}
public class AdvanceSearch : Search
{
//Your Code
}
public class SimpleSearch : Search
{
//Your Code
}
and then create Data Template base on Classes:
<DataTemplate DataType="{x:Type local:AdvanceSearch }">
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=Email}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SimpleSearch }">
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
I would use a DataTrigger to swap out the ContentTemplate of a ContentControl as needed. I wrote an article about switching Views in MVVM here if you're interested (examples included)
Here's some quick test code demonstrating it:
<Window.Resources>
<DataTemplate x:Key="TemplateA" >
<TextBlock Text="I'm Template A" />
</DataTemplate>
<DataTemplate x:Key="TemplateB" >
<TextBlock Text="I'm Template B" />
</DataTemplate>
</Window.Resources>
<StackPanel>
<ToggleButton x:Name="Test" Content="Test" />
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateA}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Test, Path=IsChecked}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</StackPanel>
I usually drop them both and then use the BooleanToVisibilityConverter. Simplest approach with what you've got set up.
<Grid>
<SimpleSearch />
<AdvancedSearch
Visibility="{Binding IsAdvancedMode, Converter={StaticResource btvc}"/>
</Grid>
When IsAdvancedMode is true, the AdvancedSearch control will overlay the SimpleSearch. Again, this is the simplest approach, not necessarily the absolute best.

What is the best way to switch views/usercontrols in MVVM-light and WPF?

I'm relatively new to WPF and MVVM and the hardest thing I have found is how to simply switch a usercontrol or a view in an application.
In winforms, to have a control remove itself you would simple say this.Parent.Controls.Remove(this);
In WPF there is no generic Parent control, you would have to typecast it to the specific type (i.e. Grid) and then remove it.
This also seems to break the MVVM architecture. I have also tried data templates and content presenters, which work well, except for the fact that I can't change the datacontext from code, since the datacontext is always the viewmodellocator.
Are pages the acceptable way to do this in WPF now? What if I had a grid with a custom usecontrol and I wanted to switch it based on some variable in the viewModel? It seems like the simplest tasks cannot be accomplished easily in WPF.
You would do so in your parent ViewModel.
For example, if your page (call it PageViewModel) had two views (ViewModelA and ViewModelB), you would have a property on PageViewModel called CurrentView, and this would determine which View is visible. When PageViewModel.CurrentView is set to an instance of ViewModelA, then ViewA's DataTemplate is used to draw the content. When it's set to an instance of ViewModelB, ViewB's DataTemplate is displayed.
<DataTemplate DataType="{x:Type local:PageViewModel}">
<ContentControl Content="{Binding CurrentView}" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelA}">
<TextBlock Text="I'm ViewModelA" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelB}">
<TextBlock Text="I'm ViewModelB" />
</DataTemplate>
It would be ideal to call the switch views command from the parent view (in this case the DataTemplate for the PageViewModel), however if you wanted to switch views from within ViewModelA/B, you can either hook up the event manually when the objects get created (CurrentView.ChangeViewCommand = this.ChangeViewCommand) or look into a messaging system. MVVM Light has a simple Messenger class which I found was fairly easy to use, or Prism has a more advanced EventAggregator
If you want to switch Views for the same ViewModel, I would recommend a Mode property that gets used to determine which view to use. For example:
<DataTemplate x:Key="ViewA" DataType="{x:Type local:MyViewModel}">
<TextBlock Text="I'm ViewModelA" />
</DataTemplate>
<DataTemplate x:Key="ViewB" DataType="{x:Type local:MyViewModel}">
<TextBlock Text="I'm ViewModelB" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:MyViewModel}">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource ViewA}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Mode}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource ViewB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
EDIT
I actually see this kind of question come up a lot, so posted something about it here if anyone is interested

WPF Highlight Item

I have a ViewModel that provides a collection of Items. There is also a ActiveItem propery. The Items collection may or may not contain ActiveItem.
What I want to do (in XAML) is display the items as a list and highlight any of the items that are equal to Active Item.
I have tried the following with no success:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border x:Name="outerBorder" Background="Green">
<TextBlock Text="{Binding ItemId}" />
</Border>
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding
Path=DataContext.Item.ItemId,
RelativeSource={RelativeSource TemplatedParent},
Mode=Default}"
Value="{Binding
Path=DataContext.ActiveItem.ItemId,
RelativeSource={RelativeSource AncestorType=Window},
Mode=Default}"
>
<Setter TargetName="outerBorder"
Property="Background" Value="Orange" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As you can see I attempted to use a DataTrigger to match the current item with the ActiveItem but it doesn't work. I think this is something to do with my trying to use a Binding in DataTrigger.Value - something I haven't seen any other examples of.
Any ideas how I might make this work?
Thanks,
Daniel
Since you're using MVVM, why not just have the view model expose a property telling the view whether it's active or not? That'll get that logic out of you view and into your VM.

Resources