C# WPF用户控件和名称范围

C# WPF用户控件和名称范围,c#,wpf,xaml,mvvm,scope,C#,Wpf,Xaml,Mvvm,Scope,我一直在玩WPF和MVVM,发现了一件奇怪的事情。在自定义用户控件上使用{Binding ElementName=…}时,用户控件中根元素的名称似乎在使用该控件的窗口中可见。例如,下面是一个示例用户控件: <UserControl x:Class="TryWPF.EmployeeControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:

我一直在玩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>
这里没有什么神秘的。然后,使用该控件的窗口如下所示:

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>
它被实例化并分配给构造函数中的窗口,这是窗口的代码隐藏中唯一的内容:

    public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModel();
    }
这一现象引发了以下问题:

  • 这是故意的吗
  • 如果是这样,使用自定义控件的人如何知道它在内部使用的名称
  • 如果自定义控件中根本不应该使用
    Name
  • 如果是的话,那么有什么替代方案?我在
    FindAncestor
    模式中切换到使用
    {RelativeSource}
    ,这很好,但是有更好的方法吗
  • 这与数据模板定义自己的名称cope这一事实有关系吗?如果我只是重命名主窗口,这样名称就不会与控件冲突,那么它不会阻止我从模板中引用主窗口

  • 在这种情况下,你对工作的困惑是可以理解的

    您的问题很简单,就是您正在对UserControl应用绑定,UserControl是其自身名称范围的“根”(可以这么说)。用户控件和几乎所有容器对象都有自己的名称范围。这些作用域不仅包含子元素,还包含包含名称范围的对象。这就是为什么您可以将
    x:Name=“root”
    应用于窗口,并从子控件中找到它(本例除外)。如果你做不到的话,名字镜将毫无用处

    当您在一个包含名称范围内的名称范围的根上进行操作时,就会出现混淆。您的假设是父级的名称范围具有优先权,但它没有。绑定正在对目标对象调用FindName,在您的情况下,该对象是您的用户控件。(旁注,绑定没有执行jack,实际调用可以在
    ElementObjectRef.GetObject
    中找到,但绑定将调用委托给了它)

    在名称作用域的根上调用
    FindName
    时,只检查在此作用域中定义的名称。不搜索父作用域。(编辑…从第46行开始对源进行更多的读取,我看到算法沿着可视化树向上走,直到找到目标,因此子作用域优先于父作用域)

    所有这一切的结果是,您得到的是用户控件实例,而不是窗口,正如您所希望的那样。现在,回答你的个人问题

    一,。这是故意的吗

    是的。否则,命名空间将无法工作

    二,。如果是这样,使用自定义控件的人如何知道它在内部使用的名称

    理想情况下,你不会。就像你不想知道
    文本框的根的名称一样。有趣的是,当试图修改控件的外观时,了解控件中定义的模板的名称通常很重要

    三,。如果自定义控件中根本不应该使用名称? 如果是的话,那么有什么替代方案?我转而在FindAncestor模式下使用{RelativeSource},这很好,但是有更好的方法吗

    不!很好。使用它。如果您没有与其他人共享您的UserControl,如果遇到此特定问题,请确保更改其名称。如果你没有任何问题,整天重复使用同一个名字,这不会伤害任何东西

    如果您正在共享您的UserControl,您可能应该将其重命名为不会与其他人的姓名冲突的名称。叫它MuhUserControlTypeName\u MuhRoot\u Durr什么的

    四,。如果是的话,那么有什么替代方案?我转而在FindAncestor模式下使用{RelativeSource},这很好,但是有更好的方法吗

    不。只需更改用户控件的
    x:Name
    ,然后继续

    五,。这与数据模板定义自己的名称cope这一事实有关系吗?如果我只是重命名主窗口,这样名称就不会与控件冲突,那么它不会阻止我从模板中引用主窗口


    不,我不这么认为。无论如何,我认为没有什么好的理由

    太宽了?“请添加详细信息,以缩小答案集或隔离可在几段中回答的问题”?我认为有足够的细节,答案可以用几句话来回答,大意是“这是因为foo,do bar和frobz,但从来没有做过bazz,这成为一个非问题”。我正要写一个答案,但你发现不止一个。另外,我无法重现您的显示问题:(我认为)您可以通过替换
    RelativeSource={RelativeSource-AncestorType={x:Type-local:EmployeeControl}}
    来避免ElementName绑定的问题。这五个问题会触发“太宽”在某些人中,@lokusking,答案是关于如何访问父数据上下文,我的问题是为什么它不能以这种特殊方式工作。“您的假设是父数据的名称范围优先”-不,我的假设是,当我使用自定义控件时,它在父控件的作用域内。就像传递给方法调用的实际参数的名称与该方法中的形式参数或局部变量的名称不冲突一样。在名称作用域的根上调用FindName时,只检查在此作用域内定义的名称。不搜索父作用域-你能澄清这一点吗?如果我的用户控件有一个不同的名称,那么
    ElementName=root
    在这个上下文中工作得很好。因此会搜索父范围,但仅当在curr中找不到该名称时
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new ViewModel();
        }