Ser*_*nov 5 c# wpf xaml scope mvvm
我一直在玩 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>
Run Code Online (Sandbox Code Playgroud)
对我来说看起来很合法。现在,依赖属性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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
这里没有什么神秘的。然后,使用控件的窗口如下所示:
<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>
Run Code Online (Sandbox Code Playgroud)
同样,没什么特别的……除了窗口和用户控件具有相同的名称!但我希望root在整个窗口 XAML 文件中都具有相同的含义,因此指的是窗口,而不是用户控件。唉,当我运行它时会打印以下消息:
System.Windows.Data 错误:40:BindingExpression 路径错误:在“对象”“字符串”(HashCode=-843597893)上找不到“DeleteEmployee”属性。BindingExpression:Path=DataContext.DeleteEmployee; DataItem='EmployeeControl' (Name='root'); 目标元素是 'EmployeeControl' (Name='root'); 目标属性是“DeleteEmployee”(类型“ICommand”)
DataItem='EmployeeControl' (Name='root')让我认为它ElementName=root是指控件本身。它看起来的事实DeleteEmployee就string证实了怀疑,因为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);
}
}
Run Code Online (Sandbox Code Playgroud)
它在构造函数中被实例化并分配给窗口,这是窗口代码隐藏中的唯一内容:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
Run Code Online (Sandbox Code Playgroud)
这种现象提示以下问题:
Name根本不应该用于自定义控件?{RelativeSource}inFindAncestor模式,这工作正常,但有更好的方法吗?小智 5
在这种情况下,您对wpf 名称范围如何工作的困惑是可以理解的。
您的问题很简单,您正在对 UserControl 应用绑定,这是其自己的名称范围的“根”(可以这么说)。用户控件和几乎所有容器对象都有自己的名称范围。这些范围不仅包含子元素,还包含包含名称范围的对象。这就是为什么您可以应用于x:Name="root"窗口并(除了这种情况)从子控件中找到它。如果不能,名称范围就毫无用处。
当您对包含的名称范围内的名称范围的根进行操作时,就会出现混乱。您的假设是父级的名称范围具有优先级,但事实并非如此。Binding 正在目标对象上调用 FindName ,在您的情况下是您的用户控件。 (旁注,绑定不做杰克,实际的调用可以在 中找到ElementObjectRef.GetObject,但这就是绑定将调用委托给的地方)
当您调用FindName名称范围的根时,仅检查此范围内定义的名称。 不搜索父范围。 (编辑...更多地阅读源代码http://referencesource.microsoft.com/#PresentationFramework/src/Framework/MS/Internal/Data/ObjectRef.cs,5a01adbbb94284c0从第 46 行开始我看到算法行走沿着可视化树向上查找,直到找到目标,因此子作用域优先于父作用域)
所有这一切的结果是您获得用户控件实例而不是窗口,就像您希望的那样。现在,回答您的个人问题...
1. 这是设计使然吗?
是的。否则名称范围将无法工作。
2. 如果是这样,使用自定义控件的人如何知道它在内部使用的名称?
理想情况下,你不会。就像您永远不想知道a 的根的名称一样TextBox。但有趣的是,在尝试修改控件的外观时,了解控件中定义的模板的名称通常很重要......
3. 如果Name根本不应该在自定义控件中使用?如果是这样,那么有哪些替代方案?我改用 FindAncestor 模式下的 {RelativeSource},效果很好,但是有更好的方法吗?
不!没关系。用它。如果您不与其他人共享您的 UserControl,请确保在遇到此特定问题时更改其名称。如果你没有任何问题,整天重复使用相同的名字,这不会有什么坏处。
如果您正在共享您的 UserControl,您可能应该将其重命名为不会与其他人的名称冲突的名称。称之为MuhUserControlTypeName_MuhRoot_Durr或其他名称。
4. 如果是这样,那么有哪些替代方案?我改用 FindAncestor 模式下的 {RelativeSource},效果很好,但是有更好的方法吗?
不。只需更改x:Name您的用户控件并继续。
5. 这与数据模板定义自己的名称对应有什么关系吗?如果我只是重命名它,这样名称就不会与控件冲突,这并不能阻止我从模板中引用主窗口。
不,我不这么认为。无论如何,我认为没有任何充分的理由。
| 归档时间: |
|
| 查看次数: |
1859 次 |
| 最近记录: |