Search code examples
wpfwpf-controls

Element binding not updating when dependency property changed


I have a custom button control which has a dependency property "IsRequired"

    public static readonly DependencyProperty IsRequiredProperty = DependencyProperty.RegisterAttached(nameof(IsRequired), typeof(bool), typeof(RequiredButton), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));

    public bool IsRequired
    {
        get { return (bool)GetValue(IsRequiredProperty); }
        set { SetValue(IsRequiredProperty, value); }
    }

And implements an interface

public interface IRequiredControl
{
    bool IsEnabled { get; }
    bool IsRequired { get; }
}

And I have a converter that uses this interface

   <sharedConverters:IsRequiredToImageConverter x:Key="IsRequiredToImageConverter"
                                                 DisabledImage="{StaticResource DisabledDrawing}"
                                                 NormalImage="{StaticResource NormalDrawing}"
                                                 RequiredImage="{StaticResource IsRequiredDrawing}" />


    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is IRequiredControl requiredControl)
        {
            return !requiredControl.IsEnabled ? DisabledImage : requiredControl.IsRequired ? RequiredImage : NormalImage;
        }

        return DependencyProperty.UnsetValue;
    }

I use the converter and bind the image source to the control, as seen below.

<DataTemplate x:Key="RightSideAddTemplate">
    <sharedControls:RequiredButton x:Name="addButton"
                                   Command="{x:Static commands:AddCommand}"
                                   IsRequired="{Binding IsButtonRequired}">
            <Image Source="{Binding ElementName=addButton, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IsRequiredToImageConverter}}" />
    </sharedControls:RequiredButton>
</DataTemplate>

The issue occurs here where the converter is never called when the "IsButtonRequired" viewmodel property is changed. When I set the "IsRequired" of the "AddButton" explicitly to true it works correctly. How can I make the converter update on a change to the "IsRequired" property?

Note I have another solution working where I use a multivalue converter, but I would prefer to get element binding solution working, because it requires much less xaml.

<Image Style="{StaticResource AddImageButtonStyle}">
     <Image.Source>
           <MultiBinding Converter="{StaticResource IsRequiredToImageMultiConverter}">
               <MultiBinding.Bindings>
                     <Binding ElementName="addButton" Path="IsEnabled" />
                     <Binding Path="IsButtonRequired" />
               </MultiBinding.Bindings>
            </MultiBinding>
         </Image.Source>
     </Image>

Solution

  • The Binding expression

    <Image Source="{Binding ElementName=addButton,
                    Converter={StaticResource IsRequiredToImageConverter}}" />
    

    binds directly to the RequiredButton control, and is not supposed to be triggered when a property of the control changes.

    The proper way to implement this is a MultiBinding on both the IsEnabled and IsRequired properties of the control:

    <MultiBinding Converter="{StaticResource IsRequiredToImageMultiConverter}">
        <MultiBinding.Bindings>
            <Binding ElementName="addButton" Path="IsEnabled"/>
            <Binding ElementName="addButton" Path="IsRequired"/>
        </MultiBinding.Bindings>
    </MultiBinding>
    

    The multi-value converter would have to test two boolean values.


    Alternatively to using a MultiBinding, a Style with a set of MultiTriggers would also work:

    <Style TargetType="sharedControls:RequiredButton">
        <Setter Property="Content">
            <Setter.Value>
                <Image Source="{StaticResource DisabledDrawing}"/>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="IsEnabled" Value="true" />
                    <Condition Property="IsRequired" Value="false" />
                </MultiTrigger.Conditions>
                <Setter Property="Content">
                    <Setter.Value>
                        <Image Source="{StaticResource NormalDrawing}"/>
                    </Setter.Value>
                </Setter>
            </MultiTrigger>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="IsEnabled" Value="true" />
                    <Condition Property="IsRequired" Value="true" />
                </MultiTrigger.Conditions>
                <Setter Property="Content">
                    <Setter.Value>
                        <Image Source="{StaticResource IsRequiredDrawing}"/>
                    </Setter.Value>
                </Setter>
            </MultiTrigger>
        </Style.Triggers>
    </Style>