开发者

WPF Commands, How to declare Application level commands?

开发者 https://www.devze.com 2023-02-05 13:00 出处:网络
I\'m interested in creating commands that are available from anywhere in my WPF application. I\'d like them to work in the same way as Cut, Copy, Paste and the other Application level commands, ie:

I'm interested in creating commands that are available from anywhere in my WPF application.

I'd like them to work in the same way as Cut, Copy, Paste and the other Application level commands, ie:

<Button Command="Paste" />

I assumed I could setup CommandBindings for t开发者_开发知识库he Application instance, but that property isn't available.

How is this done?

The best I have managed so far is to create a suite of commands on the top level window and then access them like this...:

<Button Command="{x:Static namespace::MainWindow.CommandName}" />

Which works, but is of course tightly coupled, and so extremely brittle.


You can setup CommandBindings for "All Windows" of your WPF application and implement command handlers in Application class.

First of all, create a static command container class. For example,

namespace WpfApplication1 
{
    public static class MyCommands
    {
        private static readonly RoutedUICommand doSomethingCommand = new RoutedUICommand("description", "DoSomethingCommand", typeof(MyCommands));

        public static RoutedUICommand DoSomethingCommand
        {
            get
            {
                return doSomethingCommand;
            }
        }
    }
}

Next, set your custom command to Button.Command like this.

<Window x:Class="WpfApplication1.MainWindow"
        ...
        xmlns:local="clr-namespace:WpfApplication1">
    <Grid>
        ...
        <Button Command="local:MyCommands.DoSomethingCommand">Execute</Button>
    </Grid>
</Window>

Finally, implement the command handler of your custom command in Application class.

namespace WpfApplication1 
{

    public partial class App : Application
    {
        public App()
        {
            var binding = new CommandBinding(MyCommands.DoSomethingCommand, DoSomething, CanDoSomething);

            // Register CommandBinding for all windows.
            CommandManager.RegisterClassCommandBinding(typeof(Window), binding);
        }

        private void DoSomething(object sender, ExecutedRoutedEventArgs e)
        {
            ...
        }

        private void CanDoSomething(object sender, CanExecuteRoutedEventArgs e)
        {
            ...
            e.CanExecute = true;
        }
    }
}


I did not like the complexity of the other solutions, but after a few hours of research I found out it is really simple.

First setup your command as you usually do, but add a static property for WPF so that it can obtain an instance of your command.

class MyCommand : ICommand
{
    // Singleton for the simple cases, may be replaced with your own factory     
    public static ICommand Instance { get; } = new MyCommand();

    public bool CanExecute(object parameter)
    {
        return true; // TODO: Implement
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        // TODO: Implement       
    }   
}

Add a reference to the namespace of your command in your XAML (last line), like this:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:commands="clr-namespace:MyProject.Commands">     

Then just reference your static property in your XAML like this:

<Button Content="Button" Command="commands:MyCommand.Instance" />


StackOverflow members helped me so many time that I decide now to contribute and share ;-)

Based on Shou Takenaka's answer, here is my implementation.

My interest was to produce only one reusable file.


First, create a command(s) container class

namespace Helpers
{
    public class SpecificHelper
    {
        private static RoutedUICommand _myCommand = new RoutedUICommand("myCmd","myCmd", typeof(SpecificHelper));
        public static RoutedUICommand MyCommand { get { return _myCommand; } }

        static SpecificHelper()
        {
            // Register CommandBinding for all windows.
            CommandManager.RegisterClassCommandBinding(typeof(Window), new CommandBinding(MyCommand, MyCommand_Executed, MyCommand_CanExecute));
        }

        // TODO: replace UIElement type by type of parameter's binded object
        #region MyCommand
        internal static void MyCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (!verifType<UIElement>(e.Parameter)) return;

            e.Handled = true;
            // TODO : complete the execution code ...
        }

        internal static void SelectAll_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            if (!verifType<UIElement>(e.Parameter)) return;

