Andreas Forslöw
Andreas Forslöw

Reputation: 2738

How to assign keyboard shortcuts from user at runtime, WPF MVVM

As the title says, I'm looking for a way to assign keyboard shortcuts from a user at runtime, using WPF MVVM pattern. I know that I can define keyboard shortcuts at start like this:

<Window.InputBindings>
    <KeyBinding Command="{Binding MyCommand}" Key="A"/>
</Window.InputBindings>

I've also seen that there's a way to parse input bindings from a user. What I'm struggling with, however, is binding an inputbinding from my ViewModel to the MainWindow's InputBinding. I don't know how to achieve this. Here's the code in my MainWindow:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MainWindowViewModel();
    }
}

And here's some sample code from my ViewModel:

public partial class MainWindowViewModel : Window, INotifyPropertyChanged
{
    public MainWindowViewModel()
    {
        KeyBinding kb = new KeyBinding { Key = Key.T, Command = MyCommand };
        this.InputBindings.Add(kb);
    }
}

I know that the this.InputBindings.Add(kb); part should probably be replaced with something else; adding the keybinding to the MainWindow's InputBinding instead. However, I don't know how to do this with the MVVM pattern. Therefore: how would I go about doing this?

Upvotes: 2

Views: 550

Answers (2)

Andy
Andy

Reputation: 12276

If these are to be persisted so they work next time the user runs the app then you could consider creating a resource dictionary as a string or uncompiled flat file.

This would allow you to work with xaml as strings. You could write that to disk and xamlreader.load into a resource dictionary then merge it into application resources.

https://social.technet.microsoft.com/wiki/contents/articles/28797.wpf-dynamic-xaml.aspx

This approach offers several benefits:

The styling is easily persisted. You can try it out and see what's going on. You can write a file to disk using a model method called from your viewmodel.

Upvotes: 0

mm8
mm8

Reputation: 169200

You might define the input bindings in the view model, but you still need to add them to the view somehow.

You could for example use an attached behaviour that does this for you:

public class InputBindingsBehavior
{
    public static readonly DependencyProperty InputBindingsProperty = DependencyProperty.RegisterAttached(
        "InputBindings", typeof(IEnumerable<InputBinding>), typeof(InputBindingsBehavior), new PropertyMetadata(null, new PropertyChangedCallback(Callback)));

    public static void SetInputBindings(UIElement element, IEnumerable<InputBinding> value)
    {
        element.SetValue(InputBindingsProperty, value);
    }
    public static IEnumerable<InputBinding> GetInputBindings(UIElement element)
    {
        return (IEnumerable<InputBinding>)element.GetValue(InputBindingsProperty);
    }

    private static void Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        UIElement uiElement = (UIElement)d;
        uiElement.InputBindings.Clear();
        IEnumerable<InputBinding> inputBindings = e.NewValue as IEnumerable<InputBinding>;
        if (inputBindings != null)
        {
            foreach (InputBinding inputBinding in inputBindings)
                uiElement.InputBindings.Add(inputBinding);
        }
    }
}

View Model:

public partial class MainWindowViewModel
{
    public MainWindowViewModel()
    {
        KeyBinding kb = new KeyBinding { Key = Key.T, Command = MyCommand };
        InputBindings.Add(kb);
    }

    public List<InputBinding> InputBindings { get; } = new List<InputBinding>();

    public ICommand MyCommand => ...
}

View:

<Window x:Class="WpfApp1.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:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="Window18" Height="300" Width="300"
        local:InputBindingsBehavior.InputBindings="{Binding InputBindings}">
    <Grid>

    </Grid>
</Window>

Upvotes: 1

Related Questions