繁体   English   中英

如何从 xaml 中的 ItemTemplate 访问在代码中定义的用户控件的依赖项属性?

[英]How can I access a dependency property of a user control that was defined in code behind from a ItemTemplate in xaml?

我有一个 ListBox,其 ListBoxItems 是从 UserControl 派生的控件。 这些控件中的每一个都包含一个派生自 Button 的 UserControl 和一些 TextBox。 我想通过使用 ListBox 中的 ItemTemplate 将这些绑定到我模型中的元素,但是我无法从 ItemTemplate 为按钮分配值。

ListViewer 包含 ListBox 及其 DataTemplate:

<UserControl x:Class="TestListBoxBinding.ListViewer"
             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:model="clr-namespace:Model"
             xmlns:vm="clr-namespace:ViewModel"
             xmlns:local="clr-namespace:TestListBoxBinding"
             mc:Ignorable="d">
    <UserControl.DataContext>
        <vm:ListViewerViewModel/>
    </UserControl.DataContext>
    <UserControl.Resources>
        <DataTemplate DataType="{x:Type model:Person}">
            <Grid Name="theListBoxItemGrid">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <local:PersonButton Grid.Column="0" x:Name="thePersonButton"
                                    FirstName="Ken"
                                    LastName="Jones"/>
                <TextBlock Grid.Column="1" Name="theFirstName" Text="{Binding FirstName}" Margin="5,0,5,0"/>
                <TextBlock Grid.Column="2" Name="theLastName" Text="{Binding LastName}" Margin="0,0,5,0"/>
            </Grid>
        </DataTemplate>
    </UserControl.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0" Name="theHeader" Text="These are my people"/>
        <ListBox Grid.Row="1" Name="theListBox"
                 ItemsSource="{Binding MyPeople}"/>
        <StackPanel Grid.Row="2" Name="theFooterPanel" Orientation="Horizontal">
            <TextBlock Name="theFooterLabel" Text="Count = "/>
            <TextBlock Name="theFooterCount" Text="{Binding MyPeople.Count}"/>
        </StackPanel>
    </Grid>
</UserControl>

其背后的代码:

using Model;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;

namespace TestListBoxBinding
{
    /// <summary>
    /// Interaction logic for ListViewer.xaml
    /// </summary>
    public partial class ListViewer : UserControl
    {
        public ListViewer()
        {
            InitializeComponent();
        }
    }
}

namespace ViewModel
{
    public class ListViewerViewModel
    {
        // Properties

        public People MyPeople { get; private set; }

        // Constructors

        public ListViewerViewModel()
        {
            MyPeople = (People)Application.Current.Resources["theOneAndOnlyPeople"];
            MyPeople.Add(new Person("Able", "Baker"));
            MyPeople.Add(new Person("Carla", "Douglas"));
            MyPeople.Add(new Person("Ed", "Fiducia"));
        }
    }
}

namespace Model
{
    public class Person
    {
        // Properties

        public string FirstName { get; set; }
        public string LastName { get; set; }

        // Constructors

        public Person()
        {
            FirstName = "John";
            LastName = "Doe";
        }

        public Person(string firstName, string lastName)
        {
            this.FirstName = firstName;
            this.LastName = lastName;
        }
    }

    public class People : ObservableCollection<Person>
    {
    }
}

PersonButton 在后面的代码中定义了 FirstName 和 LastName DependencyProperties,我最终将使用它们来设置按钮的显示内容。 这是它的xaml:

<UserControl x:Class="TestListBoxBinding.PersonButton"
             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:TestListBoxBinding"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Button Name="theButton" Padding="5,0,5,0"/>
    </Grid>
</UserControl>

其背后的代码:

using System.Windows;
using System.Windows.Controls;

namespace TestListBoxBinding
{
    /// <summary>
    /// Interaction logic for PersonButton.xaml
    /// </summary>
    public partial class PersonButton : UserControl
    {
        // Properties