            e.CanExecute = true;
            var item = (e.Parameter as UIElement);
            // TODO : complete the execution code ...
        }
        #endregion

        private static bool verifType<T>(object o)
        {
            if (o == null) return false;
            if (!o.GetType().Equals(typeof(T))) return false;
            return true;
        }
    }
}

Next, declare a resource in App.xaml:

<Application x:Class="Helper.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:h="clr-namespace:Helpers"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d" 
             StartupUri="MainWindow.xaml" >
    <Application.Resources>
        <h:SpecificHelper x:Key="sh" />
    </Application.Resources>
</Application>

Finally, bind any command property to the property of your application resource:

<Button Content="Click to execute my command"
        Command="{Binding Source={StaticResource sh}, Path=MyCommand}"
        CommandParameter="{Binding ElementName=myElement}" />

that's all folks :-)


If you try to define CommandBindings or InputBindings as resources in your App.xaml, you will find that you cannot use them, because XAML doesn't allow you to use either:

<Window ... CommandBindings="{StaticResource commandBindings}">

or to set command bindings with a style setter:

<Setter Property="CommandBindings" Value="{StaticResource commandBindings}">

because neither of these properties have a "set" accessor. Using the idea in this post, I came up with a clean way of using resources from App.xaml or any other resource dictionary.

First you define your command bindings and input bindings indirectly, like you would any other resource:

    <InputBindingCollection x:Key="inputBindings">
        <KeyBinding Command="Help" Key="H" Modifiers="Ctrl"/>
    </InputBindingCollection>
    <CommandBindingCollection x:Key="commandBindings">
        <CommandBinding Command="Help" Executed="CommandBinding_Executed"/>
    </CommandBindingCollection>

and then you refer to them from the XAML of another class:

<Window ...>
    <i:Interaction.Behaviors>
        <local:CollectionSetterBehavior Property="InputBindings" Value="{StaticResource inputBindings}"/>
        <local:CollectionSetterBehavior Property="CommandBindings" Value="{StaticResource commandBindings}"/>
    </i:Interaction.Behaviors>
    ...
</Window>

The CollectionSetterBehavior is a reusable behavior that doesn't "set" the property to it's value, but instead clears the collection, and re-populates it. So the collection doesn't change, only it's contents.

Here's the source for the behavior:

public class CollectionSetterBehavior : Behavior<FrameworkElement>
{
    public string Property
    {
        get { return (string)GetValue(PropertyProperty); }
        set { SetValue(PropertyProperty, value); }
    }

    public static readonly DependencyProperty PropertyProperty =
        DependencyProperty.Register("Property", typeof(string), typeof(CollectionSetterBehavior), new UIPropertyMetadata(null));

    public IList Value
    {
        get { return (IList)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(IList), typeof(CollectionSetterBehavior), new UIPropertyMetadata(null));

    protected override void OnAttached()
    {
        var propertyInfo = AssociatedObject.GetType().GetProperty(Property);
        var property = propertyInfo.GetGetMethod().Invoke(AssociatedObject, null) as IList;
        property.Clear();
        foreach (var item in Value) property.Add(item);
    }
}

If you are not familiar with behaviors, first add this namespace:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

and add the corresponding reference to your project.


Declare the CommandBinding at Application level from where it can be re-used everywhere.

<Application.Resources>       
    <CommandBinding x:Key="PasteCommandKey" Command="ApplicationCommands.Paste" CanExecute="CommandBinding_CanExecute_1"/>
</Application.Resources>

In your App.xaml.cs file, define corresponding handlers :

  private void CommandBinding_CanExecute_11(object sender, System.Windows.Input.CanExecuteRoutedEventArgs e)
    {
      e.CanExecute = false;
    }

Usage

In any xaml file, use it like below :

 <RichTextBox x:Name="Rtb1" ContextMenuOpening="Rtb1_ContextMenuOpening_1" FontSize="15" Margin="10,10,10,-73">            
        <RichTextBox.CommandBindings>
            <StaticResourceExtension ResourceKey="PasteCommandKey"/>
        </RichTextBox.CommandBindings>
0

精彩评论

暂无评论...
验证码 换一张
取 消