Q: How should I handle Datatemplates for Models? - c#

In the MVVM pattern, the view shouldn't know anything about the models, but what if I wanna display different types differently?
For example I have two classes. The class Message and the class AttachmentMessage which inherits from Message.
Message
public class Message
{
public string Content { get; set; }
}
AttachmentMessage
public class AttachmentMessage : Message
{
public string Filename { get; set; }
}
Now when I use them in an ObservableCollection<Message>, I have both models in this collection, but I can't tell WPF which Datatemplate it has to use, without knowing which Models there are.
So what are solutions for this problem?

The most common and recommended way would be to create a data template for each type you need and put that in your resources.
The following code assumes your observable collection has the name Messages.
Example:
<ItemsControl ItemsSource="{Binding Messages}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:Message}">
<TextBlock Text="{Binding Content}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:AttachmentMessage}">
<TextBlock Text="{Binding Filename}"/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
The other way is to create a DataTemplateSelector. Let's say your messages all had a property that indicated their priority. You could create a template selector like the below. DataTemplateSelector can be used when you need more fine-grained control over which template is selected.
public class MyDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is Message m && container is FrameworkElement fm)
{
if (m.Priority == Priority.High)
{
return fm.FindResource("HighPriorityTemplate") as DataTemplate;
}
else
{
return fm.FindResource("NormalPriorityTemplate") as DataTemplate;
}
}
return null;
}
}
And use it in xaml like the following:
<Window.Resources>
<!-- Put your templates here-->
<local:MyDataTemplateSelector x:Key="MyDataTemplateSelector"/>
</Window.Resources>
<ItemsControl ItemsSource="{Binding Messages}" ItemTemplateSelector="{StaticResource MyDataTemplateSelector}">
As I side note, in the MVVM pattern, usually you have three parts, Model, View and ViewModel. Some people take the shortcut of binding directly to the Model, but I generally would avoid this. You can find a discussion about this here.

Related

How to customize UI or DataTemplate for each item in listView UWP C#

I have a listView containing different types of items and I need to display them using different elements in UI. e.g. i have children and adult members in listView, and children will not have kids, spouses etc, while adults will have their children, spouses, workplace etc. As far as i know, once i layout structure in XAML using data template, i cannot change it. I created a UserControl for different items, not sure how to use it in ListView when adding items.
Looking for help on how to do this.
Thanks in advance.
Based on your scenario, you could try to use DataTemplateSelector Class. This class enables you to apply different templates for ListView based on your own logic.
Here are the steps that you need to do to implement this:
You will need to create your own DataTemplateSelector Class. Then you could declare each template as a property of the class.
You need to create an instance of your own DataTemplateSelector class in the Resources section of your XAML file. You should create instances of DataTemplate objects and define their layout in the resources section. Then assign these data templates to the template properties you declared in the DataTemplateSelector class.
The final step is that assign the DataTemplateSelector class to the ItemTemplateSelector property of the ListView.
I've made a simple demo and you could refer to the following code:
Code behind:
public sealed partial class MainPage : Page
{
public List<int> NumbersList { get; set; }
public MainPage()
{
this.InitializeComponent();
NumbersList = new List<int>();
for (int i=0;i<10; i++)
{
NumbersList.Add(i);
}
}
}
public class MyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate ChildrenTemplate { get; set; }
public DataTemplate AdultTemplateent { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
// use your own conditions
if ((int)item % 2 == 0)
{
return AdultTemplateent;
}
else
{
return ChildrenTemplate;
}
}
}
Xaml:
<Page.Resources>
<DataTemplate x:Key="AdultTemplateent" x:DataType="x:Int32">
<StackPanel Orientation="Horizontal" Background="LightGray">
<TextBlock Text="This is Adult Item" Margin="5"/>
<TextBlock Text="{x:Bind}" Margin="5"/>
<TextBlock Text="Workplace:NewYork" Margin="5"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ChildrenTemplate" x:DataType="x:Int32">
<StackPanel Orientation="Vertical" Background="LightBlue">
<TextBlock Text="This is Children Item" Margin="5" />
<TextBlock Text="{x:Bind}" Margin="5" />
<TextBlock Text="School:DC" Margin="5"/>
</StackPanel>
</DataTemplate>
<local:MyDataTemplateSelector x:Key="MyDataTemplateSelector" AdultTemplateent="{StaticResource AdultTemplateent}" ChildrenTemplate="{StaticResource ChildrenTemplate}"/>
</Page.Resources>
<Grid>
<ListView x:Name = "TestListView"
ItemsSource = "{x:Bind NumbersList}"
ItemTemplateSelector = "{StaticResource MyDataTemplateSelector}">
</ListView>
</Grid>
How it looks like:
You could get more detailed information here: Data template selection: Styling items based on their properties

Set the DataContext of a View on a Navigation in XAML/WPF using MVVM

