【问题标题】:WPF Custom control - Binding to command defined in code-behindWPF 自定义控件 - 绑定到代码隐藏中定义的命令
【发布时间】:2015-03-23 17:50:48
【问题描述】:

我正在尝试创建一个名为“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 的 Command 绑定不起作用,因为找不到“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); }
    }

【问题讨论】:

  • 您没有在这里使用您的 DP:您只是创建了它并创建了一个属性。 CalculateChecksumCommand 不会通知接口的任何更改。请看如何声明 DP:msdn(注意这可能不是问题)
  • 当您需要使用不受您控制的属性时,请使用 DP:这里不是这种情况。使用 Commands & Binding 与 ViewModel 层通信,避免它与 View 层通信。与 RelativeSource 的绑定应该不是问题,但永远不要根据自定义控件中的 DataContext 使用绑定。您的代码的问题在于它在不应该向外界公开CalculateChecksumCommand(您应该只公开外部世界可以使用的内容,而您的财产并非如此)
  • @nkoniishvt,好的,我明白了。我已经编辑了我的第一篇文章,以正确的方式声明 DP。
  • @nkoniishvt,事实上,我想使用一个命令,以便在必要时自动禁用我的 MenuItem。这可能不是最好的方法。我应该如何实现这种行为?我应该使用 MenuItem 的点击事件吗?如果是这样,怎么做? (我试了很多次都没有成功)
  • 这是正确的做法,只是您的组件应该公开一个 ICommand DP 并让使用者设置它。自定义控件应尽可能“通用”,在这里您的控件将仅在一种情况下工作:计算似乎的校验和。我不是最有经验的 WPF 开发人员,但我认为你应该做的是公开一个 ICommand DP 并在回调函数中设置你的 menuItemInsertChecksum 的 Command DP。 (当你暴露的 ICommand 被设置时,设置 MenuItem 的命令)

标签: c# wpf command custom-controls


【解决方案1】:

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

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

【讨论】:

  • 太棒了!非常感谢您的详细回答。我几乎找到了解决方案。我的情况略有不同,因为我的命令是在我的 CustomControl 代码隐藏中定义的。这就是为什么我想像这样设置我的 MenuItem DataContext :DataContext="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget}"。这应该有效,因为 PlacementTarget 应该是我的 CustomControl ......但它不是。事实上, PlacementTarget 似乎是我的 CustomControl 的孩子,而不是 CustomControl 本身。有没有办法解决这个问题?
  • 对不起,我忘记更改命令了。我没有更多的绑定错误,但它仍然不起作用。我想我不会将 ContextMenu 用于此功能...我花了太多时间但没有成功。无论如何,感谢您花费的时间。也谢谢你的链接!
  • 我终于偶然找到了解决方案......也许它会对某人有所帮助......因为我的 ContextMenu 的 PlacementTarget 是我的 CustomControl 模板中的一个子项,所以我必须像这样设置我的命令: Command="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.TemplatedParent.CalculateChecksumCommand}"(请参阅 TemplatedParent)。可能有更好的方法,但这对我有用。
  • 干得好,以后你应该发布你的整个代码,这样就没有人猜测并且可以帮助你更好。另外我认为你应该熟悉 MVVM,因为这种模式对 WPF 非常有用。
  • ,你说得对,我的问题没有解释清楚。我发布了一个答案,其中包含我的固定代码的简化版本。关于 MVVM 模式,我目前正在尝试学习它。这就是为什么我不知道好的做法。
【解决方案2】:

有了来自 Aybe 和 nkoniishvt 的 cmets,我想我能够回答我自己的问题。

目标:在 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;
        }
    }
}

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

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

不要忘记在 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() 方法。

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

【讨论】:

    【解决方案3】:

    您是否尝试过实现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

    【讨论】:

    • 这个解决方案看起来真的很有趣!我将尝试实施它。谢谢。
    • 不客气。也请查看“示例”链接。您还需要在class 下方声明静态RoutedComand
    猜你喜欢
    • 2010-12-26
    • 2021-07-19
    • 1970-01-01
    • 2018-07-17
    • 2015-06-03
    • 1970-01-01
    • 1970-01-01
    • 2016-02-22
    • 2013-03-15
    相关资源
    最近更新 更多