有关 DataContext、硬编码值、绑定表达式、模板和嵌套控件的操作顺序
这已经困扰我一段时间了,我厌倦了解决这个问题。在 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、属性上的硬编码值、绑定表达式、模板和嵌套控件的操作顺序是什么?
编辑:
HB 帮助我认识到了这一点。当窗口的 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();
}
}
}
This has bothered me for a while and I'm tired of working around the issue. In WPF, what is the "order of operations" when it comes to:
- Setting DataContext
- Inheriting DataContext
- Evaluating a "hard-coded" property value
- Evaluating a {Binding} property value
All this taking into consideration nested controls and templates (when templates are applied).
I've had a number of problematic scenarios, but here's just one example:
Custom user control
<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>
User control code-behind
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 to use the user control
<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>
And the code-behind for the 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();
}
}
}
So in this scenario, why doesn't "Label2" in the user control ever get the value from "Label2FromWindow" from the window? I feel like it's a timing issue, where the user control evaluates all of its expressions first, and then the window evaluates its expressions later, and the user control is never "notified" of the window's evaluated values.
The example is hopefully helpful to illustrate one problem, but my real question is:
What is the order of operations with regards to DataContext, Hard-Coded Values on properties, Binding Expressions, Templates, and Nested Controls?
EDIT:
H.B. helped me come to this realization. When the window's DataContext is set to itself, the user control will "inherit" the DataContext. This lets the Binding work on the user control's property, but then within the user control Binding to its local properties won't work. When DataContext is set directly on the user control, the window's Binding to the user control's property no longer works, but the user control can then Bind to its own local properties. Below is the updated code sample that works.
User control:
<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>
User control code-behind:
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();
}
}
}
Test window:
<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>
Test window code-behind:
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();
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我认为这并不是真正的顺序,而是优先级(也许我在这里吹毛求疵)。您显式设置
UserControl
的DataContext
- 这意味着它不会被继承,因此您的绑定会查找属性UserControl 内的 Label2FromWindow
。显然,它没有找到它。只要永远不要设置
UserControl
实例的DataContext
就不会遇到此类问题。 (命名您的UserControl
并使用ElementName
进行内部绑定)有关完整的优先级列表参见 MSDN。
It's not really about order I think, but rather precedence (maybe I am splitting hairs here). You explicitly set the
DataContext
of theUserControl
-- this means that it will not be inherited, thus your binding looks for the propertyLabel2FromWindow
inside the UserControl. Obviously, it does not find it.Just never set the
DataContext
ofUserControl
instances and you should not run into such problems. (Name yourUserControl
and useElementName
for internal bindings)For a full precedence list see MSDN.
另一种有效的方法是为 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
Another method that works well is to give the first child of your UserControl the x:Name="LayoutRoot". Then in your UserControl constructor, use LayoutRoot.DataContext=this.
This method allows you to set the values of the DependencyProperties defined on the UserControl from the surrounding window, and also still use standard Bindings inside the UserControl markup without using to ElementName or RelativeSource bindings.
Colin Ebererhardt explained this here: http://www.scottlogic.com/blog/2012/02/06/a-simple-pattern-for-creating-re-useable-usercontrols-in-wpf-silverlight.html