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

时间:2019-06-29 01:25:55

标签: c# wpf xaml code-behind itemtemplate

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

ListViewer包含ListBox及其数据模板:

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

从输出中可以看到:

enter image description here

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

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

更新

我认为这与评论中建议的重复项不同。

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

现在,在这里解决我的问题是:“为什么在我的ListViewer控件的ListBox的数据模板中,PersonButton.FirstName和PersonButton.LastName未被其对PersonPersonButton的赋值语句更改?”

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

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

1 个答案:

答案 0 :(得分:-2)

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

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

vi

因此,它们彼此冲突,并且系统优先于PersonButton的构造函数设置的值,因为它是本地的。

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

    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"));
    }

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