Mike Eason
Mike Eason

Reputation: 9713

Using a Dependency Property in a Data Trigger

I have the following custom control:

public class AnimatedButton : Button
    {
        public enum ButtonStates
        {
            None,
            Busy
        }

        public ButtonStates State
        {
            get { return (ButtonStates)GetValue(StateProperty); }
            set { SetValue(StateProperty, value); }
        }

        // Using a DependencyProperty as the backing store for State.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StateProperty =
            DependencyProperty.Register("State", typeof(ButtonStates), typeof(AnimatedButton), new PropertyMetadata(ButtonStates.None));

        public ImageSource ImageDefault
        {
            get { return (ImageSource)GetValue(ImageDefaultProperty); }
            set { SetValue(ImageDefaultProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ImageDefault.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ImageDefaultProperty =
            DependencyProperty.Register("ImageDefault", typeof(ImageSource), typeof(AnimatedButton), new PropertyMetadata(null));

        public ImageSource ImageBusy
        {
            get { return (ImageSource)GetValue(ImageBusyProperty); }
            set { SetValue(ImageBusyProperty, value); }
        }

        ...
    }

My aim here is to display the appropriate image source based on the current button state. For example, if the ButtonState is None, then display the default image, otherwise display the Busy image, pretty straightforward. Here is the style:

<Style TargetType="{x:Type controls:AnimatedButton}">
    ...
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type controls:AnimatedButton}">
                <Border>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>

                        <Image x:Name="img"/>

                        <TextBlock Text="{TemplateBinding Content}"
                                   Grid.Column="1"/>
                    </Grid>
                </Border>

                <ControlTemplate.Triggers>
                    <DataTrigger Binding="{Binding State}" Value="None">
                        <Setter TargetName="img" Property="Source" Value="{Binding ImageDefault}"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding State}" Value="Busy">
                        <Setter TargetName="img" Property="Source" Value="{Binding ImageBusy}"/>
                    </DataTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The problem is in the DataTrigger, it isn't picking up the dependency property State. After adding the control onto a view, I am receiving the following error in the Output:

System.Windows.Data Error: 40 : BindingExpression path error: 'State' property not found on 'object' ''WorkspaceViewModel' (HashCode=56037929)'. BindingExpression:Path=State; DataItem='WorkspaceViewModel' (HashCode=56037929); target element is 'AnimatedButton' (Name=''); target property is 'NoTarget' (type 'Object')

Reading that error message, it appears as though it's looking for the State property on the WorkspaceViewModel as opposed to the control that the dependency property belongs to. Why is this?

Upvotes: 0

Views: 1656

Answers (1)

Clemens
Clemens

Reputation: 128062

The Bindings in the DataTriggers (correctly) expect the State property to be in the DataContext of the control. But you want to trigger on the value of a property of the control itself.

You should therefore use Triggers instead of DataTriggers:

<ControlTemplate.Triggers>
    <Trigger Property="State" Value="None">
        <Setter TargetName="img" Property="Source"
            Value="{Binding ImageDefault, RelativeSource={RelativeSource TemplatedParent}}"/>
    </Trigger>
    <Trigger Property="State" Value="Busy">
        <Setter TargetName="img" Property="Source"
            Value="{Binding ImageBusy, RelativeSource={RelativeSource TemplatedParent}}"/>
    </Trigger>
</ControlTemplate.Triggers>

Upvotes: 1

Related Questions