Using Triggers in place of converters: My Encounter with WinForm style WPF apps 1

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>
converter:
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.

No Comments

Add a Comment

As it will appear on the website

Not displayed

Your website