How do I Bind WPF Commands between a UserControl and a parent Window

42,305

Solution 1

This is trivial, and made so by treating your UserControl like what it is--a control (that just happens to be made up from other controls). What does that mean? It means you should place DependencyProperties on your UC to which your ViewModel can bind, like any other control. Buttons expose a Command property, TextBoxes expose a Text property, etc. You need to expose, on the surface of your UserControl, everything you need for it to do its job.

Let's take a trivial (thrown together in under two minutes) example. I'll leave out the ICommand implementation.

First, our Window

<Window x:Class="UCsAndICommands.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:t="clr-namespace:UCsAndICommands"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <t:ViewModel />
    </Window.DataContext>
    <t:ItemsEditor Items="{Binding Items}"
                   AddItem="{Binding AddItem}"
                   RemoveItem="{Binding RemoveItem}" />
</Window>

Notice we have our Items editor, which exposes properties for everything it needs--the list of items it is editing, a command to add a new item, and a command to remove an item.

Next, the UserControl

<UserControl x:Class="UCsAndICommands.ItemsEditor"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:t="clr-namespace:UCsAndICommands"
             x:Name="root">
    <UserControl.Resources>
        <DataTemplate DataType="{x:Type t:Item}">
            <StackPanel Orientation="Horizontal">
                <Button Command="{Binding RemoveItem, ElementName=root}"
                        CommandParameter="{Binding}">Remove</Button>
                <TextBox Text="{Binding Name}" Width="100"/>
            </StackPanel>
        </DataTemplate>
    </UserControl.Resources>
    <StackPanel>
        <Button Command="{Binding AddItem, ElementName=root}">Add</Button>
        <ItemsControl ItemsSource="{Binding Items, ElementName=root}" />
    </StackPanel>
</UserControl>

We bind our controls to the DPs defined on the surface of the UC. Please, don't do any nonsense like DataContext=this; as this anti-pattern breaks more complex UC implementations.

Here's the definitions of these properties on the UC

public partial class ItemsEditor : UserControl
{
    #region Items
    public static readonly DependencyProperty ItemsProperty =
        DependencyProperty.Register(
            "Items",
            typeof(IEnumerable<Item>),
            typeof(ItemsEditor),
            new UIPropertyMetadata(null));
    public IEnumerable<Item> Items
    {
        get { return (IEnumerable<Item>)GetValue(ItemsProperty); }
        set { SetValue(ItemsProperty, value); }
    }
    #endregion  
    #region AddItem
    public static readonly DependencyProperty AddItemProperty =
        DependencyProperty.Register(
            "AddItem",
            typeof(ICommand),
            typeof(ItemsEditor),
            new UIPropertyMetadata(null));
    public ICommand AddItem
    {
        get { return (ICommand)GetValue(AddItemProperty); }
        set { SetValue(AddItemProperty, value); }
    }
    #endregion          
    #region RemoveItem
    public static readonly DependencyProperty RemoveItemProperty =
        DependencyProperty.Register(
            "RemoveItem",
            typeof(ICommand),
            typeof(ItemsEditor),
            new UIPropertyMetadata(null));
    public ICommand RemoveItem
    {
        get { return (ICommand)GetValue(RemoveItemProperty); }
        set { SetValue(RemoveItemProperty, value); }
    }        
    #endregion  
    public ItemsEditor()
    {
        InitializeComponent();
    }
}

Just DPs on the surface of the UC. No biggie. And our ViewModel is similarly simple

public class ViewModel
{
    public ObservableCollection<Item> Items { get; private set; }
    public ICommand AddItem { get; private set; }
    public ICommand RemoveItem { get; private set; }
    public ViewModel()
    {
        Items = new ObservableCollection<Item>();
        AddItem = new DelegatedCommand<object>(
            o => true, o => Items.Add(new Item()));
        RemoveItem = new DelegatedCommand<Item>(
            i => true, i => Items.Remove(i));
    }
}

