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

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

我正在尝试创建一个名为 "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>

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

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

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

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

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

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

编辑:依赖性 属性 声明应为:

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

承载控件并为其定义样式的

将其上下文菜单的一个菜单项绑定到它的命令的 window :

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>

代码

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;
    }
}

控制:

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));
        }
    }
}

有了 Aybe 和 nkoniishvt 的评论,我想我可以回答我自己的问题了。

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

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 外观定义在 Themes/Generic.xaml:

<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>

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

<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>

不要忘记在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>

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

如果您看到需要改进的地方,请告诉我,谢谢。 希望它能对某人有所帮助。

您是否尝试过实施 CustomRoutedCommand

这适用于我的 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;
        }
    }

另一个有趣的example