Customizing Your WPF/Silverlight UI using Value Converter and/or Partial Class Properties
When writing data applications, one of the things we need to take care is the difference between the data stored in the database and the data you want to show to the user in presentation layer. We need to make sure that the data being displayed in the UI is friendly and makes sense to the users. Also in some situations we want the UI to be dynamic according to the real values of the data being loaded.
Here is an example,
Imagine we have a cloud which is hosting thousands of virtual machines there. We want to create an application which lists all machines with related information, and provide a way for people who want to use any of the machines, to reserve them.
The following is the UI we want to build:
Some specific requirements:
- 1. We want to use icons to represent the State of the machine, which is more intuitive than the string data stored in the database.
- 2. We will provide a button for users to reserve only those machines which can be reserved. For machines that cannot be reserved, we will just hide the button.
Next I am going to show you how to build this DataGrid using value converters and/or partial class properties.
Data Model
Let's say I have the following Machine class to represent information for a machine. (We will use C# in this post. The complete example project will be available in both VB and C# in the MSDN Code Gallery. Link is provided at the end of the blog. )
public enum MachineState
{
Running, Saved, Stopped
}
public partial class Machine
{
public string MachineName
{
get;
set;
}
public string OperatingSystem
{
get;
set;
}
public MachineState State
{
get;
set;
}
public bool CanBeReserved
{
get;
set;
}
public Machine(string name, string os, MachineState state, bool canbeReserved)
{
this.MachineName = name;
this.OperatingSystem = os;
this.State = state;
this.CanBeReserved = canbeReserved;
}
}
If I directly drag & drop this object from Data Sources Window to the WPF Designer, it will generate the DataGrid in the following format.
Good start, but not exactly what we want. Let's do some customizations.
Customize Using Value Converters
IValueConverter allows you to implement a class to specify how data should be converted when bound to a property of a control as well as a way to convert it back.
Customize [Reserve Machine] column
Step1. Replace the [Can Be Reserved] column with button template.
<DataGridTemplateColumn Header="Reserve Machine">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Reserve machine" Margin="3" FontSize="9" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Next, we want to control the visibility of these buttons according to the CanBeReserved Boolean property.
Step2. Implement an IValueConverter which converts a Boolean value to the Visibility object. In this example, true means visible, otherwise collapsed.
public class BooleanToVisibilityValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is Boolean)
{
return ((Boolean)value) == true ? Visibility.Visible : Visibility.Collapsed;
}
else
{
throw new ArgumentException("Value should be Boolean type.");
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Step3. Add this Value Converter as a static resource in the MainPage.xaml.
<Window.Resources>
<my:BooleanToVisibilityValueConverter x:Key="boolToVisibility" />
</Window.Resources>
Step4. Bind the Button's Visibility property to the CanBeReserved property and use the value converter we just implemented.
<DataGridTemplateColumn Header="Reserve Machine">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Reserve machine" Margin="3" FontSize="9"
Visibility ="{Binding CanBeReserved, Converter={StaticResource boolToVisibility}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Now hit F5 to run the app. You will find the buttons are shown according to the Boolean value of the CanBeReserved property.
Customize [State] column
Step1. Implement a value converter which accepts a MachineState object and returns the corresponding image path. Image paths are relative paths for images in the solution tree.
public class MachineStateToImagePathConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is MachineState)
{
MachineState state = (MachineState)value;
if (state == MachineState.Running)
{
return "Images\\Running.png";
}
else if (state == MachineState.Saved)
{
return "Images\\Saved.png";
}
else if (state == MachineState.Stopped)
{
return "Images\\Stopped.png";
}
else
{
throw new NotSupportedException("Unknown machine state: " + state);
}
}
else
{
throw new ArgumentException("Value should be MachineState type.");
}
}
Step2. Add this Value Converter as a static resources in the MainPage.xaml
<Window.Resources>
<my:BooleanToVisibilityValueConverter x:Key="boolToVisibility" />
<my:MachineStateToImagePathConverter x:Key="stateConverter" />
</Window.Resources>
Step3. Change the state column to use Image as template. And set the image source to bind to the State property using the value converter we just implemented.
<DataGridTemplateColumn x:Name="stateColumn2" Header="State" Width="SizeToHeader">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source ="{Binding Path=State, Converter={StaticResource stateConverter}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Now hit F5 to run the app. You will see the machine state was changed to more intuitive icons.
Customize Using Partial Class Properties
Another alternative to value converter is to simply add new properties and bind controls to those new properties. If you are using DataSet, EDM or web services to represent your data model, most likely you will have your data objects auto generated by Visual Studio. For example, if I have a table Machines in the database and add it to my project as EDM objects. I will have the following code generated by Visual Studio in the EDMX code behind file to represent the table.
public partial class Machine
{
//....
}
Luckily, all these code generators will mark the generated classes as partial. So I can add new properties elsewhere in the project for this class. Below are the steps to use partial class properties to achieve the same result.
Step1. Implement a partial class for Machine and add the following two properties. You can see that the implementation is very similar to the value converters.
public partial class Machine
{
public string StateImagePath
{
get
{
if (this.State == MachineState.Running)
{
return "Images\\Running.png";
}
else if (this.State == MachineState.Saved)
{
return "Images\\Saved.png";
}
else if (this.State == MachineState.Stopped)
{
return "Images\\Stopped.png";
}
else
{
throw new NotSupportedException("Unknown machine state: " + this.State);
}
}
}
public Visibility CanBeReservedButtonVisibility
{
get
{
return this.CanBeReserved == true ? Visibility.Visible : Visibility.Collapsed;
}
}
}
Step2. Change the State column and the CanBeReserved Column to the following. Note that this example does not use ValueConverter but bind to the newly added properties directly.
<DataGridTemplateColumn x:Name="imagePathColumn" Header="State" IsReadOnly="True" Width="SizeToHeader">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Path=StateImagePath}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn x:Name="ReservedButtonColumn" Header="Reserved Machine" IsReadOnly="True" Width="SizeToHeader">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Margin="3" FontSize="9" Content="Reserve Machine"
Visibility="{Binding Path=CanBeReservedButtonVisibility}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Now hit F5 to run the app. You will get the same result.
Conclusion
As you can see, using value converters is a very flexible and powerful way to customize your data applications. They are available in both WPF and Silverlight.
You can get complete source code of the example project in this blog at https://code.msdn.microsoft.com/ValueConverters.
There a lot of other examples out there around value converters. E.g. here is an example of using value converter to format data in WPF controls. https://blogs.msdn.com/bethmassi/archive/2008/12/12/formatting-data-in-wpf-controls.aspx
If you need more examples, there is a project on CodePlex site implements a set of generic WPF binding converters that can be used in most any WPF application. https://wpfconverters.codeplex.com/
And BTW, the BooleanToVisibilityConverter described in this blog is already included in the .NET Framework 4.0. J
Enjoy!