You are editing three different collections, so you may want to expose more ICommands to make it clear which you are adding/removing. Or you could cheap out and use the CommandParameter to figure it out.

Solution 2

Refer the below code. UserControl.XAML

<Grid>
    <ListBox ItemsSource="{Binding Things}" x:Name="lst">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding ThingName}" Margin="3"/>
                    <Button Content="Remove" Margin="3" Command="{Binding ElementName=lst, Path=DataContext.RemoveCommand}" CommandParameter="{Binding}"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

Window.Xaml

<Window x:Class="MultiBind_Learning.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MultiBind_Learning"
    Title="Window1" Height="300" Width="300">
<StackPanel Orientation="Horizontal">
    <Button Content="Add" Width="50" Height="25" Command="{Binding AddCommnd }"/>
    <local:UserControl2/>
</StackPanel>

Window.xaml.cs

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        this.DataContext = new ThingViewModel();
    }
}

ThingViewModel.cs

  class ThingViewModel
{
    private ObservableCollection<Thing> things = new ObservableCollection<Thing>();

    public ObservableCollection<Thing> Things
    {
        get { return things; }
        set { things = value; }
    }

    public ICommand AddCommnd { get; set; }
    public ICommand RemoveCommand { get; set; }

    public ThingViewModel()
    {
        for (int i = 0; i < 10; i++)
        {
            things.Add(new Thing() { ThingName="Thing" +i});
        }

        AddCommnd = new BaseCommand(Add);
        RemoveCommand = new BaseCommand(Remove);
    }

    void Add(object obj)
    {
      things.Add(new Thing() {ThingName="Added New" });
    }

    void Remove(object obj)
    {
      things.Remove((Thing)obj);
    }
}

Thing.cs

class Thing :INotifyPropertyChanged
{
    private string thingName;