        public static readonly DependencyProperty FirstNameProperty = DependencyProperty.Register("FirstName", typeof(string), typeof(PersonButton));
        public string FirstName { get { return (string)GetValue(FirstNameProperty); } set { SetValue(FirstNameProperty, value); } }

        public static readonly DependencyProperty LastNameProperty = DependencyProperty.Register("LastName", typeof(string), typeof(PersonButton));
        public string LastName { get { return (string)GetValue(LastNameProperty); } set { SetValue(LastNameProperty, value); } }

        // Constructors
        public PersonButton()
        {
            InitializeComponent();
            FirstName = "Ira";
            LastName = "Jacobs";
            theButton.Content = FirstName + " " + LastName;
        }
    }
}

从输出中可以看出:

在此处输入图片说明

尝试从 ListBox 的 DataTemplate 设置 PersonButton 的 FirstName 和 LastName 属性失败。 我在属性集上设置了断点,它们只在构造控件时触发,但从未从 DataTemplate 触发。 我使用 Live Visual Tree 来确认这些属性实际上仍然在初始化时设置。

我还应该提到,最初,我收到了一个 Intellisense 警告,指出无法从 DataTemplate 访问这些属性,但是在我删除 bin 和 obj 文件夹并清理并重建解决方案后,该警告就消失了。

更新

我相信这与评论中建议的重复不同。

至于 PersonButton 没有任何意义,这整个代码集合是我创建的一个更复杂的应用程序的一个大大简化的表示,只是为了以更易于阅读的形式显示我遇到的问题。 我已经测试了这段代码,它完全复制了我遇到的问题。 PersonButton 表示的实际控件要复杂得多,并且在后面的代码中定义的属性的使用完全适合该控件。 PersonButton 正在做它正在做的事情的唯一原因是可以很容易地判断这些属性是否由分配设置。

现在,为了解决我的问题,“为什么 PersonButton.FirstName 和 PersonButton.LastName 没有被我的 ListViewer 控件中 ListBox 的 DataTemplate 中的 PersonButton 的赋值语句改变?”

正如我之前提到的,我已经运行了几次测试,它们都验证了实际控件中的那些值没有被赋值设置。 自从看到 Clemens 的回复后,我什至实现了 PropertyChangedCallback 函数,他们也确认了 PersonButton 属性没有被 ListBox 分配设置。

我特别感兴趣的是,这些分配本身将成为绑定,允许我通过 ItemsSource 机制捕获对模型中列表中每个单独项目的更改。 在我的原始应用程序中,这些绑定有效,但我的问题在于实际将它们分配给 PersonButton 控件。

我发现了问题! 它与依赖属性值优先级有关。 当相同的依赖属性被快速连续设置多次(足够接近以至于它们可能会相互干扰)时,系统会对它们的处理应用优先级。 本质上,本地设置值的处理优先级高于远程设置值。

在这种情况下,当 ListViewViewModel 初始化 Model 中的 People 集合时,属性更改处理程序快速连续(通常小于 1 毫秒)命中两次:一次是在构造 Person 时,一次是在将 Person 添加到集合时(和,因此,列表框)。

    public ListViewerViewModel()
    {
        MyPeople = (People)Application.Current.Resources["theOneAndOnlyPeople"];
        MyPeople.Add(new Person("Able", "Baker"));
        MyPeople.Add(new Person("Carla", "Douglas"));
        MyPeople.Add(new Person("Ed", "Fiducia"));
    }

因此,它们相互冲突,并且系统优先考虑由 PersonButton 的构造函数设置的值,因为它是本地的。

我注释掉了 PersonButton 构造函数中默认值的分配,瞧,来自 ListBox 的分配起作用了。

    public Person()
    {
        FirstName = "John";
        LastName = "Doe";
    }

这是一个追击的PITA,但现在我有了答案,这也很有趣。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM