【发布时间】:2016-07-27 17:47:59
【问题描述】:
我一直在使用 WPF 和 MVVM 并注意到一个奇怪的事情。在自定义用户控件上使用{Binding ElementName=...} 时,用户控件中根元素的名称似乎在使用该控件的窗口中可见。比如说,这是一个用户控件示例:
<UserControl x:Class="TryWPF.EmployeeControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TryWPF"
Name="root">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding}"/>
<Button Grid.Column="1" Content="Delete"
Command="{Binding DeleteEmployee, ElementName=root}"
CommandParameter="{Binding}"/>
</Grid>
</UserControl>
对我来说看起来很合法。现在,在代码隐藏中定义了依赖属性DeleteEmployee,如下所示:
public partial class EmployeeControl : UserControl
{
public static DependencyProperty DeleteEmployeeProperty
= DependencyProperty.Register("DeleteEmployee",
typeof(ICommand),
typeof(EmployeeControl));
public EmployeeControl()
{
InitializeComponent();
}
public ICommand DeleteEmployee
{
get
{
return (ICommand)GetValue(DeleteEmployeeProperty);
}
set
{
SetValue(DeleteEmployeeProperty, value);
}
}
}
这里没有什么神秘的。然后,使用该控件的窗口如下所示:
<Window x:Class="TryWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TryWPF"
Name="root"
Title="Try WPF!" Height="350" Width="525">
<StackPanel>
<ListBox ItemsSource="{Binding Employees}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<local:EmployeeControl
HorizontalAlignment="Stretch"
DeleteEmployee="{Binding DataContext.DeleteEmployee, ElementName=root}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
再一次,没什么特别的...除了窗口和用户控件具有相同的名称!但我希望root 在整个窗口 XAML 文件中都表示相同的意思,因此指的是窗口,而不是用户控件。唉,当我运行它时会打印以下消息:
System.Windows.Data 错误:40:BindingExpression 路径错误: 在“对象”“字符串”上找不到“DeleteEmployee”属性 (哈希码=-843597893)'。 BindingExpression:Path=DataContext.DeleteEmployee; DataItem='EmployeeControl' (Name='root');目标元素是 'EmployeeControl'(名称='root');目标属性是“DeleteEmployee” (输入“ICommand”)
DataItem='EmployeeControl' (Name='root') 让我觉得它将ElementName=root 视为指的是控件本身。它在string 上查找DeleteEmployee 的事实证实了这种怀疑,因为string 正是我设计的VM 中的数据上下文。为了完整起见,这里是:
class ViewModel
{
public ObservableCollection<string> Employees { get; private set; }
public ICommand DeleteEmployee { get; private set; }
public ViewModel()
{
Employees = new ObservableCollection<string>();
Employees.Add("e1");
Employees.Add("e2");
Employees.Add("e3");
DeleteEmployee = new DelegateCommand<string>(OnDeleteEmployee);
}
private void OnDeleteEmployee(string employee)
{
Employees.Remove(employee);
}
}
它在构造函数中被实例化并分配给窗口,这是窗口代码隐藏中唯一的东西:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
这种现象提示以下问题:
- 这是设计使然吗?
- 如果是这样,使用自定义控件的人应该如何知道它在内部使用的名称?
- 如果
Name根本不应该用于自定义控件? - 如果是这样,那么有哪些替代方案?我切换到在
FindAncestor模式下使用{RelativeSource},效果很好,但有更好的方法吗? - 这是否与数据模板定义自己的名称对应的事实有关?如果我只是重命名它,它不会阻止我从模板中引用主窗口,这样名称就不会与控件发生冲突。
【问题讨论】:
-
太宽泛了? “请添加细节以缩小答案范围或隔离可以在几段中回答的问题”?我认为有足够的细节,答案可以用几句话来回答,就像“这是因为 foo,做 bar 和 frobz,但永远不要做 bazz,这不是问题”。
-
我正要写一个答案,但here你发现不止一个。另外,我无法重现您的显示问题:(
-
你可以(我认为)通过替换
RelativeSource={RelativeSource AncestorType={x:Type local:EmployeeControl}}来避免 ElementName 绑定的事情。 -
这五个问题对某些人来说触发了“太宽泛”。
-
@lokusking,这个答案是关于 如何 访问父数据上下文,我的问题是关于 为什么 它不能以这种特定方式工作。