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

Posted

技术标签:

【中文标题】WPF 从后面的代码添加的 UserControl 绑定到祖先【英文标题】:WPF bind to ancestor from UserControl that is added from code behind 【发布时间】:2021-11-05 03:49:34 【问题描述】:

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

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

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

    如果我在我的 XAML 中对用户控件进行硬编码。这不是一个选择。 如果我更改样式的某些内容(例如,将默认值从 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 根本不应该声明 DataTrigger 绑定,因为它会创建不必要的依赖项。为什么控件总是只能在 ListBoxItem 中使用?相反,让您的 UserControl 公开其自己的 IsSelected 属性,您将 DataTrigger(或常规触发器)绑定到该属性,并在您使用该控件的 ListBox 的 ItemTemplate 中绑定。 从您所展示的内容来看,人们甚至可能会争论为什么有一个 UserControl,而不是一个简单的 DataTemplate 资源。 我省略了一些代码以减少混乱。它比上面显示的要多一点。谢谢您的建议。这很有意义!但是,如果我在 ItemTemplate 中使用我的 UserControl,那么我的 UserControl 中的 DataContext 将自动成为基础数据对象。然后如何绑定到我的 UserControl 中的 IsSelected 属性? 我想RelativeSource=RelativeSource Mode=FindAncestor, AncestorType=local:MyUserControl 应该可以工作。 【参考方案1】:

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

您提出的问题与 DataTrigger 中的样式和绑定无关。

这是一个简单的示例,展示了在不使用自定义 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;
        
    

此示例运行良好,没有任何绑定错误。

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

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

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

【讨论】:

【参考方案2】:

您的 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 Binding 绑定:

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

【讨论】:

以上是关于WPF 从后面的代码添加的 UserControl 绑定到祖先的主要内容,如果未能解决你的问题,请参考以下文章

在WPF中UserControl

ViewModel应该继承WPF中的DependencyObject吗?

WPF - 将 UserControl 可见性绑定到属性

从 Catel WPF UserControl 中的 ResourceDictionary 中绑定

从后面的代码中添加组合框项。 [WPF]

从代码隐藏(C#、WPF)添加时,用户控件无法在 ListBox 中正确显示