6

I've been playing around with WPF and MVVM and noticed a strange thing. When using {Binding ElementName=...} on a custom user control, the name of the root element within the user control seems to be visible in the window using the control. Say, here is an example user control:

<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>

Looks pretty legit to me. Now, the dependency property DeleteEmployee is defined in the code-behind, like this:

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);
        }
    }
}

Nothing mysterious here. Then, the window using the control looks like this:

<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>

Again, nothing fancy... except the fact that both the window and the user control have the same name! But I'd expect root to mean the same thing throughout the whole window XAML file, and therefore refer to the window, not to the user control. Alas, the following message is printed when I run it:

System.Windows.Data Error: 40 : BindingExpression path error: 'DeleteEmployee' property not found on 'object' ''String' (HashCode=-843597893)'. BindingExpression:Path=DataContext.DeleteEmployee; DataItem='EmployeeControl' (Name='root'); target element is 'EmployeeControl' (Name='root'); target property is 'DeleteEmployee' (type 'ICommand')

DataItem='EmployeeControl' (Name='root') makes me think that it treats ElementName=root as referring to the control itself. The fact that it looks for DeleteEmployee on string confirms that suspicion because string is exactly what the data context is in my contrived VM. Here it is, for the sake of completeness:

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);
    }
}

It is instantiated and assigned to the window in the constructor, which is the only thing in code-behind for the window:

    public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModel();
    }

This phenomenon prompts the following questions:

  1. Is this by design?
  2. If so, how is someone using a custom control supposed to know what name it uses internally?
  3. If Name is not supposed to be used in custom control at all?
  4. If so, then what are the alternatives? I switched to using {RelativeSource} in FindAncestor mode, which is working fine, but are there better ways?
  5. Does this have anything to do with the fact that data templates define their own names copes? It doesn't stop me from referring to the main window from within a template if I just rename it so the name doesn't clash with the control.
Sergei Tachenov
  • 24,345
  • 8
  • 57
  • 73
  • 2
    Too broad? “Please add details to narrow the answer set or to isolate an issue that can be answered in a few paragraphs”? I think there are enough details and the answer could be in a few sentences, along the lines of “This is because of foo, do bar and frobz, but never do bazz and this becomes a non-issue”. – Sergei Tachenov Jul 27 '16 at 18:19
  • I was about to write an answer but [here](http://stackoverflow.com/questions/3404707/access-parent-datacontext-from-datatemplate) you find more than one. Also, i cannot reproduce your Display-Issue :( – lokusking Jul 27 '16 at 18:25
  • 1
    You can (I think) avoid the ElementName binding thing by substituting `RelativeSource={RelativeSource AncestorType={x:Type local:EmployeeControl}}` instead. – 15ee8f99-57ff-4f92-890c-b56153 Jul 27 '16 at 18:29
  • The five questions thing triggers "too broad" in some people. –  Jul 27 '16 at 18:36
  • 1
    @lokusking, that answer is about *how* to access parent data context, my question is about *why* it doesn't work this particular way. – Sergei Tachenov Jul 27 '16 at 19:00
  • @Ed, that's exactly what I did. I just wanted to know why the names conflict. – Sergei Tachenov Jul 27 '16 at 19:01

1 Answers1

5

Your confusion here about how wpf namescopes work is understanable in this situation.

Your issue is simply that you are applying a binding upon a UserControl, which is the "root" (so to speak) of its own namescope. UserControls, and pretty much any container objects, have their own namescopes. These scopes encompass not only child elements, but the object that contains the namescope as well. This is why you can apply x:Name="root" to your window and (except in this one case) locate it from a child control. If you couldn't, namescopes would be pretty much useless.

The confusion comes when you're acting upon a root of a namescope within an encompassing namescope. Your assumption was that the parent's namescope had precedence, but it does not. The Binding is calling FindName on the target object, which in your case is your user control. (Side note, the Binding isn't doing jack, the actual calls can be found in ElementObjectRef.GetObject, but that's where the Binding delegates the call to)

When you call FindName on the root of a namescope, only names defined within this scope are examined. Parent scopes are not searched. (Edit... a bit more reading of the source http://referencesource.microsoft.com/#PresentationFramework/src/Framework/MS/Internal/Data/ObjectRef.cs,5a01adbbb94284c0 starting at line 46 I see that the algorithm walks up the visual tree until it finds a target, so child scopes have precedence over parent scopes)

The result of all this is that you get the user control instance instead of the window, like you were hoping. Now, to answer your individual questions...

1. Is this by design?

Yep. Otherwise namescopes wouldn't work.

2. If so, how is someone using a custom control supposed to know what name it uses internally?

Ideally, you wouldn't. Just like you don't ever want to have to know the name of the root of a TextBox. Interestingly, though, knowing the names of templates defined within a control is often important when attempting to modify it's look and feel...

3. If Name is not supposed to be used in custom control at all? If so, then what are the alternatives? I switched to using {RelativeSource} in FindAncestor mode, which is working fine, but are there better ways?

No! It's fine. Use it. If you aren't sharing your UserControl with other people, just make sure to change its name if you are experiencing this particular problem. If you aren't having any problem, reuse the same name all day, it isn't hurting anything.

If you ARE sharing your UserControl, you should probably rename it to something that won't conflict with other people's names. Call it MuhUserControlTypeName_MuhRoot_Durr or something.

4. If so, then what are the alternatives? I switched to using {RelativeSource} in FindAncestor mode, which is working fine, but are there better ways?

Nah. Just change the x:Name of your user control and move on.

5. Does this have anything to do with the fact that data templates define their own names copes? It doesn't stop me from referring to the main window from within a template if I just rename it so the name doesn't clash with the control.

No, I don't believe so. I don't think there is any good reason for it to be, anyhow.

  • 1
    “ Your assumption was that the parent's namescope had precedence”—no, my assumption was that when I use a custom control, it's within the parent's scope. Just like the names of *actual* parameters passed to a method call don't conflict with the names of *formal* parameters or local variables inside that method. – Sergei Tachenov Jul 27 '16 at 19:04
  • “When you call FindName on the root of a namescope, only names defined within this scope are examined. Parent scopes are not searched.”—could you clarify this point? If my user control has a different name, then `ElementName=root` works just fine in this context. So the parent scope *is* searched, but only if the name is not found within the current scope, right? Just like a field is only accessible if it's not shadowed by a local variable or something. – Sergei Tachenov Jul 27 '16 at 19:07
  • Edited. If you follow the link, you can see the actual algorithm. I missed the part where it walks up the visual tree until it finds a candidate. Anyhow, it starts at the element on which the binding is placed, so if that has a namescope, that namescope is examined first, then its parent's namescope, etc. –  Jul 28 '16 at 12:57