简体   繁体   中英

Bind KeyBinding Command to WPF DatePicker (MVVM)

I've been searching high and low for a way to bind the Return key to a DatePicker control in a MVVM way, but to no avail.

My current XAML Markup:

<DatePicker x:Name="DateFilter" SelectedDate="{Binding SearchDate, UpdateSourceTrigger=PropertyChanged}">
    <DatePicker.InputBindings>
        <KeyBinding Key="Return" Command="{Binding SearchCommand}"></KeyBinding>
    </DatePicker.InputBindings>
</DatePicker>

The closest thing I found is rewriting the entire control template, but this both requires a lot of markup and screws up the original control appearance.

Right now, I've hacked a solution together by forcefully adding the DatePicker InputBindings to the underlying DatePickerTextBox from the form codebehind, but it's awful as it requires writing code behind and uses reflection:

Form CodeBehind (bound to the Loaded event of the view):

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    var fiTextBox = typeof(DatePicker)
        .GetField("_textBox", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
    if (fiTextBox?.GetValue(DateFilter) is System.Windows.Controls.Primitives.DatePickerTextBox textBox)
    {
        textBox.InputBindings.AddRange(DateFilter.InputBindings);
    }
}

Is there a better way to do this?

(Note: I cannot bind the execution of the command to the change of the bound SearchDate property as the operation is quite expensive and I don't want it to fire every time the user picks a new date. However, I need the property to immediately refresh as the CanExecute of the command is also tied to said Date not being null.)

You could use a reusable attached behaviour:

public static class ReturnKeyBehavior
{
    public static ICommand GetCommand(UIElement UIElement) =>
        (ICommand)UIElement.GetValue(CommandProperty);

    public static void SetCommand(UIElement UIElement, ICommand value) =>
        UIElement.SetValue(CommandProperty, value);

    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached(
        "Command",
        typeof(ICommand),
        typeof(ReturnKeyBehavior),
        new UIPropertyMetadata(null, OnCommandChanged));

    private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        UIElement uie = (UIElement)d;

        ICommand oldCommand = e.OldValue as ICommand;
        if (oldCommand != null)
            uie.RemoveHandler(UIElement.PreviewKeyDownEvent, (KeyEventHandler)OnMouseLeftButtonDown);

        ICommand newCommand = e.NewValue as ICommand;
        if (newCommand != null)
            uie.AddHandler(UIElement.PreviewKeyDownEvent, (KeyEventHandler)OnMouseLeftButtonDown, true);
    }

    private static void OnMouseLeftButtonDown(object sender, KeyEventArgs e)
    {
        if(e.Key == Key.Enter)
        {
            UIElement uie = (UIElement)sender;
            ICommand command = GetCommand(uie);
            if (command != null)
                command.Execute(null);
        }
    }
}

...that can be attached to any UIElement in XAML:

<DatePicker local:ReturnKeyBehavior.Command="{Binding ListViewItemMouseLeftButtonDownCommand}" />

Then you don't have to deal with any keys in the view model.

I'd probably use an interaction trigger, or whatever else your framework uses to convert events to commands, and then trap PreviewKeyDown:

<DatePicker x:Name="DateFilter" SelectedDate="{Binding SearchDate, UpdateSourceTrigger=PropertyChanged}"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:cmd ="http://www.galasoft.ch/mvvmlight">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="PreviewKeyDown">
            <cmd:EventToCommand Command="{Binding KeyDownCommand}" PassEventArgsToCommand="True"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</DatePicker>

And then in your view model:

private ICommand _KeyDownCommand;
public ICommand KeyDownCommand => this._KeyDownCommand ?? (this._KeyDownCommand = new RelayCommand<KeyEventArgs>(OnKeyDown));

private void OnKeyDown(KeyEventArgs args)
{
    if (args.Key == Key.Return)
    {
        // do something
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM