WPF 自定义控件 - 绑定到代码隐藏中定义的命令

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29217006/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 13:01:49  来源:igfitidea点击:

WPF Custom control - Binding to command defined in code-behind

c#wpfcommandcustom-controls

提问by

I'm trying to create a WPF custom control called "DataTextBox". Everything works fine except for the context menu of this control. Indeed, I would like to add an item in the DataTextBox's Context Menu. To do this, I have added a MenuItem in my DataTextBox style defined in generic.xaml:

我正在尝试创建一个名为“DataTextBox”的 WPF 自定义控件。除了此控件的上下文菜单外,一切正常。事实上,我想在 DataTextBox 的上下文菜单中添加一个项目。为此,我在 generic.xaml 中定义的 DataTextBox 样式中添加了一个 MenuItem:

<Style TargetType="{x:Type controls:DataTextBox}">
    <Setter Property="ContextMenu">
        <Setter.Value>
            <ContextMenu>
                <MenuItem Header="{DynamicResource Components_TextBoxCut}" Command="ApplicationCommands.Cut" />
                <MenuItem Header="{DynamicResource Components_TextBoxCopy}" Command="ApplicationCommands.Copy" />
                <MenuItem Header="{DynamicResource Components_TextBoxPaste}" Command="ApplicationCommands.Paste" />
                <MenuItem x:Name="menuItemInsertChecksum" Header="{DynamicResource Components_DataTextBoxInsertChecksum}"
                Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:DataTextBox}}, Path=CalculateChecksumCommand}" />
            </ContextMenu>
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
              ...
        </Setter.Value>
    </Setter>
</Style>

I have also added a command in the DataTextBox code-behind

我还在 DataTextBox 代码隐藏中添加了一个命令

    public static DependencyProperty CalculateChecksumCommandProperty = DependencyProperty.Register("CalculateChecksumCommand", typeof(ICommand), typeof(DataTextBox));
    public ICommand CalculateChecksumCommand { get; private set; }

This command is initialized in the DataTextBox constructor :

此命令在 DataTextBox 构造函数中初始化:

    public DataTextBox() : base()
    {
        CalculateChecksumCommand = new RelayCommand(() => CalculateChecksum(), () => CanCalculateChecksum());
    }

The issue I have is that the Command binding of my last MenuItem does not work because the "CalculateChecksumCommand" is not found. This means that the "CalculateChecksum()" method is never called.

我遇到的问题是我的最后一个 MenuItem 的命令绑定不起作用,因为找不到“CalculateChecksumCommand”。这意味着永远不会调用“CalculateChecksum()”方法。

I would appreciate any help on that subject. Thank you.

我将不胜感激有关该主题的任何帮助。谢谢你。

EDIT : The Dependency Property declaration should be :

编辑:依赖属性声明应该是:

    public static DependencyProperty CalculateChecksumCommandProperty = DependencyProperty.Register("CalculateChecksumCommand", typeof(ICommand), typeof(DataTextBox));
    public ICommand CalculateChecksumCommand
    {
        get { return (ICommand)GetValue(CalculateChecksumCommandProperty); }
        private set { SetValue(CalculateChecksumCommandProperty, value); }
    }

采纳答案by Aybe

The window that hosts a control and defines a style for it which binds one menu item of its context menu to a command of it :

承载控件并为其定义样式的窗口,该样式将其上下文菜单的一个菜单项绑定到它的命令:

XAML

XAML

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:wpfApplication2="clr-namespace:WpfApplication2"
        Title="MainWindow"
        Width="525"
        Height="350"
        mc:Ignorable="d">
    <Grid>
        <Grid.Resources>
            <Style TargetType="wpfApplication2:UserControl1" x:Shared="False">
                <Style.Setters>
                    <Setter Property="ContextMenu">
                        <Setter.Value>
                            <ContextMenu>
                                <MenuItem Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget.(wpfApplication2:UserControl1.MyCommand)}" Header="Hello" />
                            </ContextMenu>
                        </Setter.Value>
                    </Setter>
                </Style.Setters>
            </Style>
        </Grid.Resources>
        <wpfApplication2:UserControl1 />

    </Grid>
</Window>

Code

代码

using System;
using System.Windows;
using System.Windows.Input;

namespace WpfApplication2
{
    /// <summary>
    ///     Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }


    public class DelegateCommand : ICommand
    {
        private readonly Func<object, bool> _canExecute;
        private readonly Action<object> _execute;

        public DelegateCommand(Action<object> execute) : this(execute, s => true)
        {
        }

        public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
        {
            _execute = execute;
            _canExecute = canExecute;
        }

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

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        public event EventHandler CanExecuteChanged;
    }
}

Control :

控制 :

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;

