Как я могу получить доступ к свойству зависимости пользовательского элемента управления, который был определен в коде позади ItemTemplate в xaml? - PullRequest
0 голосов
/ 29 июня 2019

У меня есть ListBox, элементы ListBoxItems которого являются производными от UserControl.Каждый из этих элементов управления содержит UserControl, полученный из Button, и некоторые текстовые поля.Я хочу связать их с элементами в моей модели, используя ItemTemplate в ListBox, но у меня возникают проблемы с назначением значений кнопке из 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;
        }
    }
}

Как видно из вывода:

enter image description here

Попытки установить свойства FirstName и LastName PersonButton из DataTemplate для ListBox не удаются.Я установил точки останова для наборов свойств, и они достигают цели только при создании элементов управления, но никогда из DataTemplate.Я использовал Live Visual Tree для подтверждения того, что свойства, на самом деле, все еще были установлены при инициализации.

Я должен также упомянуть, что изначально я получил предупреждение Intellisense о том, что свойства недоступны изDataTemplate, но после того, как я удалил папки bin и obj, очистил и пересобрал решение, которое ушло.

Обновление

Я считаю, что это не то же самое, что предложенный дубликат вкомментарии.

Что касается PersonButton, который не имеет никакого смысла, весь этот набор кода представляет собой значительно упрощенное представление части гораздо более сложного приложения, которое я создал, просто чтобы показать проблему, с которой я столкнулся в болеелегко читаемая форма.Я проверил этот код, и он точно дублирует проблему, с которой я столкнулся.Фактический элемент управления, который представляет PersonButton, является гораздо более сложным, и использование свойств, определенных в коде, полностью подходит для этого элемента управления.Единственная причина, по которой PersonButton делает то, что делает, состоит в том, чтобы было легко определить, были ли эти свойства установлены присваиваниями.

Теперь, чтобы ответить на мой вопрос здесь, это «Почему PersonButton.FirstName иPersonButton.LastName не изменяется их операторами присваивания для thePersonButton в DataTemplate для theListBox в моем элементе управления ListViewer? "

Как я упоминал ранее, я провел несколько тестов, и все они проверяют, что эти значения вфактический контроль не устанавливается заданиями.После просмотра ответа Клеменса я даже реализовал функции PropertyChangedCallback, и они также подтверждают, что свойства PersonButton не устанавливаются назначениями ListBox.

Особый интерес для меня заключается в том, что эти назначения станутсами привязки, которые позволяют мне фиксировать изменения для каждого отдельного элемента в списке в моей модели с помощью механизма ItemsSource.В моем исходном приложении эти привязки работают, но моя проблема в том, чтобы фактически назначить их элементу управления PersonButton.

1 Ответ

0 голосов
/ 30 июня 2019

Я нашел проблему!Это связано с приоритетом значения свойства зависимости.Когда одно и то же свойство зависимости устанавливается более одного раза в быстрой последовательности (достаточно близко, чтобы они могли мешать друг другу), система применяет приоритет к их обработке.В сущности, локально установленные значения обрабатываются с более высоким приоритетом, чем удаленно установленные значения.

В этом случае, когда ListViewViewModel инициализирует коллекцию People в модели, обработчик измененного свойства обращается дважды в быстрой последовательности (часто меньше 1ms): один раз, когда Person создается, и один раз, когда Person добавляется в коллекцию (и, следовательно, 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"));
    }

Таким образом, они конфликтуют друг с другом, и система отдает приоритетзначение, установленное конструктором PersonButton, потому что оно локальное.

Я закомментировал назначение значений по умолчанию в конструкторе PersonButton и, вуаля, сработало назначение из ListBox.

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

Это была PITA, чтобы преследовать, но теперь, когда у меня есть ответ, это было также забавно.

...