Search code examples
c#wpfxamlroutedcommand

Setting Command Target in XAML


I am having a hard time understanding the CommandTarget property for a RoutedCommand.

Basically, I have some static commands that have implementations in a user control (not the window). I create a commandbinding in the user control. If I declare the button in the usercontrol, then I am able to use my routed event. However, when the button is outside of the usercontrol, then I cannot use my routed event. I think the command target will solve my issue.

So how do I set the commandtarget for the toolbar usercontrol's button, so that the Container's Executed and CanExecuted is called?

Edited Code with changes from micahtan changes, but I still can't get it to CanExecute or Execute.

Window XAML:

<Window x:Class="RoutedCommands.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:RoutedCommands"
    xmlns:toolbar="clr-namespace:RoutedCommands.Toolbar"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <local:Container Width="100" Height="25" x:Name="MyContainer" />
        <toolbar:Toolbar Width="100" Height="25" CommandTarget="{Binding MyContainer}" />
    </StackPanel>
</Window>

Toolbar XAML:

<UserControl x:Class="RoutedCommands.Toolbar.Toolbar"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:RoutedCommands"
    x:Name="MyToolbar"
    Height="300" Width="300">
    <Grid>
        <Button Command="{x:Static local:Commands.MyCommand}" Content="Try Me" CommandTarget="{Binding ElementName=MyToolbar, Path=CommandTarget, Mode=OneWay}" />
    </Grid>
</UserControl>

Toolbar CS:

    public partial class Toolbar : UserControl
    {
        public Toolbar()
        {
            InitializeComponent();
        }

        // Using a DependencyProperty as the backing store for CommandTarget.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CommandTargetProperty =
                DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(Toolbar), new UIPropertyMetadata(null));

        public IInputElement CommandTarget
        {
            get { return (IInputElement)GetValue(CommandTargetProperty); }
            set { SetValue(CommandTargetProperty, value); }
        }
    }

Container XAML:

<UserControl x:Class="RoutedCommands.Container"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:RoutedCommands"
    Height="300" Width="300">
    <UserControl.CommandBindings>
        <CommandBinding Command="{x:Static local:Commands.MyCommand}" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed" />
    </UserControl.CommandBindings>
    <Grid>
        <Button Command="{x:Static local:Commands.MyCommand}" Content="Click Me" />
    </Grid>
</UserControl>

Container CS:

public partial class Container : UserControl
{
    public Container()
    {
        InitializeComponent();
    }

    private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        Console.WriteLine("My Command Executed");
    }

    private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        Console.WriteLine("My Command Can Execute");
        e.CanExecute = true;
    }
}

RoutedCommands:

namespace RoutedCommands
{
    public static class Commands
    {
        public static readonly RoutedUICommand MyCommand = new RoutedUICommand(); 
    }
}

Solution

  • If you want to use CommandTargets, I would create a CommandTarget DependencyProperty on your custom UserControl, similar to the way it's defined on ButtonBase.

    After doing that, set your Button's CommandTarget to your custom UserControl's CommandTarget.

    EDIT: Code Sample

    Rudi's comments are valid if you're doing an MVVM architecture -- RelayCommands or some other form of wrapped delegates work well in that case. Based on your code sample, it didn't look like you were using that approach, hence my original comment.

    As for the code, you only need to change your ToolBar class. This assumes your MyCommand class inherits from RoutedUICommand. Here's the XAML:

    <UserControl
        x:Class="WPFCommandTarget.CustomToolBar"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WPFCommandTarget"
        x:Name="theControl">
        <Grid>
            <Button
                x:Name="theButton"
                Command="{x:Static local:Commands.MyCommand}"
                CommandTarget="{Binding ElementName=theControl, Path=CommandTarget, Mode=OneWay}"
                Content="Try Me" />
        </Grid>
    </UserControl>
    

    And here's the code-behind:

    using System.Windows; using System.Windows.Controls;

    namespace WPFCommandTarget
    {
        /// <summary>
        /// Interaction logic for CustomToolBar.xaml
        /// </summary>
        public partial class CustomToolBar : UserControl
        {
            public CustomToolBar()
            {
                InitializeComponent();
            }
    
            public IInputElement CommandTarget
            {
                get { return (IInputElement)GetValue(CommandTargetProperty); }
                set { SetValue(CommandTargetProperty, value); }
            }
    
            // Using a DependencyProperty as the backing store for CommandTarget.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty CommandTargetProperty =
                DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(CustomToolBar), new UIPropertyMetadata(null));
        }
    }
    

    Please note that I've changed some of the class names/namespaces in my test project. You'll have to change them to suit your needs.