wpfxamldata-bindingtriggersrelativesource

WPF XAML Relative Source Not Updating In Data Trigger


I decomposed the problem to a simpler form but I still can't figure it out. I have a blank WPF application with a single button in the main XAML file.

The button has 2 icons that change depending on the state of the window. The icon's brush property is bound to the button's foreground property that changes if the window is active or inactive. All is fine until I maximize the window but the second icon is not shown. I know the maximize window state is working as I am able to change the background of the button to blue.

I get a binding failure error "Cannot find source: RelativeSource FindAncestor, AncestorType='System.Windows.Controls.Button', AncestorLevel='1'."

I use the same binding for the default state that works fine. It only fails to update inside the data trigger for the second icon.

Snapshot showing the problem & error

<Button Width="32" Height="32" HorizontalAlignment="Right" VerticalAlignment="Top">
<Button.Style>
    <Style TargetType="{x:Type Button}">

        <!--Default-->
        <Setter Property="Foreground" Value="Red"/>
        <Setter Property="Content">
            <Setter.Value>
                <Viewbox Width="16" Height="16" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
                    <Rectangle Width="16" Height="16">
                        <Rectangle.Fill>
                            <DrawingBrush>
                                <DrawingBrush.Drawing>
                                    <DrawingGroup>
                                        <DrawingGroup.Children>
                                            <GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" />
                                            <GeometryDrawing Brush="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}}" Geometry="F1M12,12L4,12 4,4 12,4z M3,13L13,13 13,3 3,3z" />
                                        </DrawingGroup.Children>
                                    </DrawingGroup>
                                </DrawingBrush.Drawing>
                            </DrawingBrush>
                        </Rectangle.Fill>
                    </Rectangle>
                </Viewbox>
            </Setter.Value>
        </Setter>

        <Style.Triggers>
            <!--Window inactive-->
            <DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Value="false">
                <Setter Property="Foreground" Value="White"/>
            </DataTrigger>

            <!--Window maximized-->
            <DataTrigger Binding="{Binding Path=WindowState, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Value="Maximized">
                <Setter Property="Background" Value="Blue"/>
                <Setter Property="Content">
                    <Setter.Value>
                        <Viewbox Width="16" Height="16" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
                            <Rectangle Width="16" Height="16">
                                <Rectangle.Fill>
                                    <DrawingBrush>
                                        <DrawingBrush.Drawing>
                                            <DrawingGroup>
                                                <DrawingGroup.Children>
                                                    <GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" />
                                                    <GeometryDrawing Brush="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}}" Geometry="F1M11.999,10.002L10.998,10.002 10.998,5.002 5.998,5.002 5.998,4.001 11.999,4.001z M10.002,11.999L4.001,11.999 4.001,5.998 10.002,5.998z M5.002,3L5.002,5.002 3,5.002 3,13 10.998,13 10.998,10.998 13,10.998 13,3z" />
                                                </DrawingGroup.Children>
                                            </DrawingGroup>
                                        </DrawingBrush.Drawing>
                                    </DrawingBrush>
                                </Rectangle.Fill>
                            </Rectangle>
                        </Viewbox>
                    </Setter.Value>
                </Setter>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Button.Style>
</Button>

UPDATE

Thank you all for the help! Both of the below answers work perfectly. I also had a look at the suggested similar question by Sergey:

Datatrigger in WPF Style: Change content doesn't work

By putting the icon's path content inside the Resources and using those in the DataTrigger also works. However, with this method the icon's are not shown in the designer therefore I prefer the other two methods from the comments.

I've marked Sergey's answer as the accepted answer as the explanation of the RelativeSource Binding FindAncestor Mode and where I went wrong was very useful.

Thanks again all!


Solution

  • You misunderstand how the RelativeSource Binding FindAncestor Mode works. It does NOT work in such a way that property is binded to the nearest ancestor of the desired type at any given time. The ancestor is found only once , when the binding is created. In your case, the Viewbox created inside the DataTrigger setter does not have a button type ancestor at the time of creation (since it is not yet part of the visual tree). Therefore, this binding gives an error at the initialization.

    The possible solutions are to use DataTemplate, as suggested by elena.kim, or ControlTemplate, as in the example below:

        <Button Width="32" Height="32" Foreground="Green" HorizontalAlignment="Center" VerticalAlignment="Top">
            <Button.Template>
                <ControlTemplate>
                    <Button x:Name="btn">
                        <Grid>
                            <Path Fill="#00FFFFFF" Stretch="None" Data="F1M16,16L0,16 0,0 16,0z"/>
                            <Path x:Name="icn" Fill="Red" Stretch="None" Data="F1M12,12L4,12 4,4 12,4z M3,13L13,13 13,3 3,3z"/>
                        </Grid>
                    </Button>
                    <ControlTemplate.Triggers>
                        <DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Value="false">
                            <Setter TargetName="icn" Property="Fill" Value="White"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding Path=WindowState, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Value="Maximized">
                            <Setter TargetName="btn" Property="Background" Value="Blue"/>
                            <Setter TargetName="icn" Property="Data" Value="F1M11.999,10.002L10.998,10.002 10.998,5.002 5.998,5.002 5.998,4.001 11.999,4.001z M10.002,11.999L4.001,11.999 4.001,5.998 10.002,5.998z M5.002,3L5.002,5.002 3,5.002 3,13 10.998,13 10.998,10.998 13,10.998 13,3z"/>
                        </DataTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Button.Template>
        </Button>