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>
我把问题分解成更简单的形式,但我还是想不通。我有一个空白的 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>