in my WPF-application i have multiple Views in a main window and i tried to implement a navigation between those.
My Problem is that i can't set the DataContext attribute of the views.
My MainWindowViewModel:
public Class MainWindowViewModel
{
public MainScreenViewModel mainScreenViewModel { get; set; }
public LevelViewModel levelViewModel { get; set; }
public ViewModelBase CurrentViewModel
{
get { return _currentViewModel; }
set
{
_currentViewModel = value;
RaisePropertyChanged(nameof(CurrentViewModel));
}
}
private AdvancedViewModelBase _currentViewModel;
}
My MainWindow:
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModels:MainScreenViewModel}">
<views:MainScreen />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:LevelViewModel}">
<views:LevelView />
</DataTemplate>
</Window.Resources>
<Border>
<StackPanel>
<UserControl Content="{Binding Path=CurrentViewModel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></UserControl>
</StackPanel>
</Border>
So the main idea is that the CurentViewModel shows on which View the navigation is at the moment (the DataTemplate shows the coreponding View to the ViewModel).
The Problem is that the shown View doesn't get the DataContext (so the properties mainScreenViewModel/levelViewModel of the MainWindowViewModel), it creates a new instance of the ViewModels.
Is it possible to hand over the properties as a DataContext to the View from the DataTemplate?
Thanks for your help!
The Content property contains
An object that contains the control's content
This means it is not the correct property to bind the view model. Instead you need to bind it to the DataContext property which contains
The object to use as data context
Now the defined templates are selected by their type like defined in the resources.
This means your code is almost correct, just change the binding of the CurrentViewModel like
<UserControl DataContext="{Binding CurrentViewModel}"/>
to get your code to work.

Change View with its ViewModel based on a ViewModel Property

As far as I know in WPF you can do something like this:
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModels:IronStage1ViewModel}">
<Views:IronStage1View/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:IronStage2ViewModel}">
<Views:IronStage2View/>
</DataTemplate>
<Views:TestStageToTabIndexConverter x:Key="TestStageToTabIndexConverter" />
</Window.Resources>
My question:
Is there any way to choose the View based on a property in your ViewModel?
something like this:
<Window.Resources> //If property Selector==1
<DataTemplate DataType="{x:Type ViewModels:IronStage1ViewModel}">
<Views:IronStage1View/>
</DataTemplate>
// If property Selector==2
<DataTemplate DataType="{x:Type ViewModels:IronStage1ViewModel}">
<Views:IronStage2View/>
</DataTemplate>
</Window.Resources>
Would a datatemplate selector do this?
tutorial here
This is how this would apply to your scenario:
First create a DataTemplateSelector:
public class IronStageTemplateSelector : DataTemplateSelector
{
public DataTemplate IronStage1Template { get; set; }
public DataTemplate IronStage2Template { get; set; }
public object IronStage1Selector { get; set; }
public object IronStage2Selector { get; set; }
public override DataTemplate SelectTemplate(object selector,
DependencyObject container)
{
if(selector == this.IronStage1Selector)
{
return IronStage1Template;
}
return IronStage2Template;
}
}
I have extended the tutorial to include properties you can assign for when to return each template.
Declare the XAML resources
<UserControl.Resources>
<DataTemplate x:Key="iron1Template">
<TextBlock/>
</DataTemplate>
<DataTemplate x:Key="iron2Template">
<Label />
</DataTemplate>
<System:Double x:Key="Selector1">1</System:Double>
<System:Double x:Key="Selector2">2</System:Double>
<local:IronStageTemplateSelector x:Key="IronStageTemplateSelector"
IronStage1Selector="{StaticResource Selector1}"
IronStage2Selector="{StaticResource Selector2}"
IronStage1Template="{StaticResource iron1Template}"
IronStage2Template="{StaticResource iron2Template}"/>
</UserControl.Resources>
In this example we have declared our selector so that when our property has value 1, template1 is returned, otherwise we get template 2.
Add Control to XAML
Finally, a little hack is needed - your VM property needs to be IEnumerable...
<ItemsControl ItemsSource="{Binding toProperty}"
ItemTemplateSelector="{StaticResource IronStageTemplateSelector}">
</ItemsControl>
I hope this helps, please mark as answer if you found it useful
Is the view model property of known type at compile time? if so you can just add the control directly into main (parent) view and bind datacontext to the view model property.
something like this..
<Address:AddressControl Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="6" DataContext=" {Binding PresentAddress}"/>
Just let me know if you have different scenario.

Changing the View for a ViewModel

