关于DataContext、硬编码值、绑定表达式、模板和嵌套控件的操作顺序

6

这个问题困扰了我很久,我已经厌倦了绕过这个问题。在WPF中,涉及以下操作时的“操作顺序”是什么:

  • 设置DataContext
  • 继承DataContext
  • 评估“硬编码”属性值
  • 评估{Binding}属性值

所有这些都要考虑到嵌套控件和模板(当应用模板时)。

我遇到了许多问题,但这里只举一个例子:

自定义用户控件

<UserControl x:Class="UserControls.TestUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             >
    <StackPanel>
        <Label Content="{Binding Label1}" />
        <Label Content="{Binding Label2}" />
    </StackPanel>
</UserControl>

用户控件代码后台
using System;
using System.Windows;
using System.Windows.Controls;

namespace UserControls
{
    public partial class TestUserControl : UserControl
    {
        public static readonly DependencyProperty Label1Property = DependencyProperty.Register("Label1", typeof(String), typeof(TestUserControl), new FrameworkPropertyMetadata(OnLabel1PropertyChanged));
        public String Label1
        {
            get { return (String)GetValue(Label1Property); }
            set { SetValue(Label1Property, value); }
        }

        public static readonly DependencyProperty Label2Property = DependencyProperty.Register("Label2", typeof(String), typeof(TestUserControl), new FrameworkPropertyMetadata(OnLabel2PropertyChanged));
        public String Label2
        {
            get { return (String)GetValue(Label2Property); }
            set { SetValue(Label2Property, value); }
        }

        public TestUserControl()
        {
            DataContext = this;

            InitializeComponent();
        }

        private static void OnLabel1PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            //used for breakpoint
        }

        private static void OnLabel2PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            //used for breakpoint
        }
    }
}

使用用户控件的窗口

<Window x:Class="Windows.TestWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:UC="clr-namespace:UserControls"
        >
    <StackPanel>
        <Label Content="Non user control label" />

        <UC:TestUserControl x:Name="uc" Label1="User control label 1" Label2="{Binding Label2FromWindow}" />
    </StackPanel>
</Window>

窗口的代码后台

using System;
using System.Windows;

namespace Windows
{
    public partial class TestWindow : Window
    {
        public String Label2FromWindow
        {
            get { return "User control label 2"; }
        }

        public TestWindow()
        {
            DataContext = this;

            InitializeComponent();
        }
    }
}

在这种情况下,为什么用户控件中的“Label2”从窗口中的“Label2FromWindow”没有获取到值呢?我感觉这是一个时间问题,即用户控件先评估所有表达式,然后窗口稍后再评估其表达式,并且用户控件从未“被通知”窗口已评估的值。
这个例子有助于说明一个问题,但我的真正问题是:
关于DataContext、属性上的硬编码值、绑定表达式、模板和嵌套控件,操作的顺序是什么?
编辑: H.B. 帮助我认识到了这一点。当窗口的DataContext设置为自身时,用户控件将“继承”DataContext。这使得绑定可以在用户控件的属性上工作,但在用户控件内部绑定其本地属性则不起作用。当DataContext直接设置在用户控件上时,窗口对用户控件属性的绑定不再起作用,但用户控件可以绑定到其自己的本地属性。以下是更新后的可工作代码示例。
用户控件:
<UserControl x:Class="UserControls.TestUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                Name="uc">
    <StackPanel>
        <Label Content="{Binding ElementName=uc, Path=Label1}" />
        <Label Content="{Binding ElementName=uc, Path=Label2}" />
    </StackPanel>
</UserControl>

用户控件代码后台:
using System;
using System.Windows;
using System.Windows.Controls;

namespace UserControls
{
    public partial class TestUserControl : UserControl
    {
        public static readonly DependencyProperty Label1Property = DependencyProperty.Register("Label1", typeof(String), typeof(TestUserControl));
        public String Label1
        {
            get { return (String)GetValue(Label1Property); }
            set { SetValue(Label1Property, value); }
        }

        public static readonly DependencyProperty Label2Property = DependencyProperty.Register("Label2", typeof(String), typeof(TestUserControl));
        public String Label2
        {
            get { return (String)GetValue(Label2Property); }
            set { SetValue(Label2Property, value); }
        }

        public TestUserControl()
        {
            InitializeComponent();
        }
    }
}

测试窗口:

<Window x:Class="Windows.TestWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:UC="clr-namespace:UserControls"
        >
    <StackPanel>
        <Label Content="Non user control label" />

        <UC:TestUserControl Label1="User control label 1" Label2="{Binding Label2FromWindow}" />
    </StackPanel>
</Window>

测试窗口代码后台:

using System;
using System.Windows;

namespace Windows
{
    public partial class TestWindow : Window
    {
        public String Label2FromWindow
        {
            get { return "User control label 2"; }
        }

        public TestWindow()
        {
            DataContext = this;
            InitializeComponent();
        }
    }
}
2个回答

3
我认为这不是关于顺序的问题,而是关于优先级的问题(也许我在这里有点过于苛求)。您明确设置了UserControlDataContext——这意味着它不会被继承,因此您的绑定将在UserControl内寻找Label2FromWindow属性。显然,它找不到。
永远不要设置UserControl实例的DataContext,这样就不会遇到这样的问题。(给您的UserControl命名并使用ElementName进行内部绑定) 完整优先级列表请参见MSDN

谢谢!您提供的链接正是我正在寻找的文档。然而,正是您的回答让我豁然开朗。 - tyriker

0

另一种有效的方法是将您的UserControl的第一个子元素命名为x:Name="LayoutRoot"。然后在您的UserControl构造函数中,使用LayoutRoot.DataContext=this。

这种方法允许您从周围的窗口设置在UserControl上定义的DependencyProperties的值,并且仍然可以在UserControl标记中使用标准绑定,而不必使用ElementName或RelativeSource绑定。

Colin Ebererhardt在这里解释了这个问题:http://www.scottlogic.com/blog/2012/02/06/a-simple-pattern-for-creating-re-useable-usercontrols-in-wpf-silverlight.html


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接