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