簡體   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