    public string ThingName
    {
        get { return thingName; }
        set { thingName = value; OnPropertyChanged("ThingName"); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
}

BaseCommand.cs

public class BaseCommand : ICommand
{
    private Predicate<object> _canExecute;
    private Action<object> _method;
    public event EventHandler CanExecuteChanged;

    public BaseCommand(Action<object> method)
    {
        _method = method;            
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        _method.Invoke(parameter);
    }
}

Instead of Base command you can try RelayCommand from MVVMLight or DelegateCommand from PRISM libraries.

Solution 3

By default, your user control will inherit the DataContext of its container. So the ViewModel class that your window uses can be bound to directly by the user control, using the Binding notation in XAML. There's no need to specify DependentProperties or RoutedEvents, just bind to the command properties as normal.

Share:
42,305

Related videos on Youtube

Todd Sprang
Author by

Todd Sprang

Hands-on Technical Manager and Software Architect, focused on Microsoft tech stacks since 1999. Expertise in Azure, .NET Core, C#, WCF, ASP.NET MVC, Angular, WPF, MSSQL. Proponent and practitioner of iDesign's Method to SOA. When not working or learning something new/fun, I'm probably riding a bike. "It’s not about writing more code; it’s about writing the right code. You become a 10x programmer not by doing an order of magnitude more work, but by making better decisions an order of magnitude more often." - Yevgeniy Brikman

Updated on May 18, 2020

Comments

  • Todd Sprang
    Todd Sprang almost 4 years

    I'l start by letting a picture do some talking.

    MVVM User Control to Window wireframe

    So you see, I want to create a WPF user control that supports binding to a parent window's DataContext. The user control is simply a Button and a ListBox with a custom ItemTemplate to present things with a Label and a Remove Button.

    The Add button should call an ICommand on the main view model to interact with the user in selecting a new thing (instance of IThing). The Remove buttons in the ListBoxItem in the user control should similarly call an ICommand on the main view model to request the related thing's removal. For that to work, the Remove button would have to send some identifying information to the view model about the thing requesting to be removed. So there are 2 types of Command that should be bindable to this control. Something like AddThingCommand() and RemoveThingCommand(IThing thing).

    I got the functionality working using Click events, but that feels hacky, producing a bunch of code behind the XAML, and rubs against the rest of the pristine MVVM implementation. I really want to use Commands and MVVM normally.

    There's enough code involved to get a basic demo working, I am holding off on posting the whole thing to reduce confusion. What is working that makes me feel like I'm so close is the DataTemplate for the ListBox binds the Label correctly, and when the parent window adds items to the collection, they show up.

    <Label Content="{Binding Path=DisplayName}" />
    

    While that displays the IThing correctly, the Remove button right next to it does nothing when I click it.

    <Button Command="{Binding Path=RemoveItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
    

    This isn't terribly unexpected since the specific item isn't provided, but the Add button doesn't have to specify anything, and it also fails to call the command.

    <Button Command="{Binding Path=AddItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
    

    So what I need is the "basic" fix for the Add button, so that it calls the parent window's command to add a thing, and the more complex fix for the Remove button, so that it also calls the parent command but also passes along its bound thing.

    Many thanks for any insights,

    • Admin
      Admin about 9 years
      Add two ICommand properties on the surface of the UserControl. Bind the buttons within the control to these properties. Bind the properties to the Add and Remove ICommands to your window's view model. Done and done.
    • Todd Sprang
      Todd Sprang about 9 years
      Thanks, Will. I'm working with Ganesh's solution and am having difficulties still. I will try your proposal and let you know.
  • Todd Sprang
    Todd Sprang about 9 years
    Thanks for this detailed response. On first read it does look like it gives me the pattern I need to get this sorted out. Let me try it out and I'll award the answer shortly. Thanks!
  • Todd Sprang
    Todd Sprang about 9 years
    While I'm not sure I'd call the solution "trivial" :) you explained it perfectly and your solution "just worked". I see that I was making some of it more complicated than necessary, and I was missing a number of little things that all added up to getting nowhere. Many thanks for the lucid response. I'm in MVVM dreamland again!
  • Sergei Tachenov
    Sergei Tachenov almost 8 years
    It's probably worth noting that using x:Name="root' inside a custom control may be a bad idea because if the containing window uses the same name, things may get rather ugly (I just had a user control that bound a property to itself because the control had internally the same name as the window). Better to use a unique name like itemsEditorRoot, or maybe even better, use something like {RelativeSource Mode=FindAncestor, AncestorType={x:Type t:ItemsEditor}}.
  • Admin
    Admin almost 8 years
    @SergeyTachenov I wonder how that could happen, as names are definitively scoped within their containers. I do this all the time and I always use "root" as the name of the root element within a certain scope. I've never had any issue whatsoever with it.
  • Sergei Tachenov
    Sergei Tachenov almost 8 years
    I'm not overly familiar with WPF, but that definitely happened. And just renaming root to mainWindow (both in x:Name and ElementName) fixed the problem immediately. I'll investigate it further and maybe post a new question if there is indeed a contradiction between theoretical scoping and real life. Or, if there is no real problem, except for my misunderstanding, maybe I'll just post a self-answered question anyway.
  • Sergei Tachenov
    Sergei Tachenov almost 8 years
    OK, I give up. It doesn't make any sense to me. Here it is the question.
  • ktingle
    ktingle almost 7 years
    I think this answer is simple and covers most of the folks trying to just 'group some controls together in a control' for the sake of reuse. Thank you
  • user3668129
    user3668129 over 6 years
    Thank you Will for a very helpfull answer. Can you please give the same example for other control that "Command" is not a property of it?( Button has Command property but what if i am using combobox?) How to do the binding ? thank you.
  • Admin
    Admin over 6 years
    @user3668129 not sure why you'd want a command binding on a combobox. binding to SelectedItem is usually good enough. If not, create a UserControl, expose DependencyProperties on the surface for whatever you need, including the ICommand, and then add logic to the codebehind to manage the interaction.
  • TBG
    TBG over 3 years
    Thank you for that answer ! My friend's project was missing the 'x:Name' and 'ElementName=root' in Binding, pay attention to that ! (I understand that the elements were then bound to the UserControl, not the Button inside it)