我一直在玩WPF和MVVM并注意到一件奇怪的事情。在自定义用户控件上使用Integer
时,用户控件中的根元素的名称似乎在使用该控件的窗口中可见。比如,这是一个示例用户控件:
{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>
在整个窗口XAML文件中表示相同的内容,因此请参考窗口,而不是用户控件。唉,运行时会打印以下消息:
System.Windows.Data错误:40:BindingExpression路径错误: &#39; DeleteEmployee&#39;在&#39; object&#39;上找不到的属性&#39;&#39;字符串&#39; (的HashCode = -843597893)&#39 ;. BindingExpression:路径= DataContext.DeleteEmployee; 的DataItem =&#39; EmployeeControl&#39; (名称=&#39;根&#39);目标元素是 &#39; EmployeeControl&#39; (名称=&#39;根&#39);目标财产是“删除雇员”&#39; (键入&#39; ICommand&#39;)
root
让我觉得它将DataItem='EmployeeControl' (Name='root')
视为指控制本身。它在ElementName=root
上查找DeleteEmployee
的事实证实了这种怀疑,因为string
正是我设计的虚拟机中的数据上下文。在这里,为了完整起见:
string
它被实例化并分配给构造函数中的窗口,这是窗口代码隐藏的唯一内容:
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
模式下切换到使用{RelativeSource}
,这种方法运行正常,但有更好的方法吗?答案 0 :(得分:2)
在这种情况下,你对wpf namescopes如何运作的困惑是无法理解的。
您的问题只是您正在对UserControl应用绑定,这是&#34; root&#34; (可以这么说)它自己的名称范围。 UserControls和几乎所有容器对象都有自己的名称范围。这些范围不仅包含子元素,还包含包含名称范围的对象。这就是为什么你可以将x:Name="root"
应用到你的窗口并且(在这种情况下除外)从子控件中找到它。如果你无法,那么名称范围将毫无用处。
当你在一个包罗万象的范围内对一个名望镜的根行动时,会出现混乱。你的假设是父母的名称范围优先,但事实并非如此。 Binding在目标对象上调用FindName,在您的情况下是您的用户控件。 (旁注,Binding没有做插孔,实际的通话可以在ElementObjectRef.GetObject
中找到,但是Binding将通话委托给了
当您在名称范围的根目录上调用FindName
时,只会检查此范围内定义的名称。 不搜索父作用域。 (编辑...从第46行开始更多地阅读源http://referencesource.microsoft.com/#PresentationFramework/src/Framework/MS/Internal/Data/ObjectRef.cs,5a01adbbb94284c0我看到算法走向可视树,直到找到目标,因此子范围优先于父范围)
所有这一切的结果是你获得了用户控件实例而不是窗口,就像你希望的那样。现在,回答你的个人问题......
1。这是设计吗?
是的。否则,名称范围不会起作用。
2。如果是这样,那么使用自定义控件的人如何知道它在内部使用的名称?
理想情况下,你不会。就像你不想必须知道TextBox
的根名称一样。有趣的是,在尝试修改它的外观时,了解控件中定义的模板名称通常很重要......
3。如果Name不应该在自定义控件中使用? 如果是这样,那么有哪些替代方案呢?我转而在FindAncestor模式下使用{RelativeSource},这种方式运行正常,但还有更好的方法吗?
没有!没关系。用它。如果您未与其他人共享UserControl,请确保在遇到此特定问题时更改其名称。如果你没有任何问题,整天重复使用相同的名字,它不会伤害任何东西。
如果您要分享您的UserControl ,您应该将其重命名为不会与其他人的名字发生冲突的内容。称之为 MuhUserControlTypeName_MuhRoot_Durr 或其他什么。
4。如果是这样,那么有哪些替代方案呢?我转而在FindAncestor模式下使用{RelativeSource},这种方式运行正常,但还有更好的方法吗?
罗。只需更改用户控件的x:Name
即可继续。
不,我不相信。无论如何,我不认为它有任何充分的理由。5。这是否与数据模板定义自己的名称相关的事实有关?如果我只是重命名它,那么它不会阻止我从模板中引用主窗口,因此名称不会与控件发生冲突。