Change View with its ViewModel based on a ViewModel Property - c#

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.

Related

Q: How should I handle Datatemplates for Models?

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.

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.

Binding to Window.DataContext.ViewModelCommand inside a ItemsControl

I Have some window like:
<Window>
<ItemsControl ItemsSource="{Binding MyItemList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding ViewModelCommand}">My Button</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
This window has the DataContext property defined with an instance of:
public class MyVM
{
public IEnumerable<FooType> MyItemList { get; set; }
public ICommand ViewModelCommand { get; set; }
}
The problem is that the Button.Command binding is not working. I'm guessing that the problem is because my button is inside ItemsControl, so the binding is looking for ViewModelCommand inside of the object of FooType.
So how can I make this bind properly?
The DataContext inside that DataTemplate will be the FooType item; that's what the ItemTemplate is there for: To display each item.

Apply multiple Datatemplates to listbox Itemtemplate based on data

I'm sure this is simple but i just cant seem to figure out how to do it. Basicly i have a list of customers that comes from a azure mobile service database. so far everything works fine but I would like to set the item template for each item in a listbox based on the data. I have 2 templates, one for companies and on for just a person. My question is how to apply each one.
Templates
<DataTemplate x:Key="CompanyItemTemplate">
-------
</DataTemplate>
<DataTemplate x:Key="CustomerItemTemplate">
-------
</DataTemplate>
Code
CustomerListItems.ItemsSource = customeritems.OrderBy(customer => customer.CustomerName);
foreach (Customers customer in customeritems)
{
if (customer.Company != "")
{
CustomerListItems.ItemTemplate = CompanyItemTemplate;
}
else
{
CustomerListItems.ItemTemplate = CustomerItemTemplate;
}
}
You can use a DataTemplateSelector to dynamically select the DataTemplate based on the data bound to your properties:
Sample code:
public class ImgStringTemplateSelector : DataTemplateSelector
{
public DataTemplate ImageTemplate { get; set; }
public DataTemplate StringTemplate { get; set; }
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
String path = (string)item;
String ext = System.IO.Path.GetExtension(path);
if (System.IO.File.Exists(path) && ext == ".jpg")
return ImageTemplate;
return StringTemplate;
}
}
<Window.Resources>
<local:RelativeToAbsolutePathConverter x:Key="relToAbsPathConverter" />
<DataTemplate x:Key="stringTemplate">
<TextBlock Text="{Binding}"/>
</DataTemplate>
<DataTemplate x:Key="imageTemplate">
<Image Source="{Binding Converter={StaticResource relToAbsPathConverter}}"
Stretch="UniformToFill" Width="200"/>
</DataTemplate>
<local:ImgStringTemplateSelector
ImageTemplate="{StaticResource imageTemplate}"
StringTemplate="{StaticResource stringTemplate}"
x:Key="imgStringTemplateSelector" />
</Window.Resources>
<Grid>
<ListView ScrollViewer.CanContentScroll="False"
ItemsSource="{Binding ElementName=This, Path=PathCollection}"
ItemTemplateSelector="{StaticResource imgStringTemplateSelector}">
</ListView>
</Grid>
</Window>

Categories

Resources