2
votes

i'm trying to understand how Routed Commands works, but I've got a problem. I've created a Main Window with Button and ItemControl with UserControls as its Item template.

<Window>
<Grid>
<ItemsControl
            ItemsSource="{Binding CollectionOfUsers}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <uc:UserUserControl
                        Name="{Binding PersonName}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
<Button
      Command="{x:Static helpers:RoutedCommands.SendChangesCommand}"
      Content="SAVE"/>
</Grid>
</Window>

If button in Main Window is Clicked I want to run some method from every UserControl in ItemsControl.

I've created RoutedCommand in static class:

public static class RoutedCommands
{
    public static readonly RoutedCommand SendChangesCommand = new RoutedCommand(); 
}

And bound UserControl to RoutedCommand.

<UserControl.CommandBindings>
    <CommandBinding Command="{x:Static helpers:RoutedCommands.SendChangesCommand}"
                    Executed="CommandBinding_Executed"/>

With method in code-behind:

private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
      // Do something
}

Thought It will fire method on every User Control object when I click the button, but sadly this code doesn't work - button is disabled. What am I missing?

2

2 Answers

1
votes

Thought It will fire method on every User Control object when I click the button, but sadly this code doesn't work - button is disabled. What am I missing?

A RoutedCommand searches the visual tree from the target element, i.e. the element that invokes the command which is your Button in this case, and up for an element that has a matching CommandBinding object in its CommandBindings collection and then executes the Execute delegate for this particular CommandBinding.

The issue here is that the UserControl elements in the ItemsControl are not visual parents of the Button so the command will never be invoked.

If you move the CommandBinding to the parent Grid, the Button will be enabled:

<Grid>
    <Grid.CommandBindings>
        <CommandBinding Command="{x:Static local:RoutedCommands.SendChangesCommand}"
            Executed="CommandBinding_Executed"/>
    </Grid.CommandBindings>
    <ItemsControl
    ...

If you really want to do something with the UserControls when the Button is clicked, using a RoutedCommand is not the way to do this.

Instead you should look into the MVVM design pattern and how to implement your custom ICommand or use a common implementation from any of the MVVM libraries out there. This is another story though but please refer to the following blog post for a brief introduction: https://blog.magnusmontin.net/2013/06/30/handling-events-in-an-mvvm-wpf-application/.

MVVM is the recommended design pattern to use when building XAML based applications and all serious WPF developers should learn it.

0
votes

You can/should send the ItemsSource of the ItemsControl as the CommandParameter of the Button, and do what ever you like with those Items (Models). With proper Bindings, you can see the results in the UserControls (View). Here an example:

Assuming that this is the RoutedCommands:

public static class RoutedCommands
{
    public static readonly RoutedCommand SendChangesCommand = new RoutedCommand();

    public static void CanExecuteSendChangesCommand(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
    }

    public static void ExecutedSendChangesCommand(object sender, ExecutedRoutedEventArgs e)
    {
       // Use  e.Parameter;
    }
}

And the command is added to the CommandBindings in your application, for example:

    public MainWindow()
    {
        InitializeComponent();
        CommandBindings.Add(new CommandBinding(RoutedCommands.SendChangesCommand,                       RoutedCommands.ExecutedSendChangesCommand,RoutedCommands.CanExecuteSendChangesCommand));
    }

you can bind the Command parameter in something like this:

<DockPanel>
    <Button DockPanel.Dock="Top" Content="Click Me" 
            Command="{x:Static local:RoutedCommands.SendChangesCommand}"
            CommandParameter="{Binding ElementName=ic, Path=ItemsSource}"/>
    <ItemsControl Name="ic" ItemsSource="{Binding CollectionOfUsers}">
        //...
    </ItemsControl>
</DockPanel>