.NET Framework Bookmark and Share   
 index > Windows Presentation Foundation (WPF) > MVVM binding best practice
 

MVVM binding best practice

I am trying to make use of the MVVM design pattern so I have a separate data model, a view, and a view model. My data model has an enumerated property (enum DataState)that indicates the state of the data - Error, SevereError, Warning, OK. My view is made using ControlTemplate. A have seperate UI elements for each of the possible state values. Only the UI element corresponding to the state should be visible, the others hidden. What is the best way to do this? I wanted to use triggers to hide/show the applicable UI whenever something changed. Here were my ideas:
1) Have the view model implement public properties for "IsError", "IsSevereError", "IsWarning", and "IsOK" and then bind the UI to the appropriate property. This got too big because my data model actually has a state for each field in the data model representing the validity of the field value.
2) Use bindings with specific type converters - a converter for Error, one for SevereError, etc. But when I tried to call DependencyProperty.Register() with typeof(DataState)I got an invocation exception.

John Miller
JGMiller
It depends on what you want to change. Many system controls change the whole Template property regarding a property. As long INotifyPropertyChanged interface is implemented you can use the property directly on DataTriggers. Implementing bool properties makes very little sence.

If you want just the change Visility of some controls I'd say converters is the way to go.
Bigsby, Lisboa, Portugal - O que for, quando for, é que será o que é... http://bigsby.eu
Bigsby
Hello,

You can do it in severals ways. In your case, the best approach is to have one UIElement with mutliple data templates and set the rigth data template evaluating the property DataState. It can be acomplished using a data trigger.

If you need to have different UIElement for each data state you can define each UIElement as resource and one container element. Then with a data template set the container element's content property using as value one of the UIElement resources.

Another option is to have a big data template with all the ui elements in it and set the property visibility="collapsed" for each one.
The using a data trigger set the property visibility="visible" in the right UIElement according the DataState property.

Good Luck.
HomeroThompson
Hi,

I believe what the MVVM cracks would do in this case is to have one ErrorBase base class, derive from it classes like Error, SevereError, Warning and NoError, and expose the appropriate object, including all the information it should display, through a property of type ErrorBase in the ViewModel. Then you would have different DataTemplates for the different classes within one ContentControl that is bound to this property, and the DataTemplates would show the appropriate UI according to the type of the current value of the property.

Reading your post again, I would even suggest creating aDictionaryof ErrorBase objects in the ViewModel, using thefield name as key. Then you could bind the error display UI to this collection, using the field name as index, and a NoError object as FallbackValue. Your validation logic would populate the dictionary, creating the appropriate error objects if there is a problem with a given field, and doing nothing else. You would just have to take care to not throw exceptions if the UI tries to access a dictionary entry that doesn't exists.
hbarck

I am going to try the various suggestions and see which best fits my needs. Below is what I initially had that works. It uses bool properties that were added because of the triggers.

[Flags]
public enum DataStateType
{ OK = 0, Error = 1, SevereError = 2, Warning = 4}

public class DataStateObj : INotifyPropertyChanged
{
public DataStateType DataState {...}
public bool IsReadOnly {...}
...
// Right now added bool properties
public bool HasError {...}
public bool HasWarning {...}
}

public class TestObj : INotifyPropertyChanged
{
public DataStateObj OverallState {...}
public string Email {...}
public DataStateObj EmailState {...}
...
}

<style TargetType="{x:Type local:TestControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TestControl}">
...
<Image x:SevereErrorImage ...>
<Image x:ErrorImage ...>
<Image x:WarningImage ...>
<Image x:OKImage ...>
...
<ControlTemplate.Triggers>
<Trigger Property="HasError" Value="true"...>
<Trigger Property="HasError" Value="false"...>
...
</Setter.Value>
</Setter>
</style>

XAML
<window...>
...
<testui:TestControl x:Name="TestControl1"
HasError="{Binding Path=EmailState.HasError}"
HasWarning="{Binding Path=EmailState.HasWarning}"
Content="{Binding Path=Email}"...
...
</window>

XAML - Code Behind
public foo()
{
TestControl1.DataContext = new TestObj();
...
}


Ideally I would like the resource to do most of the work (in case it gets re-skinned) and have code similar to below. The control template would somehow use the bound "State" property and it's properties to manipulate the state related elements. Also I would like to take the bool properties out of the legacy DataStateObj but I don't know how to write the trigger based on a flagged enum since it only performs an equality check (it is possible to have an error and a warning, etc)
<window...>
...
<testui:TestControl x:Name="TestControl1"
State="{Binding Path=EmailState}"
Content="{Binding Path=Email}"
...
...
</window>


Since I am new to WPF I am not sure of the best way to go so I am grateful for the replies and I will try to figure out how to implement the suggestions.


John Miller
JGMiller

Hi John,

The following are two ways to get what you want. Firstly, the business class is defined as follows:

