views:

270

answers:

2

I have a UserControl that adds a CommandBinding to it's CommandBindings collection to handle a specific Command. Later I use this control in a window and want to add another binding to that same control to add additional behavior. The problem though, is that when I do this it seems that when I add another CommandBinding to the CommandBindings collection of a control that it replaces any binding that was already made for the same Command. So what it seems like is that a control can only have a single CommandBinding per control, is this correct?

Please see the code example below which attempts to set two CommandBindings for the same Save Command.

<Window x:Class="MultipleCommandBindings.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.CommandBindings>
    <CommandBinding Command="Save"
                    Executed="CommandBinding_Executed" />
    <CommandBinding Command="Save"
                    Executed="CommandBinding_Executed" />
</Window.CommandBindings>
<Grid>
    <Button Height="23"
            HorizontalAlignment="Right"
            Margin="0,0,25,88"
            Name="button1"
            VerticalAlignment="Bottom"
            Width="75"
            Command="Save">Button</Button>
</Grid>

Originally I was expecting either a compile-time or runtime exception when wrote this code but was surprised that it didn't complain. Next though I was disappointed since my CommandBinding_Executed handler only gets called once instead of twice as I was hoping.

Update: After a bit of testing it appears that my second CommandBinding is not overwriting my first one but instead it appears that even though I'm not setting Handled to true in my event handler that the first command binding swallows up the Command. I'm pretty sure at this point that the solution to my problem is to understand why the routed command is not propagating past the first handler even when Handled is not set to true.

Update: I've found this great little tidbit of information which just confirms some of the strange behavior behind Command routing in WPF.

Update: One thought about how to work around the fact that it appears that there can only be a single effective CommandBinding per command is that it appears that the default CommandBinding class exposes Executed and CanExecute as events which of course like all events can have multiple handlers. The idea then is to have some other way than the standard CommandBindings.Add method to add additional handlers to a command. Maybe this could be done via an extension method on the Control class and conjunction with a custom CompositeCommandBinding class which allows us to aggregate multiple bindings within one main binding.

+1  A: 

Hi J,

I don't know the answer to your question, but using Reflector sounds reasonable to me.

I'm wondering why are you doing this? Maybe it makes more sense to use Composite pattern to aggregate behaviors, rather then trying to combine command bindings?

Anvaka
+1 for the notion that you should have only 1 binding in the view and let multiple behaviors be invoked further down the stack.
Curt Nichols
Well, in my case I have a User Control that can be the source of several different types of commands that get propagated up that come from an internal control. Within the user control I want to be aware of when any of these commands are issued so that I can have my ViewModel initiate some other processes of which the end result will be exposed through my UserControl (View). I can bind to the property that I'm exposing and see when it changed in oder to know when it is ready to use but I look the information about the actual Command type that started the whole process.
jpierson
By the way, I thought I would also mention that the need to go about things this way is that WPF dependency properties generally seem to work more off of a push concept instead of a pull, so I can't defer my process until it is actually requested but instead I have to subscribe to it via binding and wait for that value actually to be computed and change. So in the example I provided pretend that I want to package up some object to be able to serialize it by handling the Save command internally but then want to actually open up a file dialog and do the actual saving external from my Control.
jpierson
+1  A: 

So far I've only been able to come up with a workaround for this problem which is to handle the command at two different levels in the logical tree. In the example below I handle the Save command within my grid and then also again within the Window element.

<Window x:Class="MultipleCommandBindings.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.CommandBindings>
    <CommandBinding Command="Save"
                    Executed="CommandBinding_Executed2"/>
</Window.CommandBindings>
<Grid>
    <Grid.CommandBindings>
        <CommandBinding Command="Save"
                        Executed="CommandBinding_Executed1" />
    </Grid.CommandBindings>

    <Button Height="23"
            HorizontalAlignment="Right"
            Margin="0,0,25,88"
            Name="button1"
            VerticalAlignment="Bottom"
            Width="75"
            Command="Save">Button</Button>
</Grid>

In order to get this to work my code behind needs to also manually propagate the Command execution to the next level up.

    private void CommandBinding_Executed1(object sender, ExecutedRoutedEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Executed 1!");
        var command = e.Command as RoutedUICommand;
        command.Execute(e.Parameter, this);
    }

    private void CommandBinding_Executed2(object sender, ExecutedRoutedEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Executed 2!");
    }

If anybody has any better ideas on how I can monitor a command still let it propagate naturally up the tree I would love to see it otherwise I might just resort to this workaround.

jpierson