namespace WpfApplication2
{
    /// <summary>
    ///     Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class UserControl1 : UserControl, INotifyPropertyChanged
    {
        private DelegateCommand _myCommand;

        public UserControl1()
        {
            InitializeComponent();

            MyCommand = new DelegateCommand(Execute);
        }

        public DelegateCommand MyCommand
        {
            get { return _myCommand; }
            set
            {
                _myCommand = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void Execute(object o)
        {
            MessageBox.Show("Hello");
        }

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

回答by Aybe

With comments from Aybe and nkoniishvt, I think that I'm able to answer my own question.

根据 Aybe 和 nkoniishvt 的评论,我认为我可以回答我自己的问题。

Objective: create a command in a CustomControl (not UserControl) and use it in the xaml part of this CustomControl (As nkoniishvt said, commands are typically used in ViewModel and not in UI components. However, I have not found any similar solutions.)

目标:在 CustomControl(不是 UserControl)中创建一个命令,并在这个 CustomControl 的 xaml 部分使用它(正如 nkoniishvt 所说,命令通常用于 ViewModel 而不是 UI 组件。但是,我还没有找到任何类似的解决方案。)

CustomControl code-behind:

CustomControl 代码隐藏:

using GalaSoft.MvvmLight.Command;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfApplication1
{
    public class CustomControl1 : TextBox
    {
        public CustomControl1()
            : base()
        {
            MyCommand = new RelayCommand(() => Execute(), () => CanExecute());
        }

        static CustomControl1()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
        }

        public static DependencyProperty MyCommandProperty = DependencyProperty.Register("MyCommand", typeof(ICommand), typeof(CustomControl1));
        public ICommand MyCommand
        {
            get { return (ICommand)GetValue(MyCommandProperty); }
            private set { SetValue(MyCommandProperty, value); }
        }

        private void Execute()
        {
            //Do stuff
        }

        private bool CanExecute()
        {
            return true;
        }
    }
}

CustomControl appearance defined in Themes/Generic.xaml:

在 Themes/Generic.xaml 中定义的 CustomControl 外观:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1">


    <Style TargetType="{x:Type local:CustomControl1}">
        <Setter Property="ContextMenu">
            <Setter.Value>
                <ContextMenu>
                    <MenuItem Header="Cut" Command="ApplicationCommands.Cut" />
                    <MenuItem Header="Copy" Command="ApplicationCommands.Copy" />
                    <MenuItem Header="Past" Command="ApplicationCommands.Paste" />
                    <MenuItem Header="Execute MyCommand in CustomControl1"
                              Command="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.TemplatedParent.MyCommand}" />
                    <!--In this case, PlacementTarget is "txtBox"
                    This is why we have to find the templated parent of the PlacementTarget because MyCommand is defined in the CustomControl1 code-behind-->
                </ContextMenu>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:CustomControl1}">
                    <Grid>
                        <!--Some UI elements-->
                        <TextBox Name="txtBox" ContextMenu="{TemplateBinding ContextMenu}" />
                        <!--Others UI elements-->
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Example of use of this CustomControl in MainWindow.xaml:

在 MainWindow.xaml 中使用此 CustomControl 的示例:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <local:CustomControl1 />
    </Grid>
</Window>

Do not forget to add resources in App.xaml:

不要忘记在 App.xaml 中添加资源:

<Application x:Class="WpfApplication1.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Themes/Generic.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
  </Application.Resources>
</Application>

By running the application, we can see that MyCommand is properly bound. This means that the Execute() method is called when the user click on the fourth MenuItem in the ContextMenu.

通过运行应用程序,我们可以看到 MyCommand 被正确绑定。这意味着当用户单击 ContextMenu 中的第四个 MenuItem 时,将调用 Execute() 方法。

If you see areas for improvement, thank you to let me know. Hoping it will help someone.

如果您发现需要改进的地方,请告诉我。希望它会帮助某人。

回答by Zoti

Have you tried to implement a CustomRoutedCommand?

您是否尝试过实现CustomRoutedCommand

This works for my CustomControl:

这适用于我的 CustomControl:

public static RoutedCommand CustomCommand = new RoutedCommand();

        CommandBinding CustomCommandBinding = new CommandBinding(CustomCommand, ExecutedCustomCommand, CanExecuteCustomCommand);
        this.CommandBindings.Add(CustomCommandBinding);
        customControl.Command = CustomCommand;
        KeyGesture kg = new KeyGesture(Key.F, ModifierKeys.Control);
        InputBinding ib = new InputBinding(CustomCommand, kg);
        this.InputBindings.Add(ib);

    private void ExecutedCustomCommand(object sender, ExecutedRoutedEventArgs e)
    {
        //what to do;
        MessageBox.Show("Custom Command Executed");
    }

    private void CanExecuteCustomCommand(object sender, CanExecuteRoutedEventArgs e)
    {
        Control target = e.Source as Control;

        if (target != null)
        {
            e.CanExecute = true;
        }
        else
        {
            e.CanExecute = false;
        }
    }

another interesting example

另一个有趣的例子