public class TestObj : INotifyPropertyChanged
{
private DataStateType _state;
public DataStateType EmailState
{
get { return _state; }
set {
if (value != _state)
{
_state = value;
OnPropertyChanged("EmailState");
}
}
}
private void OnPropertyChanged(string propname)
{
if (_propertyChanged != null)
{
_propertyChanged(this, new PropertyChangedEventArgs(propname));
}
}

private event PropertyChangedEventHandler _propertyChanged;
#region INotifyPropertyChanged Members

event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add { _propertyChanged += value; }
remove { _propertyChanged -= value; }
}

#endregion
}


Sample 1: Define a DataTemplate for eachvalueofDataStateType.UseaDataTemplateSelectorto select a properDataTemplateaccording to the value of theEmailState propertyof thebusiness object.

<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Wndow.Resources>
<local:TestObj x:Key="testobj" EmailState="OK"/>
<DataTemplate x:Key="OK" DataType="{x:Type local:DataStateType}">
<Image>
<Image.Source>
<BitmapImage UriSource="Images/Bitmap1.bmp"/>
</Image.Source>
</Image>
</DataTemplate>
<DataTemplate x:Key="Error" DataType="{x:Type local:DataStateType}">
<Image>
<Image.Source>
<BitmapImage UriSource="Images/Bitmap2.bmp"/>
</Image.Source>
</Image>
</DataTemplate>
<DataTemplate x:Key="SevereError" DataType="{x:Type local:DataStateType}">
<Image>
<Image.Source>
<BitmapImage UriSource="Images/Bitmap3.bmp"/>
</Image.Source>
</Image>
</DataTemplate>
<DataTemplate x:Key="Warning" DataType="{x:Type local:DataStateType}">
<Image>
<Image.Source>
<BitmapImage UriSource="Images/Bitmap4.bmp"/>
</Image.Source>
</Image>
</DataTemplate>
<local:DataStateTemplateSelector x:Key="templateSelector"/>
</Window.Resources>
<StackPanel >
<ContentControl DataContext="{StaticResource testobj}" Content="{Binding Path=EmailState}" Width="200" Height="40"
ContentTemplate="{StaticResource GeneralTemplate}">
</ContentControl>
</StackPanel>
</Window>

// code behind
public class DataStateTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{

if (item != null && item is DataStateType)
{
Window win = Application.Current.MainWindow;
switch((DataStateType)item)
{
case DataStateType.OK:
{
return win.FindResource("OK") as DataTemplate;
}
case DataStateType.Error:
{ return win.FindResource("Error") as DataTemplate; }
case DataStateType.SevereError:
{ return win.FindResource("SevereError") as DataTemplate; }
case DataStateType.Warning:
{ return win.FindResource("Warning") as DataTemplate; }
}
}
return null;
}
}

Sample 2: Define only one DataTemplate and use data triggers to show the proper image according to the value of the EmailState property.

<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Wndow.Resources>
<local:TestObj x:Key="testobj" EmailState="OK"/>
<DataTemplate x:Key="GeneralTemplate" DataType="{x:Type local:DataStateType}">
<Grid Name="grid">
<Image Name="OK" Visibility="Hidden">
<Image.Source>
<BitmapImage UriSource="Images/Bitmap1.bmp"/>
</Image.Source>
</Image>
<Image Name="Error" Visibility="Hidden" >
<Image.Source>
<BitmapImage UriSource="Images/Bitmap2.bmp"/>
</Image.Source>
</Image>
<Image Name="SevereError" Visibility="Hidden">
<Image.Source>
<BitmapImage UriSource="Images/Bitmap3.bmp"/>
</Image.Source>
</Image>
<Image Name="Warning" Visibility="Hidden">
<Image.Source>
<BitmapImage UriSource="Images/Bitmap4.bmp"/>
</Image.Source>
</Image>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Content}" Value="{x:Static local:DataStateType.OK}">
<Setter Property="Visibility" TargetName="OK" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Content}" Value="{x:Static local:DataStateType.Error}">
<Setter Property="Visibility" TargetName="Error" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Content}" Value="{x:Static local:DataStateType.SevereError}">
<Setter Property="Visibility" TargetName="SevereError" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Content}" Value="{x:Static local:DataStateType.Warning}">
<Setter Property="Visibility" TargetName="Warning" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<StackPanel >
<ContentControl DataContext="{StaticResource testobj}" Content="{Binding Path=EmailState}" Width="200" Height="40"
ContentTemplate="{StaticResource GeneralTemplate}">
</ContentControl>
</StackPanel>
</Window>

Hope this helps.
If you have anything unclear, please feel free to let me know.

Sincerely,
Linda Liu


Please remember to mark the replies as answers if they help and unmark them if they provide no help.
Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
Linda Liu

You can use google to search for other answers

Custom Search

More Threads

• How to get listViewItem checkBox control?
• How can a .xaml file read infrormation from Properties/Resources.resx
• WPF Window1 and NotifyIcon
• timer control in Wpf
• Memory leaks with BitmapImage
• DefaultStyleKeyProperty.OverrideMetadata
• Can't drag and drop a control onto a FlowDocument in another window
• Visual Studio 2005 is silent after a publication of the project
• Deprecated Effects in WPF
• How do I access intermediate values of an animation?