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:
- Is this by design?
- If so, how is someone using a custom control supposed to know what name it uses internally?
- 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}
inFindAncestor
mode, which is working fine, but are there better ways? - 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.