I'm building a simple application using WPF and 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.
Sample button looks like that:
<Button Command="{Binding MarkRouteAsCompletedCommand, Mode=OneTime}">Mak as Completed</Button>
When I change the Button to my UserControl I don't get the "enable/disable" effect and my custom control is always enabled.
I've created a UserControl that looks like this (two controls shown):
The XAML for them looks like this:
<controls:ShortcutButton Text="Create" Command="{Binding CreateCommand, Mode=OneTime}" Shortcut="Insert"/>
<controls:ShortcutButton Text="Edit" Command="{Binding EditCommand, Mode=OneTime}" Shortcut="F2"/>
The idea was to display the keyboard key that is assigned to a specific button.
My UserControl looks like this:
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>
Code behind:
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);
}
}
}
I have no idea why enabling/disabling works with Button but not with my UserControl. Probably I must implement something in my UserControl, but I have not a clue what.
CodePudding user response:
You did not implement the command logic properly. To omit this, you can simply extend ButtonBase
(or Button
) instead of UserControl
. Otherwise let your ShortcutButton
implement ICommandSource
.
Extending Button
is the recommended solution. Extending UserControl
is almost always a bad decision as it does not provide the customization that a plain ContentControl
, with a default Style
defined in Generic.xaml, offers.
The logic to handle the command state is as followed:
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;
}
}
Instead of implementing a custom Button
control, you should use the standard Button
and configure a KeyBinding
for each shortcut key. For example, to make the shortcut keys global, define the input bindings on the Window
element:
<Window>
<Window.InputBindings>
<KeyBinding Key="F2" Command="{Binding EditCommand, Mode=OneTime}" />
</Window.InputBindings>
</Window>
To achieve what you want, you should definitely extend Button
and modify the default Style
to show the additional label. Your ShortcutButton
musat be modified as followed:
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), OnShortcutKeyChanged));
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()
{
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);
this.ShortcutKeyDisplayText = this.ShortcutModifierKeys != ModifierKeys.None
? $"{this.ShortcutModifierKeys} {this.ShortcutKey}"
: this.ShortcutKey.ToString();
}
}
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>
Usage
<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>
CodePudding user response:
Note: this is not related to MvvLight at all.
The WPF ButtonBase class has hard-coded support for evaluating Command.CanExecute to provide a value for the IsEnabled property. See also IsEnabledCore
in the source code.
There is not such support for UserControl, so you have to bind IsEnabled
yourself.
That said, you could - instead of defining a user control - use a Button control with custom control template.
CodePudding user response:
Why don't you bind directly to the Command
and CommandParameter
properties of the Button
?:
<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}}}"
Command="{Binding Command, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding CommandParameter, RelativeSource={RelativeSource AncestorType=UserControl}}" />
Then it should work provided that there is a CreateCommand
/EditCommand
property of the DataContext
of the ShortcutButton
control that returns a valid ICommand
implementation.