WPF XAML 相对源未在数据触发器中更新

WPF XAML Relative Source Not Updating In Data Trigger

我把问题分解成更简单的形式,但我还是想不通。我有一个空白的 WPF 应用程序,主 XAML 文件中只有一个按钮。

该按钮有 2 个图标,根据 window 的状态而变化。图标的画笔 属性 绑定到按钮的前景 属性,如果 window 处于活动状态或非活动状态,该前景会发生变化。一切都很好,直到我最大化 window 但第二个图标未显示。我知道最大化 window 状态有效,因为我可以将按钮的背景更改为蓝色。

我收到绑定失败错误“无法找到来源:RelativeSource FindAncestor,AncestorType='System.Windows.Controls.Button',AncestorLevel='1'。”

我对默认状态使用相同的绑定,效果很好。它仅无法在第二个图标的数据触发器内更新。

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>

更新

谢谢大家的帮助!以下两个答案都完美无缺。我还看了谢尔盖建议的类似问题:

通过将图标的路径内容放在 Resources 中并使用 DataTrigger 中的内容也可以。但是,使用这种方法,图标不会显示在设计器中,因此我更喜欢评论中的其他两种方法。

我已将 Sergey 的答案标记为已接受的答案,因为 RelativeSource Binding FindAncestor Mode 的解释和我出错的地方非常有用。

再次感谢大家!

如何使用 'ControlTemplate' 而不是在 DataTrigger 中更改 'Content' 本身?

这是一个示例。

<Button>
    <Button.Resources>

        <DataTemplate x:Key="DEFAULT">
            <Border Background="#DDDDDD"
                            BorderBrush="#AA111111"
                            BorderThickness="1">
                <Viewbox Width="16" Height="16">
                    <Canvas Width="16" Height="16">
                        <Path Data="F1M16,16L0,16 0,0 16,0z" Fill="#00FFFFFF"/>
                        <Path Data="F1M12,12L4,12 4,4 12,4z M3,13L13,13 13,3 3,3z" 
                                      Fill="{Binding RelativeSource={RelativeSource AncestorType={x:Type Button}}, Path=Foreground}"/>
                    </Canvas>
                </Viewbox>
            </Border>
        </DataTemplate>

        <DataTemplate x:Key="MAX">
            <Border Background="Blue"
                            BorderBrush="#AA111111"
                            BorderThickness="1">
                <Viewbox Width="16" Height="16">
                    <Canvas Width="16" Height="16">
                        <Path Data="F1M16,16L0,16 0,0 16,0z" Fill="#00FFFFFF"/>
                        <Path Data="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" 
                                      Fill="{Binding RelativeSource={RelativeSource AncestorType={x:Type Button}}, Path=Foreground}"/>
                    </Canvas>
                </Viewbox>
            </Border>
        </DataTemplate>

        <Style TargetType="{x:Type Button}">
            <Setter Property="Width" Value="32"/>
            <Setter Property="Height" Value="32"/>
            <Setter Property="HorizontalAlignment" Value="Right"/>
            <Setter Property="VerticalAlignment" Value="Top"/>
            <Setter Property="ContentTemplate" Value="{StaticResource DEFAULT}"/>
            <Setter Property="Foreground" Value="Red"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                        <ContentPresenter/>
                        <ControlTemplate.Triggers>
                            <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=IsActive}" Value="False">
                                <Setter Property="Foreground" Value="#FFFFFF"/>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=WindowState}" Value="Maximized">
                                <Setter Property="ContentTemplate" Value="{StaticResource MAX}"/>
                            </DataTrigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Button.Resources>
</Button>

建议您定义ControlTemplate,使用ControlTemplate中的Trigger来改变内容

您误解了 RelativeSource Binding FindAncestor Mode 的工作原理。它确实 NOT 以 属性 在任何给定时间绑定到所需类型的最近祖先的方式工作。仅在创建绑定时找到祖先一次。 在您的情况下,在 DataTrigger setter 中创建的 Viewbox 在创建时没有按钮类型祖先(因为它还不是可视化树的一部分)。因此,此绑定在初始化时出错。

可能的解决方案是使用 DataTemplate,如 elena.kim 所建议,或 ControlTemplate,如下例所示:

    <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>