I am trying to implement the MVVM design patern for mt WPF application. In order to connect the view to the viewmodels, I use a ResourceDictionary (used in Application.Resources), that looks like
<DataTemplate DataType={x:Type viewmodel:SampleViewModel}>
<view:SampleView1 />
</DataTemplate>
The view models are then simply put into content presenters to display them.
Now, when the user presses a button, I'd like to display SampleViewModel using a different view. How do I change the data template used for SampleViewModel?
Less words more code.
As far as you said, you have the class SampleViewModel. I added the property Title for demonstration and ViewType for identifying the correct view:
public enum ItemViewType { View1, View2 };
public class SampleViewModel
{
public string Title { get; set; }
public ItemViewType ViewType { get; set; }
}
The DataTemplateSelector for two views depending on the ViewType property:
class ItemViewTemplateSelector : DataTemplateSelector
{
public DataTemplate View1Template { get; set; }
public DataTemplate View2Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var vm = item as SampleViewModel;
if (vm == null)
return null;
switch (vm.ViewType)
{
case ItemViewType.View1:
return View1Template;
case ItemViewType.View2:
return View2Template;
}
return null;
}
}
Xaml code:
<Window.Resources>
<DataTemplate x:Key="view1Template">
<TextBlock Text="{Binding Title}" Foreground="Red"/>
</DataTemplate>
<DataTemplate x:Key="view2Template">
<TextBox Text="{Binding Title}" />
</DataTemplate>
<local:ItemViewTemplateSelector x:Key="viewTemplateSelector"
View1Template="{StaticResource view1Template}"
View2Template="{StaticResource view2Template}"/>
</Window.Resources>
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<StackPanel>
<Button Content="ChangeView" HorizontalAlignment="Center" Command="{Binding SwitchViewCommand}"/>
<ContentControl Content="{Binding ItemViewModel}" ContentTemplateSelector="{StaticResource viewTemplateSelector}"/>
</StackPanel>
The main part is in the class MainViewModel where I've put the logic for switching views:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
this.ItemViewModel = new SampleViewModel { Title = "Some title", ViewType = ItemViewType.View1 };
this.SwitchViewCommand = new RelayCommand(() =>
{
this.ItemViewModel.ViewType = this.ItemViewModel.ViewType == ItemViewType.View1
? ItemViewType.View2
: ItemViewType.View1;
//The magic senquence of actions which forces a contentcontrol to change the content template
var copy = this.ItemViewModel;
this.ItemViewModel = null;
this.ItemViewModel = copy;
});
}
public RelayCommand SwitchViewCommand { get; set; }
private SampleViewModel itemViewModel;
public SampleViewModel ItemViewModel
{
get { return itemViewModel; }
set
{
itemViewModel = value;
RaisePropertyChanged("ItemViewModel");
}
}
}
The SwitchViewCommand can be any type of command, I use the command from the mvvmlight library.
Inside the handler of the command I change the type of viewmodel and update the property ItemViewModel in a tricky way because a ContentControl refreshes a view only if to change the Content property, and this property will not be changed unless you set a reference to different object.
I mean, even the code this.ItemViewModel = this.itemViewModel will not change the view.
It's strange, but the workaround doesn't require much work.
You can achieve this in many different ways depends upon the architecture you want.
You can write a custom DataTemplateSelector and use it on ContentControl.ContentTemplateSelector and choose those two templates appropriately
If this pattern of changing the view occures in many different places and more frequent UX, I would also recommend those two views toggled using a DataTemplate.DataTrigger based on a property in SampleViewModel [I am guessing you might have a distinguishing property in the ViewModel to know that state]
You can override the mapping by placing a similar resource lower down in the tree. Since WPF will resolve the resource by searching upwards, such an override will replace your existing mapping.

WPF TreeView does not apply DataTemplate accordingly

I have a business object project, which contains composite structure:
public class Tree
{ public IProductComponent TreeRoot { get; set; } }
public interface ITreeComponent
{ public string Name { get; set; } }
public class ContainerComponent : ITreeComponent
{ public BindingList<ITreeComponent> Children { get; set; } }
public class LeafComponent : ITreeComponent
{ }
I need to bind this structure to a TreeView in my WPF project. The tree view first:
<TreeView x:Name="treeView" Grid.ColumnSpan="2">
<TreeView.Resources>
<HierarchicalDataTemplate
ItemsSource="{Binding Children}"
DataType="{x:Type businessObjects:ContainerComponent}">
<Label Content="{Binding Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type businessObjects:LeafComponent}">
<Label Content="{Binding Name}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
And the code for binding:
bTreeView = new Binding();
bTreeView.Source = MyTree;
bTreeView.Path = new PropertyPath("TreeRoot.Children");
treeView.SetBinding(TreeView.ItemsSourceProperty, bTreeView);
The problem is that the TReeView does not actually use those templates (it displays only the top level of hierarchy and calls .ToString() to display those items. Please tell me where have I gone wrong. Otherwise, if I set the it is working, but I cannot define two templates there.
Thanks.
Well I notice you are putting the template in resources, not under TreeVeiw.ItemTemplate.
TreeView should have an ItemTemplate (the Hierarchical) and the ItemsSource set. Shouldn't need anything more than that.
Would help with example data for us to test though.
My bad - the Main assembly was loading the dll with entities two times instead of one. That caused it to go crazy - as soon as I fixed it and the assembly loaded once the problems went away.

Categories

Resources