I am working on some refactoring kind of tasks and thought to share some of my findings with you all. In one of the application I am working, lot of converters were used for determining the value of a property based on various conditions (values of other properties); It’s not required to use these converters at all and these can easily be replaced with Triggers. I don’t have any statistics in favor of triggers at present but some problems I see in using converters are –
Cons:
1. The logic resides in a separate place where it doesn’t belong.
2. As the number of files/classes increases maintainability suffers (in this case).
3. WPF keeps refreshing values of most of the properties (like IsEditable for textbox) whenever any operation on text box happens (like mouse over) and if a converter is used to calculate the value of this property then it will be called every time. Although, triggers will also be evaluated every time but as they are evaluated by WPF framework it’s safe to assume that they will be more efficient then our converter.
4. Converters are more prone to break functionality (as same converter can be used at multiple places and a change for one feature may adversely affect other).
Pros:
1. Converters make it easy to debug the code and are helpful in case something is not working (but, generally it’s needed once in a while; so we can use a dummy converter whenever required).
Actually, “converters are for converting one data type into another” not for determining conditional values. So converters should be used only if there is a need of converting a value from one type to another and not for calculating the value based on some conditions.
Examples of using triggers in place of converters –
Ex 1:
This code was used for setting the IsEditable property of a control:
<CommonControls:InlineRenameableLabel.IsEditable>
<MultiBinding
Converter="{StaticResource MultipleBoolConverter}">
<Binding
Path="Model.EditMode" />
<Binding
Path="Model.UserRights.CanSetCells" />
</MultiBinding>
</CommonControls:InlineRenameableLabel.IsEditable>
and the converter:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
foreach (object value in values)
{
if (value is bool && (bool)value == false)
{
return false;
}
}
return true;
}
Trigger which can be used in place of this:
<CommonControls:InlineRenameableLabel.Style>
<Style
TargetType="{x:Type CommonControls:InlineRenameableLabel}">
<Setter
Property="IsEditable"
Value="False" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition
Binding="{Binding Path=Model.EditMode}"
Value="True" />
<Condition
Binding="{Binding Path=Model.UserRights.CanSetCells}"
Value="True" />
</MultiDataTrigger.Conditions>
<Setter
Property="IsEditable"
Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</CommonControls:InlineRenameableLabel.Style>
Ex 2 –
This code was used for setting the Tag property of a textbox
<CommonControls:InlineRenameableLabel.Tag>
<MultiBinding
Converter="{StaticResource UpperLeftCornerHeaderTextConverter}">
<Binding
BindsDirectlyToSource="true"
Path="Model.UpperLeftCornerHeaderText" />
<Binding
BindsDirectlyToSource="true"
Path="Model.Title" />
</MultiBinding>
</CommonControls:InlineRenameableLabel.Tag>
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values == null || values.Length < 2)
{
return string.Empty;
}
Debug.Assert(values.Length == 2);
var cornerText = values[0] as string;
var title = values[1] as string;
if (string.IsNullOrEmpty(cornerText))
{
return title;
}
return cornerText;
}
Trigger to replace converter:
<CommonControls:InlineRenameableLabel.Style>
<Style
TargetType="CommonControls:InlineRenameableLabel">
<Setter
Property="Tag"
Value="{Binding Path=Model.UpperLeftCornerHeaderText}" />
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=Model.UpperLeftCornerHeaderText}"
Value="{x:Static System:String.Empty}">
<Setter
Property="Tag"
Value="{Binding Path=Model.Title}" />
</DataTrigger>
<DataTrigger
Binding="{Binding Path=Model.UpperLeftCornerHeaderText}"
Value="{x:Null}">
<Setter
Property="Tag"
Value="{Binding Path=Model.Title}" />
</DataTrigger>
</Style.Triggers>
</Style>
</CommonControls:InlineRenameableLabel.Style>
As a rule of thumb, I always try to refactor the code whenever a MultiValue converter is used for determining the value of a property.