Are data templates obsolete in MVVM? - wpf

I have created the following model (the code is simplified to illustrate the situation):
public abstract class Account
{
public string Name { get; set; }
}
public class Person : Account
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Company : Account
{
public string Owner { get; set; }
}
Next I have created a view model:
public class ViewModel
{
public Account Model { ... }
public string Name { ... }
public string FirstName { ... }
public string LastName { ... }
public string Owner { ... }
...
}
And finally, the view:
<UserControl>
<UserControl.Resources>
<!-- Person data template -->
<DataTemplate x:Key="personTemplate" DataType="{x:Type model:Person}">
<Grid DataContext="{Binding ElementName=rootLayout, Path=DataContext}">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=FirstName}" />
<TextBlock Text="{Binding Path=LastName}" />
</Grid>
</DataTemplate>
<!-- Company data template -->
<DataTemplate x:Key="companyTemplate" DataType="{x:Type model:Company}">
<Grid DataContext="{Binding ElementName=rootLayout, Path=DataContext}">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=Owner}" />
</Grid>
</DataTemplate>
<!-- Data template selector for different account types -->
<local:AccountTemplateSelector x:Key="templateSelector"
PersonTemplate="{StaticResource personTemplate}"
CompanyTemplate="{StaticResource companyTemplate}" />
</UserControl.Resources>
<StackPanel Name="rootLayout" DataContext="{Binding Path=viewModel}">
<ContentControl Content="{Binding Path=Model}"
ContentTemplateSelector="{StaticResource templateSelector}"/>
<Button Content="Save" />
<Button Content="Close" />
</StackPanel>
</UserControl>
So, when the model that is loaded is of type Person the personTemplate is shown; vice versa, when the model is Company the companyTemplate is shown.
My questions are:
Does this approach make sense at all? Would it be smarter to delete the Model
property in the ViewModel class and to introduce an enum or just a simple bool
which would show person if true, or company if `false?
While defining the data templates, I specified DataTypes to Person and Company
types (it was natural to me to do it this way). Do I need it at all because in the very
next line I am setting a new data context to be the one from the UserControl?
Should the DataTypes of the data templates be different view models, something like
PersonViewModel and CompanyViewModel? Does it make sense to create them?
How can I, and can I at all, make data template inherit the data context from the
ContentControl automatically?
I know that all this is a matter of a personal choice in the end, but since I am learning MVVM (I am using MVVM Light), I am wondering which approach would be the most recommendable one? I still do not fully understand when should the classes from models be used as data types for data templates and when should view models be used for that purpose. Should the assembly that represents the model even be referenced in the view assembly (assuming that view, model and view model all reside in separate assemblies)?
Thanks for all the clarifications!
UPDATE:
This update should explain the problem of having classes of the model as DataTypes in the data templates when the property of the model class is not directly binded to just one control in the view.
There is an enum and a new property in the Person, so now it looks like this:
public class Person : Account
{
public enum GenderType { Female, Male, NotSpecified }
public string FirstName { get; set; }
public string LastName { get; set; }
public GenderType Gender {get; set; }
}
And in the view, the data template of the person is changed as well of course:
<!-- Person data template -->
<DataTemplate x:Key="personTemplate" DataType="{x:Type model:Person}">
<Grid DataContext="{Binding ElementName=rootLayout, Path=DataContext}">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=FirstName}" />
<TextBlock Text="{Binding Path=LastName}" />
<RadioButton Name="Female" />
<RadioButton Name="Male" />
<RadioButton Name="NotSpecified" />
</Grid>
</DataTemplate>
If the Content of the ContentControl is set to Model property of the ViewModel, how would I resolve the gender/radio buttons situation; because, now they do not match in the way one control/one property?

I would change it to this:
<UserControl>
<UserControl.Resources>
<!-- Person data template -->
<DataTemplate DataType="{x:Type model:Person}">
<Grid>
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=FirstName}" />
<TextBlock Text="{Binding Path=LastName}" />
<RadioButton Name="Female" IsChecked="{Binding Gender , Converter={StaticResource enumBooleanConverter}, ConverterParameter=Female}" />
<RadioButton Name="Male" IsChecked="{Binding Gender , Converter={StaticResource enumBooleanConverter}, ConverterParameter=Male}" />
<RadioButton Name="NotSpecified" IsChecked="{Binding Gender , Converter={StaticResource enumBooleanConverter}, ConverterParameter=NotSpecified }" />
</Grid>
</DataTemplate>
<!-- Company data template -->
<DataTemplate DataType="{x:Type model:Company}">
<Grid>
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=Owner}" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<StackPanel DataContext="{Binding viewModel}">
<ContentControl Content="{Binding Model}" />
<Button Content="Save" />
<Button Content="Save" />
<Button Content="Close" />
</StackPanel>
</UserControl>
like this you define implicit styles for your classes and you don't have to use a templateselector. Also then you don't need all your string properties in the ViewModel class:
public class ViewModel
{
public Account Model { ... }
...
}
Disclaimer, the binding in the RadioButtons uses a Converter from here.

Absolutely positively not (are they obsolete).
Yes, it makes perfect sense, however your bindings, not so much. Those issues can be handled in a number of different ways. For instance, the Account could have a Parent property which exposes the ViewModel it is contained in (I didn't say it was the best approach).
This is an issue with your ViewModel design, where you must bind not against the Account but the ViewModel. It might be possible to change the design so that you don't need to do this; hard to tell with the snapshot you have provided.
I don't think that will help you at this point. I'd see if there was a way to keep your ViewModel related UI out of the DataTemplate.
Here's one good solution: Create a DataTemplateSelector that chooses the template based on the Account property. That way, you can bind the ItemsSource directly to the DataContext, and it will be available within the DataTemplate.

Related

Accessing INotifyDataErrorInfo validation errors from XAML error template using composition

My view model base class was originally implementing INotifyDataErrorInfo, and everything worked flawlessly, but I am now exploring how I might do validation using composition rather than inheritance, so that my base view model class doesn't have to do anything more than INotifyPropertyChanged. I'm also looking for a reusable solution so I don't have to implement INotifyDataErrorInfo on all my view models.
I created a concrete implementation of INotifyDataErrorInfo that I can include in my view models that need validation (only relevant code included):
public class NotifyDataErrorInfo : INotifyDataErrorInfo
{
public readonly Dictionary<string, string> ValidationErrorsByPropertyName = new Dictionary<string, string>();
public IEnumerable GetErrors(string propertyName)
{
...
}
public bool HasErrors { get; }
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}
When MyViewModel has validation errors, it gets/sets them through an instance of the NotifyDataErrorInfo object. (In my original version, ViewModel implemented INotifyDataErrorInfo. This is no longer the case as I explore achieving the same results via composition.)
public class MyViewModel : ViewModel
{
public NotifyDataErrorInfo NotifyDataErrorInfo { get; } = new NotifyDataErrorInfo();
}
Here is a text box that is reporting validation errors on the MaxDaysText property setter, and sets the validation error template.
<TextBox
Text="{Binding MaxDaysText, UpdateSourceTrigger=PropertyChanged}"
Validation.ErrorTemplate="{StaticResource TextBoxValidationErrorTemplate}" />
I now need to update my validation error template to access the errors in the NotifyDataErrorInfo property but I am not sure how to do this.
<ControlTemplate x:Key="TextBoxValidationErrorTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Border
Grid.Row="0"
HorizontalAlignment="Left"
BorderBrush="{StaticResource ErrorMessageBorderBrush}"
BorderThickness="1">
<AdornedElementPlaceholder x:Name="_adornedElementPlaceholder" />
</Border>
<ItemsControl
Grid.Row="1"
ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox
Style="{StaticResource ErrorMessageStyle}"
Text="{Binding Path=ErrorContent, Mode=OneWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</ControlTemplate>
I have tried changing all the bindings to look for NotifyDataErrorInfo, but with no luck.
What change do I need to make to the template to access the validation errors on MyViewModel's NotifyDataErrorInfo property?
EDIT:
It appears that in the composition approach, ErrorsChanged is always null, and the view is never being notified. I guess when the view model itself implements INotifyDataErrorInfo, the framework assigns the delegate using ErrorsChangedEventManager. But now I'm leaving that out of the loop.
So it appears that composition will not work for this approach. Is this assessment correct?
The property is called ‘NotifyDataErrorInfo’, you need to bind ItemsSource to this property
<ItemsControl
Grid.Row="1"
ItemsSource="{Binding NotifyDataErrorInfo}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox
Style="{StaticResource ErrorMessageStyle}"
Text="{Binding Path=ErrorContent, Mode=OneWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If the controltemplate doesn’t retrieve the datacontext, then you need to add it
<ControlTemplate DataContext={Binding DataContext,
RelativeSource={RelativeSource AncestorType={x:Type views:YourView}}} x:Key="TextBoxValidationErrorTemplate">

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.

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>

WPF Treeview - Binding to a collection with different depths, and styling differently

Apologies for the long post -- Read a few threads about this, but still find it hard to implement. Basically, I have a collection of objects, defined as:
public class LibData
{
public string Name { get; set; }
ObservableCollection<LibObject> _list;
public ObservableCollection<LibObject> List { get { return _list; } }
public LibData(string name, LibDataType type)
{
this.Name = name;
_list = new ObservableCollection<LibObject>();
}
}
and the object:
public class LibObject
{
public string Name { get; set; }
public LibObject(string name)
{
this.Name = name;
}
}
My main problem is in the XAML, and styling this TreeView. I need to have a specific style for a "root" item, and a specific style for a "leaf". Thing is, that one item in the bound list is "Root->Leaf", and another is "Root->Child->Leaf".
I tried this:
<TreeView x:Name="myTree" ItemsSource="{x:Static local:myDataList}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=List}" >
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<CheckBox IsChecked="True" Content="HeaderCheckbox"/>
</StackPanel>
</Grid>
<HierarchicalDataTemplate.ItemTemplate >
<DataTemplate>
<Grid>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="True" Content="LeafCheckbox" />
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</Grid>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
This obviously works fine for the "Root->Leaf" item, but not for the "Root-Child-Leaf".
The XAML implementation seems to be more of a "hard coded" solution, where I know that the item layout is always "Root->Leaf" - how do I make this dynamic?
Again, I have seen solutions to different levels (including using converters), but the problem i'm having is that I need specific styles to root and leaf and nothing for levels in between.
I'm wondering if I'm looking at this completely wrong ...
Easy way to do this. Pull the DataTemplates out of the TreeView and put them in the resources section. Specify the DataType property for each DataTemplate, but do not include a key. The ItemTemplateSelector (a property of type DataTemplateSelector) on the TreeView will do the magic of using whichever DataTemplate suits the correct item type.
Create a HierarchicalDataTemplate for types Root and Child, and a DataTemplate for type Leaf.
Something like this:
<Window.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type local:Leaf}">
<Grid>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="True" Content="LeafCheckbox" />
<TextBlock Text="{Binding Path=SomeValue}"/>
</StackPanel>
</Grid>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Child}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Child " />
<TextBlock Text="{Binding Path=SomeValue}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Root}"
ItemsSource="{Binding Children}">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Root " />
<TextBlock Text="{Binding Path=SomeValue}" />
</StackPanel>
</Grid>
</HierarchicalDataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<TreeView x:Name="myTree" ItemsSource="{Binding}" />
</Grid>

How to bind to a property of a property in WPF

I have a listview who's itemssource is a ObservableCollection of MyModel. I am trying to figure how to bind a textbox text's property to the Name property of the model's Owner property
public class Person
{
public string Name { get; set; }
public string Address { get; set; }
//...
}
public class MyModel
{
public string Title { get; set; }
public Person Owner { get; set; }
//...
}
I tried:
<TextBlock Text="{Binding Owner.Name}" />
but that leaves the textblock blank. Whats the proper syntax?
The binding looks fine. I assume that you put the TextBlock into a DataTemplate and attached this to the ListView. If yes, that should work.
To find the error, replace the Binding through a literal to see if you have some rows (The literal must be shown in every line). If not, check the ItemsSource. If yes, check that you have really a Person-object attached to your MyModel-instances and that the Name-property is not null or empty. Check also the output window of VS. There you will see binding-errormessages.
If you have no DataTemplate, here an example:
<ListView ItemsSource="[Your ItemsSource]">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Owner.Name}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Check that the DataContext is set properly and implement INotifyPropertyChanged (raise the event it defines when the value of the property changes.).
Try to use Data Source Wizard form VS 2010
I just add these classes and click, clik, clik
<TextBox Grid.Column="1" Grid.Row="0" Height="23" HorizontalAlignment="Left" Margin="3" Name="nameTextBox" Text="{Binding Path=Owner.Name, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
<Slider Margin="81,161.66,66,0" Name="Height" Minimum="0" Maximum="10" Width="400"></Slider>
<Slider Margin="81,1.66,66,0" Name="Width" Minimum="0" Maximum="10" Width="400"/>
<Ellipse Height="{Binding ElementName=Height}" Width="{Binding ElementName=Width}" Fill="Blue" ></Ellipse>

Resources