WPF 绑定到从后面的代码添加的 UserControl 的祖先

WPF bind to ancestor from UserControl that is added from code behind

我有一个用户控件,用于从代码隐藏动态填充列表框。我希望在选择父 ListBoxItem 时反转图标的颜色。

但是数据触发器不起作用。我收到以下错误消息:“未找到来源:RelativeSource FindAncestor,AncestorType='System.Windows.Controls.ListBoxItem',AncestorLevel='1'”

但是,我遇到了 2 个 DataTrigger(如下所示)开始工作的情况。

  1. 如果我在 XAML 中对用户控件进行硬编码。这不是一个选项。
  2. 如果我更改样式的某些内容(例如,默认值从 true 变回 false)。所以基本上如果我强制重新评估样式。

所以我想我知道发生了什么,但我不知道该怎么办:我在代码后面创建了一个新的 UserControl 实例,Style 和 DataTrigger 立即被评估并抛出错误(这是有道理的,因为它尚未添加到 VisualTree,因此找不到祖先。

这是我的用户控件的内容:

<UserControl.Resources>
    <Style x:Key="FontAwesomeIconInvertedColorOnSelection" TargetType="fonts:FontAwesomeIcon">
        <Setter Property="ReverseColors" Value="False" />
        <Style.Triggers>
            <DataTrigger Binding="{Binding
                            RelativeSource={RelativeSource
                            Mode=FindAncestor,
                            AncestorType={x:Type ListBoxItem}},
                            Path=IsSelected}"
                         Value="True">
                <Setter Property="ReverseColors" Value="True" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</UserControl.Resources>
<Grid>
    <fonts:FontAwesomeIcon 
        Style="{StaticResource FontAwesomeIconInvertedColorOnSelection}" />
</Grid>

我能否以某种方式强制样式在 UserControl.Loaded 时重新评估?或者您对如何获得我想要的行为有其他建议吗?

您的 UserControl 应该公开一个适当的 属性,例如IsSelected 您将绑定到 DataTemplate 中的 ListBoxItem 的 IsSelected 属性。

<ListBox>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <local:MyIconControl
                IsSelected="{Binding IsSelected,
                    RelativeSource={RelativeSource AncestorType=ListBoxItem}}"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

在 UserControl 的 XAML 中,属性 将被另一个 RelativeSource 绑定绑定:

<Grid>
    <fonts:FontAwesomeIcon
        ReverseColors="{Binding IsSelected,
            RelativeSource={RelativeSource AncestorType=UserControl}}" />
</Grid>

这个答案不是问题的解决方案(特别是考虑到@Clemens 的回答)。 将此视为扩展评论。

您提出的问题与DataTrigger中的Style和Binding无关。

这是一个简单的示例,展示了在不使用自定义 FontAwesomeIcon 的情况下应用您的样式:

<UserControl x:Class="StyleInUserControl.MyUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:StyleInUserControl"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <Style x:Key="FontAwesomeIconInvertedColorOnSelection" TargetType="TextBlock">
            <Style.Triggers>
                <DataTrigger Binding="{Binding
                            RelativeSource={RelativeSource
                            Mode=FindAncestor,
                            AncestorType={x:Type ListBoxItem}},
                            Path=IsSelected}"
                         Value="True">
                    <Setter Property="Foreground" Value="LightGreen" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </UserControl.Resources>
    <Grid>
        <TextBlock Text="{Binding}" 
                   Style="{DynamicResource FontAwesomeIconInvertedColorOnSelection}"/>
    </Grid>
</UserControl>
<Window x:Class="StyleInUserControl.MyUCExamleWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MyUCExamleWindow" Height="450" Width="800">
    <Grid>
        <ListBox x:Name="listBox"/>
    </Grid>
</Window>
    public partial class MyUCExamleWindow : Window
    {
        public MyUCExamleWindow()
        {
            InitializeComponent();

            listBox.Items.Add(new MyUserControl() { DataContext = "First" });
            listBox.Items.Add(new MyUserControl() { DataContext = "Second" });
            listBox.Items.Add(new MyUserControl() { DataContext = "Third" });
            listBox.SelectedIndex = 1;
        }
    }

这个例子很好用,没有任何绑定错误。

由此我们可以得出结论,问题的原因不在于您显示的代码。 它可以在“fonts: FontAwesomeIcon”的实现代码中找到,也可以在 Window 中的 ListBox 的初始化代码中找到。 但是你没有显示这些代码。

至于UserControl本身的实现,它只有一个UI元素和它的样式,完全不清楚为什么要声明这样一个UserControl。

我也完全同意克莱门特的观点。
UserControl“不知道”它将在哪个容器中的何处使用。 而且Style中的“FindAncestor”绑定看起来非常歪歪扭扭。 如果你需要一些只能通过UserControl的应用位置来确定的外部数据,那么最好在UserControl中为它们声明一个属性,然后设置一个值或绑定到这个属性.