How to choose View dynamically based on current DataContext view model - wpf

I have a Page which will receive a different DataContext (View Model), dynamically.
I can't figure out how to use DataTemplate in a switch/case fashion, to render the appropriate view based on the current context.
I would imagine that I will have multiple DataTemplates like this:
<DataTemplate DataType="{x:Type LocalViewModels:ABC}">
<LocalViews:ABC/>
</DataTemplate>
but can't figure out in what container to put them. Only one of them will be rendered at a time, so ListBox makes no sense to me.

Given the following XAML of a Window
<Window.Resources>
<DataTemplate DataType="{x:Type local:ABC}">
<Border BorderThickness="2" BorderBrush="Red">
<TextBlock Text="{Binding Text}"/>
</Border>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding}"/>
</StackPanel>
you can simply assign an instance of ABC to the Window's DataContext to create the templated view.
class ABC
{
public string Text { get; set; }
}
...
public MainWindow()
{
InitializeComponent();
DataContext = new ABC { Text = "Hello, World." };
}
All details are here: Data Templating Overview.

Related

Associating a ViewModel to a View

I have a main View and corresponding ViewModel. Because the main View is too complicated so I split it into many small Views; and each small View also has its own ViewModel.
My question is that how to "associating a sub ViewModel to a sub View" in the main View?
I am doing the following way, not sure if it is right or I have to use DataTemplate?
<StackPanel>
<local:SmallView-A DataContext="{x:Type local:SmallViewModel-A}" />
</StackPanel>
<StackPanel>
<local:SmallView-B DataContext="{x:Type local:SmallViewModel-B}" />
</StackPanel>
<StackPanel>
<local:SmallView-C DataContext="{x:Type local:SmallViewModel-C}" />
</StackPanel>
I would saif that the right anwser is the following :-)... (not tested!)
in a generic.xaml, associated views and viewmodels
In your MainViewModel create properties for each sub viewmodel
In your MainView, bind content presenter to there properties
generic
<DataTemplate DataType="{x:Type ViewModels:SubViewModel1}">
<Views:SubView1 />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:SubViewModel2}">
<Views:SubView2 />
</DataTemplate>
MainViewModel
public SubViewModel1 Sub1 { get; set; }
public SubViewModel2 Sub2 { get; set; }
MainView
<Grid>
## your layout here
<ContentPresenter Content="{Binding Sub1}" />
<ContentPresenter Content="{Binding Sub2}" />
##could be
<TabControl ItemSource="{Binding MyViewModelCollection}" />
</Grid>
It turns out to use the following code likely.
<ContentControl Content="{Binding MyViewInstance}">
<ContentControl.Resources>
<DataTemplate DataType="x:Type vm:MyViewModel">
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
In your 'Views' folder(Create one if you have not got one already) create a new user control called `SmallViewA'.
In your main XAML:
xmlns:views = "clr-namespace:[Your app name].Views"
<views:SmallViewA x:Name = "vSmallViewA" Loaded = "SmallViewALoaded"/>
private void SmallViewALoaded(object sender, RoutedEventArgs e)
{
vSmallViewA.DataContext = YoursmallAViewModelshouldgohere;
// Initialise
}
Add your SmallViewA control the the SmallViewA control in the Views folder. From there you can bind your properties from your SmallViewA Viewmodel.
Follow the same procedure above for SmallViewB and SmallViewC
Note: be sure to implement the INotifyPropertyChanged to your ViewModel.

C# WPF What's wrong with this template?

I'm trying to create a new window (child of MainWindow) that will display different numbers of rows with data each time. I used the following template that I copied from a wpf template tutorial. I've checked that the data are right and I can project them right with a MessageBox.Show window but I can't make them appear as bound properties. Here is the xaml...
<ListView x:Name="listUnits" x:FieldModifier="public" HorizontalAlignment="Left" Height="Auto" Margin="5,5,5,5" VerticalAlignment="Top" Width="Auto">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Id:"/>
<TextBlock Text="{Binding pu_Id}" Width="Auto"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding pu_unitName}" Width="Auto"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
So although pu_unitName contains the data, they're are displayed in the new window.
The binding is done in the constructor:
public partial class ChooseUnit : Window
{
public ChooseUnit(List<XML_Handler.PrUnits> thisList)
{
InitializeComponent();
listUnits.ItemsSource = thisList;
}
}
The PrUnits is a class which contains variables including pu_id and pu_unitName. So what's wrong with this code?
Since you can only bind to public properties, you need to make sure that pu_Id and pu_UnitName are properties (and not fields) of the PrUnits class. Then your bindings will work.
Also note that this is not a binding:
listUnits.ItemsSource = thisList;
In general, you would set the DataContext of the parent window to an instance of a view model class and bind the ItemsSource property of the ListView to a property that returns the List<XML_Handler.PrUnits>:
<ListView x:Name="listUnits" ItemsSource="{Binding Units}">
...
In your .cs:
public partial class ChooseUnit : Window
{
public ChooseUnit(List<XML_Handler.PrUnits> thisList)
{
InitializeComponent();
this.DataContext = thisList;
}
}
What this does it assigns the data context of your window to the list of itemsa you are trying to pass. At this point every element in that window will have access to the passed in list.
And then in your XAML do this:
<ListView x:Name="listUnits" ItemsSource="{Binding .}" x:FieldModifier="public" HorizontalAlignment="Left" Height="Auto" Margin="5,5,5,5" VerticalAlignment="Top" Width="Auto">
This allows us to use binding as the entire window has access to the list. Hence ItemsSource="{Binding .}".
EDIT
Reading more into your question and thanks to Ash your class needs to expose Properties with INotifyPropertyChanged interface implemented.
This interface is expected by the WPF framework so it can subscribe to the changed events and update the UI.

It is possible to bind collection of different viewmodels in some viewcollection (c# XAML WPF)?

XAML:
<ItemsControl ItemsSource="{Binding Messages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Views:Message110FirstView DataContext="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
VIEWMODEL:
public ObservableCollection<ViewModelBase> Messages
{
get { return GetValue<ObservableCollection<ViewModelBase>>(MessagesProperty); }
set { SetValue(MessagesProperty, value); }
}
public static readonly PropertyData MessagesProperty = RegisterProperty("Messages", typeof(ObservableCollection<ViewModelBase>), null);
My question relates to this part of xaml:
<Views:Message110FirstView DataContext="{Binding}"/>
So, how to make different views in this place.
Thank you.
If I understand you correctly, then you want change view based in viewmodel.
It is appropriate to use DataTemplates if you want to dynamically switch Views depending on the ViewModel:
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModelA}">
<localControls:ViewAUserControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModelB}">
<localControls:ViewBUserControl/>
</DataTemplate>
<Window.Resources>
<ContentPresenter Content="{Binding CurrentView}"/>
</Window>
If Window.DataContext is an instance of ViewModelA, then ViewA will be displayed and Window.DataContext is an instance of ViewModelB, then ViewB will be displayed.
The best example I've ever seen and read it is made by Rachel Lim. See the example.

How to correctly bind to a dependency property of a usercontrol in a MVVM framework

I have been unable to find a clean, simple, example of how to correctly implement a usercontrol with WPF that has a DependencyProperty within the MVVM framework. My code below fails whenever I assign the usercontrol a DataContext.
I am trying to:
Set the DependencyProperty from the calling ItemsControl , and
Make the value of that DependencyProperty available to the ViewModel of the called usercontrol.
I still have a lot to learn and sincerely appreciate any help.
This is the ItemsControl in the topmost usercontrol that is making the call to the InkStringView usercontrol with the DependencyProperty TextInControl (example from another question).
<ItemsControl ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the InkStringView usercontrol with the DependencyProperty.
XAML:
<UserControl x:Class="Nova5.UI.Views.Ink.InkStringView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Name="mainInkStringView"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding TextInControl, ElementName=mainInkStringView}" />
<TextBlock Grid.Row="1" Text="I am row 1" />
</Grid>
</UserControl>
Code-Behind file:
namespace Nova5.UI.Views.Ink
{
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
this.DataContext = new InkStringViewModel(); <--THIS PREVENTS CORRECT BINDING, WHAT
} --ELSE TO DO?????
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
}
That is one of the many reasons you should never set the DataContext directly from the UserControl itself.
When you do so, you can no longer use any other DataContext with it because the UserControl's DataContext is hardcoded to an instance that only the UserControl has access to, which kind of defeats one of WPF's biggest advantages of having separate UI and data layers.
There are two main ways of using UserControls in WPF
A standalone UserControl that can be used anywhere without a specific DataContext being required.
This type of UserControl normally exposes DependencyProperties for any values it needs, and would be used like this:
<v:InkStringView TextInControl="{Binding SomeValue}" />
Typical examples I can think of would be anything generic such as a Calendar control or Popup control.
A UserControl that is meant to be used with a specific Model or ViewModel only.
These UserControls are far more common for me, and is probably what you are looking for in your case. An example of how I would use such a UserControl would be this:
<v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" />
Or more frequently, it would be used with an implicit DataTemplate. An implicit DataTemplate is a DataTemplate with a DataType and no Key, and WPF will automatically use this template anytime it wants to render an object of the specified type.
<Window.Resources>
<DataTemplate DataType="{x:Type m:InkStringViewModel}">
<v:InkStringView />
</DataTemplate>
<Window.Resources>
<!-- Binding to a single ViewModel -->
<ContentPresenter Content="{Binding MyInkStringViewModelProperty}" />
<!-- Binding to a collection of ViewModels -->
<ItemsControl ItemsSource="{Binding MyCollectionOfInkStringViewModels}" />
No ContentPresenter.ItemTemplate or ItemsControl.ItemTemplate is needed when using this method.
Don't mix these two methods up, it doesn't go well :)
But anyways, to explain your specific problem in a bit more detail
When you create your UserControl like this
<v:InkStringView TextInControl="{Binding text}" />
you are basically saying
var vw = new InkStringView()
vw.TextInControl = vw.DataContext.text;
vw.DataContext is not specified anywhere in the XAML, so it gets inherited from the parent item, which results in
vw.DataContext = Strings[x];
so your binding that sets TextInControl = vw.DataContext.text is valid and resolves just fine at runtime.
However when you run this in your UserControl constructor
this.DataContext = new InkStringViewModel();
the DataContext is set to a value, so no longer gets automatically inherited from the parent.
So now the code that gets run looks like this:
var vw = new InkStringView()
vw.DataContext = new InkStringViewModel();
vw.TextInControl = vw.DataContext.text;
and naturally, InkStringViewModel does not have a property called text, so the binding fails at runtime.
You're almost there. The problem is that you're creating a ViewModel for your UserControl. This is a smell.
UserControls should look and behave just like any other control, as viewed from the outside. You correctly have exposed properties on the control, and are binding inner controls to these properties. That's all correct.
Where you fail is trying to create a ViewModel for everything. So ditch that stupid InkStringViewModel and let whoever is using the control to bind their view model to it.
If you are tempted to ask "what about the logic in the view model? If I get rid of it I'll have to put code in the codebehind!" I answer, "is it business logic? That shouldn't be embedded in your UserControl anyhow. And MVVM != no codebehind. Use codebehind for your UI logic. It's where it belongs."
Seems like you are mixing the model of the parent view with the model of the UC.
Here is a sample that matches your code:
The MainViewModel:
using System.Collections.Generic;
namespace UCItemsControl
{
public class MyString
{
public string text { get; set; }
}
public class MainViewModel
{
public ObservableCollection<MyString> Strings { get; set; }
public MainViewModel()
{
Strings = new ObservableCollection<MyString>
{
new MyString{ text = "First" },
new MyString{ text = "Second" },
new MyString{ text = "Third" }
};
}
}
}
The MainWindow that uses it:
<Window x:Class="UCItemsControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:UCItemsControl"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<v:MainViewModel></v:MainViewModel>
</Window.DataContext>
<Grid>
<ItemsControl
ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Your UC (no set of DataContext):
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
}
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
(Your XAML is OK)
With that I can obtain what I guess is the expected result, a list of values:
First
I am row 1
Second
I am row 1
Third
I am row 1
You need to do 2 things here (I'm assuming Strings is an ObservableCollection<string>).
1) Remove this.DataContext = new InkStringViewModel(); from the InkStringView constructor. The DataContext will be one element of the Strings ObservableCollection.
2) Change
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
to
<v:InkStringView TextInControl="{Binding }" />
The xaml you have is looking for a "Text" property on the ItemsControl to bind the value TextInControl to. The xaml I put using the DataContext (which happens to be a string) to bind TextInControl to. If Strings is actually an ObservableCollection with a string Property of SomeProperty that you want to bind to then change it to this instead.
<v:InkStringView TextInControl="{Binding SomeProperty}" />

Conditional DataTemplates when binding to a collection

Sample Application:
The sample application that the supplied code belongs to displays a list of Vehicle objects via Binding. The Vehicle class is a top level class that subclasses can derive from e.g. Car and Bike. The sample application at the moment displays the owner's name of the Vehicle.
Sample Model code:
public class Vehicle
{
private string _ownerName;
public string ownerName
{
get { return _ownerName; }
set { _ownerName = value; }
}
}
public class Car : Vehicle
{
public int doors;
}
public class Bike : Vehicle
{
// <insert variables unique to a bike, ( I could not think of any...)>
}
UserControl XAML Code:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="itemTemplate">
<WrapPanel>
<TextBlock Text="{Binding Path=ownerName}"/>
</WrapPanel>
</DataTemplate>
</Grid.Resources>
<ListBox x:Name="list" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5" ItemsSource="{Binding}" ItemTemplate="{StaticResource itemTemplate}" />
</Grid>
UserControl code behind:
public List<Vehicle> vehicleList = new List<Vehicle>();
public CustomControl()
{
InitializeComponent();
createSomeVehicles();
list.DataContext = vehicleList;
}
public void createSomeVehicles()
{
Car newcar = new Car();
newcar.doors = 5;
newcar.ownerName = "mike";
Bike newbike = new Bike();
newbike.ownerName = "dave";
vehicleList.Add(newcar);
vehicleList.Add(newbike);
}
What I want to be able to do:
I would like to be able to display a button in the list object dependant upon the Type of the Vehicle object. E.g. I would like to display a Open Boot button within the list item for Car's; Type Bike does not have a boot and so no button would display within the list item.
Idea's on how to accomplish this:
I have looked into the custom binding of different DataTemplates based upon what type of object it is. E.g. from the code behind I could call:
object.Template = (ControlTemplate)control.Resources["templateForCar"];
The problem here is that I am using a Binding on the whole list and so there is no way to manually bind a DataTemplate to each of the list items, the list binding controls the DataTemplate of it's items.
You can create a DataTemplate for each Bike and Car (and for any CLR type). By specifying the DataTemplate's DataType property, the template will automatically be applied whenever WPF sees that type.
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:Car}">
<WrapPanel>
<TextBlock Text="{Binding Path=ownerName}"/>
<Button Content="Open Boot" ... />
</WrapPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Bike}">
<WrapPanel>
<TextBlock Text="{Binding Path=ownerName}"/>
</WrapPanel>
</DataTemplate>
</Grid.Resources>
<ListBox x:Name="list" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5" ItemsSource="{Binding}" />
</Grid>

Resources