Introduction
One of the things I do not believe one should do is passing WPF/Silverlight enumerations (i.e., Visibility
) directly from the ViewModel
to the View
. All bindings to a ViewModel
that are two values should be defined as a bool
in the ViewModel
even if the View
requires some other value to reduce coupling. One of the reasons is that at times properties in the ViewModel
are used in ways that were not originally intended, and using bool initially the ViewModel
can save making changes in the ViewModel
or the creation of a IValueConverter
to convert from two values to another two values. The preferred way should always be to use a IValueConverter
to convert the value (bool
, or something else) to WPF. Creating a generic IValueConverter
to convert a bool
from the ViewModel
to the enumeration (or some other value) needed by the View
is actually quite easy. This can then be a IValueConverter
and can then be customized in XAML in the View
or can be defined in a resource dictionary. Such a converter can also provide additional features to aid the programmer in finding bugs.
Note
I have pretty much abandoned this since IValueConverter
I think I have a much more elegant solution. Among the issues I have with this solution is that I have to declare this in the XAML bevore using it. I pretty much always now derive from my converters from the MarkupExtension
so that I do not have to declare the converter before I use it. This converter is not compatible with the MarkupExtension
since the TrueValue
and FalseValue
need to be set. I could overcome this and maybe I will investigate the possibility in the future. Ultimately it would probably even be a better solution. Here is the link to the article about the converter: http://www.codeproject.com/Articles/1017358/WPF-Converter-Helper-Class. The way the TrueValue
and FalseValue
are passed into converters that use the helper class is through the ConverterParameter
.
Background
Amazingly, I was on a Silverlight project for Microsoft where Visibility
was being set in the ViewModel
and I actually had to convert this Visibility
to a bool
for some other purpose using a value converter (I believe it was a bool
). Due to administrative constraints in changes I could make, I could not make the changes to the ViewModel
(I understand that they were probably going to fix this problem, but I left the project before the fix was implemented).
I probably started directly passing WPF enumerations from the ViewModel
to the View, but I was pretty quick in creating custom value converters to do the work. It seemed wrong to have WPF enumerations in the ViewModel
, and there was the IValueConverter
interface that provides the customization needed. In creating this code, I was simultaneously cursing Microsoft for not providing a simple logic within XAML to allow for simple conversions or simple equations.
I did not like creating a custom converter for each conversion of a Boolean to some value needed in the View, and wrote a simple value converter that could be customized for the values associated with true and false in the View’s XAML:
public class IfTrueValueConverter : IValueConverter
{
public object TrueValue { get; set; }
public object FalseValue { get; set; }
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (TrueValue == null)
{
TrueValue = true;
FalseValue = false;
}
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (TrueValue == null)
{
TrueValue = true;
FalseValue = false;
}
return (value.ToString() == TrueValue.ToString());
}
}
The XAML to use this converter is very similar to using a simple converter except that true and false values are defined as part of the defining of the converter in the resources:
<Window x:Class="GenericValueConverter.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:GenericValueConverter"
Title="IfTrueValueConverter example" Height="350" Width="525">
<Window.Resources>
<local:IfTrueValueConverter x:Key="VisibilityConverter"
TrueValue="Visible" FalseValue="Hidden"/>
</Window.Resources>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBox Name="TestControl" Text="Test Message" Margin="5"
Visibility="{Binding IsVisible,
Converter={StaticResource VisibilityConverter}}"/>
</StackPanel>
</Window>
As can be seen, first we need to define the value converter in the Window.Resources
element of the XAML. This is just defining any value converter for use except there are two additional arguments: TrueValue
and FalseValue
. All that is needed is to put in the string value for the enumeration desired for when the ViewModel
’s value is true and false. The conversion capability of WPF is such that it can convert these string
values into the enumeration, so the converter works. The converter can also be used to set colors, so string values of “Red” and “Black” can be assigned to TrueValue
and FalseValue
, and then the converter can be used to set the Foreground
Brush
.
Using the converter is now just like using any converter as can be seen in the XAML for the TextBox
.
I have used this value converter extensively in my coding, and have considered writing an article on the idea for quite a while, but I knew I could do better. First of all, I could convert the string to the actual type so that string conversion would not be required except the first time, which should increase performance at a small cost during initialization. Second, I could do some error checking. The main purpose of the error checking would be to help the programmer find errors; there have been quite a few times I have spent too much time finding a problem in binding that was actually very simple, just that WPF/Silverlight does a very poor job of helping the developer with binding problems. The two objectives actually go hand in hand since converting a string to the required value and giving the developer feedback both require the conversion of the string to the type expected by WPF.
The important aspect of creating an implementation is converting between a string and the value, and vice versa.
I have often worked with enumerations in WPF, so was quite familiar converting strings to the enumerations and enumerations to string values. Also, I have used the above value converter mostly for enumerations, particularly Visibility, so was only initially thinking about doing the checking and conversion only for enumerations. When I started to implement my improved value converter, I did the enumeration part, and then thought that it should also be able to convert strings to other object types since WPF and Silverlight do this conversion. Figuring out how to do it was a bit trickier.
I initially attempted to use the System.Convert.ChangeType
method, which did not work (did not surprise me). Research found the TypeConverter
class. This class can be associated with the associated class it translates for by using an attribute:
[TypeConverter(typeof(MyClassTypeConverter))]
public class MyClass
{
}
public class MyClassTypeConverter : TypeConverter
{
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture,
object value,
Type destinationType)
{
}
}
Microsoft has type converters for many of the standard classes. All that is required is to use the static TypeDescriptor.GetConverter
method, and then the ConvertFrom
method of the returned class:
TypeConverter converter = TypeDescriptor.GetConverter(targetType);
return converter.ConvertFrom(value);
I originally had a different code for conversion of the enumeration since I could use the Enum.Parse
method, but the TypeConverter
works for both, and using only the TypeConverter
eliminated code.
Implementation
The code for the converter is as follows:
public class IfTrueValueConverter : IValueConverter
{
public object TrueValue { get; set; }
public object FalseValue { get; set; }
private bool _checkValues;
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (!_checkValues)
Initialize(targetType);
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (!_checkValues)
Initialize(value.GetType());
return (value.ToString() == TrueValue.ToString());
}
private void Initialize(Type targetType)
{
_checkValues = true;
if (TrueValue == null)
{
TrueValue = true;
FalseValue = false;
}
else
{
TrueValue = FixValue(targetType, TrueValue);
FalseValue = FixValue(targetType, FalseValue);
}
}
private static object FixValue(Type targetType, object value)
{
if (value.GetType() == targetType)
return value;
try
{
TypeConverter converter = TypeDescriptor.GetConverter(targetType);
return converter.ConvertFrom(value);
}
catch
{
DisplayIssue(targetType, value);
return value;
}
}
[ConditionalAttribute("DEBUG")]
private static void DisplayIssue(Type targetType, object invalidValue)
{
if (targetType.IsEnum)
{
var enumNames = string.Join(", ", Enum.GetNames(targetType));
MessageBox.Show(string.Format(
"Enumeration value '{0}' not recognized for enumeration type '{1}'. " +
“Valid values are {2}.",
invalidValue, targetType, enumNames));
}
else
MessageBox.Show(string.Format(
"The value '{0}' not recognized for target type '{1}'.",
invalidValue, targetType));
}
}
The public methods are almost identical to the code above, except the creation of a private Initialize
method to remove the checking out of the public methods. The _checkValues
variable allows the initializing code to be skipped once initialization has occurred. The Initialize
method checks if the TrueValue
has been assigned (just like in the simpler implementation above), and makes it a Boolean if it has not. Next, the private static FixValue
function is called for each of the TrueValue
and FalseValue
variables. The FixValue
method first checks to make sure that conversion is required: if conversion is not required, just return the same value (if an attempt is made to convert twice, an exception will probably be raised). Otherwise, the code obtains the TypeConverter
for the targetType
, and does the conversion. If there is an exception (it is not possible to convert to a value of the right type), the code catches the exception and the original value is returned (probably this will not work, but WPF will let the code work). I have programmed the converter so that if the environment is in debug mode, then a message box will inform the developer of the reason for the exception. This is true because the method containing the MessageBox.Show
method is decorated with “ConditionalAttribute("DEBUG")
”. In this conditional method, the target type is checked for being an enumeration so that the message box content for an enumeration can include additional information about the acceptable enumeration values.
The Example
The example included shows this converter being used for a number of different properties on a TextBox
. These properties are changed by checking CheckBox
es. One of the checkboxes controls the visibility, and this one needs to be checked to be able to see any of the other changes. Among the properties that are bound are double (FontSize
), Brush (BorderBrush
), Visibility, reversed Boolean (IsReadOnly
), and Thickness
(BorderThickness
). This should be a pretty good variety of properties and types to show the flexibility of the value converter.
The XAML for this is as follows:
<Window x:Class="GenericValueConverter.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:GenericValueConverter"
Title="IfTrueValueConverter example" Height="250" Width="450">
<Window.Resources>
<local:IfTrueValueConverter x:Key="VisibilityConverter"
TrueValue="Visible" FalseValue="Hidden"/>
<local:IfTrueValueConverter x:Key="BorderColorConverter"
TrueValue="Red" FalseValue="Blue"/>
<local:IfTrueValueConverter x:Key="ReverseBoolConverter"
TrueValue="false" FalseValue="true"/>
<local:IfTrueValueConverter x:Key="BorderThicknessConverter"
TrueValue="3,4,5,6" FalseValue="1,2,3,4"/>
<local:IfTrueValueConverter x:Key="FontSizeConverter"
TrueValue="10" FalseValue="12.5"/>
</Window.Resources>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<CheckBox Content="checked for visible, unchecked for invisible"
IsChecked="{Binding IsVisible,FallbackValue=true}"/>
<CheckBox Content="checked for Red border color, unchecked for Blue border color"
IsChecked="{Binding BorderColor,FallbackValue=true}"/>
<CheckBox Content="checked to enable editing (IsReadOnly = false)"
IsChecked="{Binding CanEdit,FallbackValue=true}"/>
<CheckBox Content="checked for thick border thickness, unchecked for thinner"
IsChecked="{Binding ThinBorderThickness,FallbackValue=true}"/>
<CheckBox Content="checked for font size 10, unchecked for font size 12.5"
IsChecked="{Binding FontSize,FallbackValue=true}"/>
<TextBlock Text="TestControl:" Margin="0,5,0,0" Foreground="DarkBlue"/>
<TextBox Name="TestControl" Text="Test Message" Margin="5" Height="30"
Visibility="{Binding IsVisible,
Converter={StaticResource VisibilityConverter}}"
BorderBrush="{Binding BorderColor,
Converter={StaticResource BorderColorConverter}}"
BorderThickness="{Binding ThinBorderThickness,
Converter={StaticResource BorderThicknessConverter}}"
FontSize="{Binding FontSize,
Converter={StaticResource FontSizeConverter}}"
IsReadOnly="{Binding CanEdit,Converter={StaticResource
ReverseBoolConverter}}"/>
</StackPanel>
</Window>
As can be seen, using the converter is just like using the simpler one I showed above.
You will have to modify the XAML to show the debugging assistance that this code provides. All that is required is that either TrueValue
or FalseValue
be assigned an invalid value when the converter is defined in the XAML. If TrueValue
for Visibility
is changed to something like “illegal”, then the following MessageBox
would appear:
This information should provide a lot of help in fixing binding issues for enumerations. For non-enumerations, the MessageBox
is slightly simpler without the list of valid enumeration values:
One of the nice things is that the dialog is only displayed the first time the converter is run.
There are other ways to provide feedback on translation issues, including writing to the Output
window, but I prefer displaying a message to the developer.
Possible Improvements and Options
One of the ideas I have had is to extend the converter to be three-state where the third state is null. Personally, I have not seen any need for this third value in my code, but I can see cases where a third value is desirable.
Another idea is to add properties that allow the value to be compared to something other than true and false. This would be pretty straightforward, and could be very useful if binding to properties of a control defined in the XAML.
An extension of this would be support for more than two or three values. This could be done by defining all the items in a delimited list in the XAML that is entered as a property in the converter. The converter would then parse the list to get the conversions.
As I stated above, think that it should be possible to use the MarkupExtension
class to make it so that this converter does not have to be declared before it is used.
Conclusion
This generic Boolean value converter is flexible enough to handle all bool
conversions required by a View
. It can also be used in certain circumstances to do other binding where the value being bound to is a bool
.