WPF用户控件和名称范围

时间:2016-07-27 17:47:59

标签: c# wpf xaml mvvm scope

我一直在玩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);
    }
}

这种现象引发了以下问题:

  1. 这是设计吗?
  2. 如果是这样,那么使用自定义控件的人如何知道它在内部使用的名称?
  3. 如果 public MainWindow() { InitializeComponent(); DataContext = new ViewModel(); } 根本不应该在自定义控件中使用?
  4. 如果是这样,那么有什么选择呢?我在Name模式下切换到使用{RelativeSource},这种方法运行正常,但有更好的方法吗?
  5. 这是否与数据模板定义自己的名称相关的事实有关?如果我只是重命名它,那么它不会阻止我从模板中引用主窗口,因此名称不会与控件发生冲突。

1 个答案:

答案 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。这是否与数据模板定义自己的名称相关的事实有关?如果我只是重命名它,那么它不会阻止我从模板中引用主窗口,因此名称不会与控件发生冲突。

不,我不相信。无论如何,我不认为它有任何充分的理由。