GalaSoft MvvmLight 不使用 RelayCommand 禁用 UserControl
GalaSoft MvvmLight not disabling UserControl with RelayCommand
我正在使用 WPF 和 GalaSoft MvvmLight (5.4.1.1) 构建一个简单的应用程序。
Everything works fine, I have a grid, and when a row is selected I enable/disable buttons that have actions assigned.
示例按钮如下所示:
<Button Command="{Binding MarkRouteAsCompletedCommand, Mode=OneTime}">Mak as Completed</Button>
当我将 Button 更改为我的 UserControl 时,我没有得到“enable/disable”效果并且我的自定义控件始终处于启用状态。
我创建了一个如下所示的 UserControl(显示了两个控件):
他们的 XAML 看起来像这样:
<controls:ShortcutButton Text="Create" Command="{Binding CreateCommand, Mode=OneTime}" Shortcut="Insert"/>
<controls:ShortcutButton Text="Edit" Command="{Binding EditCommand, Mode=OneTime}" Shortcut="F2"/>
想法是显示分配给特定按钮的键盘键。
我的用户控件如下所示:
XAML:
<UserControl x:Class="ABC.Desktop.Wpf.Controls.Buttons.ShortcutButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<TextBlock Margin="3,0,3,0" FontSize="10" Text="{Binding Shortcut, Mode=OneWay, Converter={StaticResource ObjectToStringConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"/>
<Button MinWidth="80"
Content="{Binding Text, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
IsCancel="{Binding IsCancel, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
<Button.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding Command, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding CommandParameter, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"/>
</Button.InputBindings>
</Button>
</StackPanel>
</UserControl>
后面的代码:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace ABC.Desktop.Wpf.Controls.Buttons
{
public partial class ShortcutButton : UserControl
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(ShortcutButton), new PropertyMetadata(null));
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(ShortcutButton), new PropertyMetadata(null));
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(nameof(CommandParameter), typeof(object), typeof(ShortcutButton), new PropertyMetadata(null));
public ShortcutButton()
{
InitializeComponent();
}
public Key? Shortcut { get; set; }
public bool IsCancel { get; set; }
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
}
}
我不知道为什么 enabling/disabling 适用于 Button 而不是我的 UserControl。
可能我必须在我的用户控件中实现一些东西,但我不知道是什么。
注意:这与 MvvLight 完全无关。
WPF ButtonBase
class 具有对计算 Command.CanExecute
的硬编码支持,以便为 IsEnabled
属性 提供值。另请参阅 source code 中的 IsEnabledCore
。
UserControl
没有这样的支持,需要自己绑定IsEnabled
也就是说,您可以 - 而不是定义用户控件 - 使用具有自定义控件模板的 Button 控件。
您没有正确实现命令逻辑。要忽略这一点,您可以简单地扩展 ButtonBase
(或 Button
)而不是 UserControl
。否则让你的 ShortcutButton
实现 ICommandSource
.
扩展 Button
是推荐的解决方案。扩展 UserControl
几乎总是一个错误的决定,因为它不提供普通 ContentControl
的自定义,默认 Style
在 Generic.xaml[= 中定义52=], 报价。
命令状态的处理逻辑如下:
public partial class ShortcutButton : UserControl, ICommandSource
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(ShortcutButton),
new PropertyMetadata(default(ICommand), OnCommandChanged));
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
private bool OriginalIsEnabledValue { get; set; }
private bool IsEnabledChangedByCommandCanExecute { get; set; }
public ShortcutButton()
{
this.OriginalIsEnabledValue = this.IsEnabled;
this.IsEnabledChanged += OnIsEnabledChanged;
}
private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (this.IsEnabledChangedByCommandCanExecute)
{
return;
}
this.OriginalIsEnabledValue = (bool)e.NewValue;
}
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as ShortcutButton;
if (e.OldValue is ICommand oldCommand)
{
CanExecuteChangedEventManager.RemoveHandler(this_.Command, this_.OnCommandCanExecuteChanged);
}
if (e.NewValue is ICommand newCommand)
{
CanExecuteChangedEventManager.AddHandler(this_.Command, this_.OnCommandCanExecuteChanged);
}
}
private void OnCommandCanExecuteChanged(object sender, EventArgs e)
{
this.IsEnabledChangedByCommandCanExecute = true;
this.IsEnabled = this.OriginalIsEnabledValue
&& this.Command.CanExecute(this.CommandParameter);
this.IsEnabledChangedByCommandCanExecute = false;
}
}
您应该使用标准 Button
并为每个快捷键配置一个 KeyBinding
,而不是实现自定义 Button
控件。例如,要使快捷键成为全局快捷键,请在 Window
元素上定义输入绑定:
<Window>
<Window.InputBindings>
<KeyBinding Key="F2" Command="{Binding EditCommand, Mode=OneTime}" />
</Window.InputBindings>
</Window>
要实现您想要的效果,您一定要扩展 Button
并修改默认的 Style
以显示附加标签。您的 ShortcutButton
必须修改如下:
ShortcutButton.cs
public class ShortcutButton : Button
{
public static readonly DependencyProperty ShortcutModifierKeysProperty =
DependencyProperty.Register(
"ShortcutModifierKeys",
typeof(ModifierKeys),
typeof(ShortcutButton),
new PropertyMetadata(default(ModifierKeys), OnShortcutModifierKeysChanged));
public ModifierKeys ShortcutModifierKeys
{
get => (ModifierKeys)GetValue(ShortcutModifierKeysProperty);
set => SetValue(ShortcutModifierKeysProperty, value);
}
public static readonly DependencyProperty ShortcutKeyProperty =
DependencyProperty.Register(
"ShortcutKey",
typeof(Key),
typeof(ShortcutButton),
new PropertyMetadata(default(Key), OnShortcutKeyChanged));
public Key ShortcutKey
{
get => (Key)GetValue(ShortcutKeyProperty);
set => SetValue(ShortcutKeyProperty, value);
}
public static readonly DependencyProperty ShortcutKeyTargetProperty =
DependencyProperty.Register(
"ShortcutKeyTarget",
typeof(UIElement),
typeof(ShortcutButton),
new PropertyMetadata(default(UIElement), OnShortcutKeyTargetChanged));
public UIElement ShortcutKeyTarget
{
get => (UIElement)GetValue(ShortcutKeyTargetProperty);
set => SetValue(ShortcutKeyTargetProperty, value);
}
private static readonly DependencyPropertyKey ShortcutKeyDisplayTextPropertyKey =
DependencyProperty.RegisterReadOnly(
"ShortcutKeyDisplayText",
typeof(string),
typeof(ShortcutButton),
new PropertyMetadata(default(string)));
public static readonly DependencyProperty ShortcutKeyDisplayTextProperty = ShortcutKeyDisplayTextPropertyKey.DependencyProperty;
public string ShortcutKeyDisplayText
{
get => (string)GetValue(ShortcutKeyDisplayTextProperty);
private set => SetValue(ShortcutKeyDisplayTextPropertyKey, value);
}
private KeyBinding ShortcutKeyBinding { get; set; }
static ShortcutButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ShortcutButton), new FrameworkPropertyMetadata(typeof(ShortcutButton)));
CommandProperty.OverrideMetadata(typeof(ShortcutButton), new FrameworkPropertyMetadata(OnCommandChanged));
}
private static void OnShortcutModifierKeysChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as ShortcutButton;
this_.UpdateShortcutKeyBinding();
}
private static void OnShortcutKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as ShortcutButton;
this_.UpdateShortcutKeyBinding();
}
private static void OnShortcutKeyTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as ShortcutButton;
this_.UpdateShortcutKeyBinding();
}
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as ShortcutButton;
this_.UpdateShortcutKeyBinding();
}
private void UpdateShortcutKeyBinding()
{
this.ShortcutKeyDisplayText = this.ShortcutModifierKeys != ModifierKeys.None
? $"{this.ShortcutModifierKeys}+{this.ShortcutKey}"
: this.ShortcutKey.ToString();
if (this.Command == null || this.ShortcutKeyTarget == null)
{
return;
}
this.ShortcutKeyTarget.InputBindings.Remove(this.ShortcutKeyBinding);
this.ShortcutKeyBinding = new KeyBinding(this.Command, this.ShortcutKey, this.ShortcutModifierKeys);
this.ShortcutKeyBinding.Freeze();
this.ShortcutKeyTarget.InputBindings.Add(this.ShortcutKeyBinding);
}
}
Generic.xaml
<Style TargetType="{x:Type local:ShortcutButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ShortcutButton}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<TextBlock Text="{TemplateBinding ShortcutKeyDisplayText}" />
<ContentPresenter />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
用法
<Window x:Name="Window">
<local:ShortcutButton Content="Edit"
Command="{Binding EditCommand}"
ShortcutKey="{x:Static Key.F2}"
ShortcutModifierKeys="{x:Static ModifierKeys.Alt}"
ShortcutKeyTarget="{Binding ElementName=Window}" />
</Window>
我正在使用 WPF 和 GalaSoft MvvmLight (5.4.1.1) 构建一个简单的应用程序。
Everything works fine, I have a grid, and when a row is selected I enable/disable buttons that have actions assigned.
示例按钮如下所示:
<Button Command="{Binding MarkRouteAsCompletedCommand, Mode=OneTime}">Mak as Completed</Button>
当我将 Button 更改为我的 UserControl 时,我没有得到“enable/disable”效果并且我的自定义控件始终处于启用状态。
我创建了一个如下所示的 UserControl(显示了两个控件):
他们的 XAML 看起来像这样:
<controls:ShortcutButton Text="Create" Command="{Binding CreateCommand, Mode=OneTime}" Shortcut="Insert"/>
<controls:ShortcutButton Text="Edit" Command="{Binding EditCommand, Mode=OneTime}" Shortcut="F2"/>
想法是显示分配给特定按钮的键盘键。
我的用户控件如下所示:
XAML:
<UserControl x:Class="ABC.Desktop.Wpf.Controls.Buttons.ShortcutButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<TextBlock Margin="3,0,3,0" FontSize="10" Text="{Binding Shortcut, Mode=OneWay, Converter={StaticResource ObjectToStringConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"/>
<Button MinWidth="80"
Content="{Binding Text, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
IsCancel="{Binding IsCancel, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}">
<Button.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding Command, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding CommandParameter, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"/>
</Button.InputBindings>
</Button>
</StackPanel>
</UserControl>
后面的代码:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace ABC.Desktop.Wpf.Controls.Buttons
{
public partial class ShortcutButton : UserControl
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(ShortcutButton), new PropertyMetadata(null));
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(ShortcutButton), new PropertyMetadata(null));
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(nameof(CommandParameter), typeof(object), typeof(ShortcutButton), new PropertyMetadata(null));
public ShortcutButton()
{
InitializeComponent();
}
public Key? Shortcut { get; set; }
public bool IsCancel { get; set; }
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
}
}
我不知道为什么 enabling/disabling 适用于 Button 而不是我的 UserControl。 可能我必须在我的用户控件中实现一些东西,但我不知道是什么。
注意:这与 MvvLight 完全无关。
WPF ButtonBase
class 具有对计算 Command.CanExecute
的硬编码支持,以便为 IsEnabled
属性 提供值。另请参阅 source code 中的 IsEnabledCore
。
UserControl
没有这样的支持,需要自己绑定IsEnabled
也就是说,您可以 - 而不是定义用户控件 - 使用具有自定义控件模板的 Button 控件。
您没有正确实现命令逻辑。要忽略这一点,您可以简单地扩展 ButtonBase
(或 Button
)而不是 UserControl
。否则让你的 ShortcutButton
实现 ICommandSource
.
扩展 Button
是推荐的解决方案。扩展 UserControl
几乎总是一个错误的决定,因为它不提供普通 ContentControl
的自定义,默认 Style
在 Generic.xaml[= 中定义52=], 报价。
命令状态的处理逻辑如下:
public partial class ShortcutButton : UserControl, ICommandSource
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(ShortcutButton),
new PropertyMetadata(default(ICommand), OnCommandChanged));
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
private bool OriginalIsEnabledValue { get; set; }
private bool IsEnabledChangedByCommandCanExecute { get; set; }
public ShortcutButton()
{
this.OriginalIsEnabledValue = this.IsEnabled;
this.IsEnabledChanged += OnIsEnabledChanged;
}
private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (this.IsEnabledChangedByCommandCanExecute)
{
return;
}
this.OriginalIsEnabledValue = (bool)e.NewValue;
}
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as ShortcutButton;
if (e.OldValue is ICommand oldCommand)
{
CanExecuteChangedEventManager.RemoveHandler(this_.Command, this_.OnCommandCanExecuteChanged);
}
if (e.NewValue is ICommand newCommand)
{
CanExecuteChangedEventManager.AddHandler(this_.Command, this_.OnCommandCanExecuteChanged);
}
}
private void OnCommandCanExecuteChanged(object sender, EventArgs e)
{
this.IsEnabledChangedByCommandCanExecute = true;
this.IsEnabled = this.OriginalIsEnabledValue
&& this.Command.CanExecute(this.CommandParameter);
this.IsEnabledChangedByCommandCanExecute = false;
}
}
您应该使用标准 Button
并为每个快捷键配置一个 KeyBinding
,而不是实现自定义 Button
控件。例如,要使快捷键成为全局快捷键,请在 Window
元素上定义输入绑定:
<Window>
<Window.InputBindings>
<KeyBinding Key="F2" Command="{Binding EditCommand, Mode=OneTime}" />
</Window.InputBindings>
</Window>
要实现您想要的效果,您一定要扩展 Button
并修改默认的 Style
以显示附加标签。您的 ShortcutButton
必须修改如下:
ShortcutButton.cs
public class ShortcutButton : Button
{
public static readonly DependencyProperty ShortcutModifierKeysProperty =
DependencyProperty.Register(
"ShortcutModifierKeys",
typeof(ModifierKeys),
typeof(ShortcutButton),
new PropertyMetadata(default(ModifierKeys), OnShortcutModifierKeysChanged));
public ModifierKeys ShortcutModifierKeys
{
get => (ModifierKeys)GetValue(ShortcutModifierKeysProperty);
set => SetValue(ShortcutModifierKeysProperty, value);
}
public static readonly DependencyProperty ShortcutKeyProperty =
DependencyProperty.Register(
"ShortcutKey",
typeof(Key),
typeof(ShortcutButton),
new PropertyMetadata(default(Key), OnShortcutKeyChanged));
public Key ShortcutKey
{
get => (Key)GetValue(ShortcutKeyProperty);
set => SetValue(ShortcutKeyProperty, value);
}
public static readonly DependencyProperty ShortcutKeyTargetProperty =
DependencyProperty.Register(
"ShortcutKeyTarget",
typeof(UIElement),
typeof(ShortcutButton),
new PropertyMetadata(default(UIElement), OnShortcutKeyTargetChanged));
public UIElement ShortcutKeyTarget
{
get => (UIElement)GetValue(ShortcutKeyTargetProperty);
set => SetValue(ShortcutKeyTargetProperty, value);
}
private static readonly DependencyPropertyKey ShortcutKeyDisplayTextPropertyKey =
DependencyProperty.RegisterReadOnly(
"ShortcutKeyDisplayText",
typeof(string),
typeof(ShortcutButton),
new PropertyMetadata(default(string)));
public static readonly DependencyProperty ShortcutKeyDisplayTextProperty = ShortcutKeyDisplayTextPropertyKey.DependencyProperty;
public string ShortcutKeyDisplayText
{
get => (string)GetValue(ShortcutKeyDisplayTextProperty);
private set => SetValue(ShortcutKeyDisplayTextPropertyKey, value);
}
private KeyBinding ShortcutKeyBinding { get; set; }
static ShortcutButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ShortcutButton), new FrameworkPropertyMetadata(typeof(ShortcutButton)));
CommandProperty.OverrideMetadata(typeof(ShortcutButton), new FrameworkPropertyMetadata(OnCommandChanged));
}
private static void OnShortcutModifierKeysChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as ShortcutButton;
this_.UpdateShortcutKeyBinding();
}
private static void OnShortcutKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as ShortcutButton;
this_.UpdateShortcutKeyBinding();
}
private static void OnShortcutKeyTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as ShortcutButton;
this_.UpdateShortcutKeyBinding();
}
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as ShortcutButton;
this_.UpdateShortcutKeyBinding();
}
private void UpdateShortcutKeyBinding()
{
this.ShortcutKeyDisplayText = this.ShortcutModifierKeys != ModifierKeys.None
? $"{this.ShortcutModifierKeys}+{this.ShortcutKey}"
: this.ShortcutKey.ToString();
if (this.Command == null || this.ShortcutKeyTarget == null)
{
return;
}
this.ShortcutKeyTarget.InputBindings.Remove(this.ShortcutKeyBinding);
this.ShortcutKeyBinding = new KeyBinding(this.Command, this.ShortcutKey, this.ShortcutModifierKeys);
this.ShortcutKeyBinding.Freeze();
this.ShortcutKeyTarget.InputBindings.Add(this.ShortcutKeyBinding);
}
}
Generic.xaml
<Style TargetType="{x:Type local:ShortcutButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ShortcutButton}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<TextBlock Text="{TemplateBinding ShortcutKeyDisplayText}" />
<ContentPresenter />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
用法
<Window x:Name="Window">
<local:ShortcutButton Content="Edit"
Command="{Binding EditCommand}"
ShortcutKey="{x:Static Key.F2}"
ShortcutModifierKeys="{x:Static ModifierKeys.Alt}"
ShortcutKeyTarget="{Binding ElementName=Window}" />
</Window>