0

I've got this WPF MVVM window app, that I'm a bit stuck on KeyBinding on buttons.

What I've done so far is create an ObservableCollection of CommandVM's. Which hold a bunch of properties to set up button properties in the XAML.

In my MainWindow VM I initialize an OC of CommandVMs like so:

    public ObservableCollection<CommandVM> Commands { get; set; }

    CommandVM cmdVMInsert;
    CommandVM cmdVMEdit;
    CommandVM cmdVMDelete;
    CommandVM cmdVMCommit;
    CommandVM cmdVMRollback;
    CommandVM cmdVMMerge;

    public MainWindowViewModel()
    {
        cmdVMInsert = new CommandVM { LeftMargin = 0, CommandDisplay = "Authorize", IconGeometry = Application.Current.Resources["InsertIcon"] as Geometry, BoundKey = Key.OemPlus, Message = new CommandMessage { Command = CommandType.Insert }, IsEnabled = false };
        cmdVMEdit = new CommandVM { LeftMargin = 0, CommandDisplay = "Edit", IconGeometry = Application.Current.Resources["EditIcon"] as Geometry, BoundKey = Key.Space, Message = new CommandMessage { Command = CommandType.Edit }, IsEnabled = false };
        cmdVMDelete = new CommandVM { LeftMargin = 0, CommandDisplay = "Delete", IconGeometry = Application.Current.Resources["DeleteIcon"] as Geometry, BoundKey = Key.Delete, Message = new CommandMessage { Command = CommandType.Delete }, IsEnabled = false };
        cmdVMCommit = new CommandVM { LeftMargin = 25, CommandDisplay = "Commit", IconGeometry = Application.Current.Resources["SaveIcon"] as Geometry, BoundModifierKeys = new ModifierKeys[] { ModifierKeys.Control }, BoundKey = Key.S, Message = new CommandMessage { Command = CommandType.Commit }, IsEnabled = false };
        cmdVMRollback = new CommandVM { LeftMargin = 0, CommandDisplay = "Rollback and refresh", IconGeometry = Application.Current.Resources["RefreshIcon"] as Geometry, Message = new CommandMessage { Command = CommandType.Rollback }, IsEnabled = false };
        cmdVMMerge = new CommandVM { LeftMargin = 0, CommandDisplay = "Merge", IconGeometry = Application.Current.Resources["RefreshIcon"] as Geometry, Message = new CommandMessage { Command = CommandType.Merge }, IsEnabled = true };

        ObservableCollection<CommandVM> commands = new ObservableCollection<CommandVM>
        {
            cmdVMInsert,
            cmdVMEdit,
            cmdVMDelete,
            cmdVMCommit,
            cmdVMRollback,
            cmdVMMerge
        };
        Commands = commands;
        RaisePropertyChanged("Commands");
    }

The CommandVM class

public class CommandVM : ViewModelBase
{
    public bool VM_StyleCommand = false;

    private int f_LeftMargin = 0;
    public int LeftMargin
    {
        get
        {
            return f_LeftMargin;
        }
        set
        {
            f_LeftMargin = value;
        }
    }

    private string f_VM_DisplayName = "";
    public string VM_DisplayName
    {
        get
        {
            return f_VM_DisplayName;
        }
        set
        {
            f_VM_DisplayName = value;
            RaisePropertyChanged("CommandDisplay");
        }
    }

    private string f_CommandDisplay = "";
    public string CommandDisplay
    {
        get
        {
            string result = f_CommandDisplay;
            if (VM_StyleCommand && !VM_DisplayName.Equals(""))
            {
                result = result + " " + VM_DisplayName;
            }
            return result;
        }
        set
        {
            f_CommandDisplay = value;
            RaisePropertyChanged();
        }
    }

    public CommandMessage Message { get; set; }
    public RelayCommand Send { get; private set; }
    public Geometry IconGeometry { get; set; }
    public ModifierKeys[] BoundModifierKeys;// { get; set; }
    public Key BoundKey { get; set; }

    public string GetBoundKey
    {
        get
        {
            return BoundKey.ToString();
        }
    }

    public string GetBoundModifierKeys
    {
        get
        {
            string builder = "";
            if (BoundModifierKeys != null)
            {
                foreach (var ModifierKey in BoundModifierKeys)
                {
                    if (!builder.Equals(""))
                        builder += "+";
                    builder += ModifierKey.ToString();
                }
            }
            return builder;
        }
    }

    private bool f_IsEnabled = false;
    public bool IsEnabled
    {
        get
        {
            return f_IsEnabled;
        }
        set
        {
            f_IsEnabled = value;
            RaisePropertyChanged("IsEnabled");
        }
    }

    private bool canExecute = true;
    public bool CanExecute
    {
        get
        {
            return canExecute = IsEnabled;
        }
        set
        {
            canExecute = value;
            RaiseCanExecuteChanged();
        }
    }

    public CommandVM()
    {
        Send = new RelayCommand(() => SendExecute());
    }

    private void SendExecute()
    {
        Messenger.Default.Send<CommandMessage>(Message);
    }
    public void RaiseCanExecuteChanged()
    {
        CommandManager.InvalidateRequerySuggested();
    }
}

In the XAML:

<Window x:Class="MobileDeviceAuthenticator.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:MobileDeviceAuthenticator"
        xmlns:conv="clr-namespace:WPF_Converters;assembly=WPF_Converters"
        mc:Ignorable="d"
        Title="Device Authorization" 
        Height="381" 
        Width="879" >

    <Window.Resources>

        <DataTemplate DataType="{x:Type local:MobileDeviceRequestsViewModel}">
            <local:MobileDeviceRequestsView/>
        </DataTemplate>

        <DataTemplate DataType="{x:Type local:UserMobileDevicesViewModel}">
            <local:UserMobileDevicesView/>
        </DataTemplate>

        <conv:MarginConverter x:Key="marginConverter"/>

    </Window.Resources>

    <Grid x:Name="GridContainer" Margin="5" >
        <Grid Margin="0,25,0,0" AllowDrop="True">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="10"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>

            <ListView Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"
                      ItemsSource="{Binding Commands}" 
                      BorderBrush="Transparent" 
                      FontSize="12" FontWeight="Bold" 
                      ScrollViewer.CanContentScroll="False" >
                <ListView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Horizontal"/>
                    </ItemsPanelTemplate>
                </ListView.ItemsPanel>
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <Button Command="{Binding Send}" 
                                BorderThickness="0" 
                                Margin="{Binding LeftMargin, Converter={StaticResource marginConverter}}" 
                                Padding="0" 
                                IsEnabled="{Binding IsEnabled, Mode=TwoWay}"
                                        >
                            <Path Data="{Binding IconGeometry}" Stretch="Uniform" 
                                            Style="{StaticResource PathOpacityStyle}"
                                            Fill="{StaticResource MidDullBrush}" Width="25" Height="25"/>
                            <Button.ToolTip>
                                <TextBlock Text="{Binding CommandDisplay, Mode=TwoWay}"/>
                            </Button.ToolTip>
                            <Button.InputBindings>
                                <KeyBinding Key="{Binding GetBoundKey}" 
                                            Modifiers="{Binding GetBoundModifierKeys}" 
                                            Command="{Binding Send}"/>
                            </Button.InputBindings>
                        </Button>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>
    </Grid>
</Window>

The buttons themselves work nicely, however, the Button.KeyBinding does not, I've seen other KeyBindings on other solutions that get attached at Window level.

My question is: Is there a way with my current CommandVM with the Key properties to somehow hookup the KeyBindings to the Window

[Edit] Changing the question a little to achieve the same result.

Can I achieve the same sort of thing, but as below, still using the ObservableCollection of commands and some sort of ItemsControl iteration

<Window.InputBindings>
    <!-- Start items iteration with ItemsSource set to 'Commands'-->
    <KeyBinding Key="{Binding GetBoundKey}" Modifiers="{Binding GetBoundModifierKeys}" Command="{Binding Send}"/>
    <!-- end iteration -->
</Window.InputBindings>
Hank
  • 2,456
  • 3
  • 35
  • 83
  • So you want to hook up window keyboard events for ListViewItem buttons which change dynamically based on bindings – TheGeneral Feb 18 '18 at 07:16
  • Yes, but just to clarify, they load dynamically and as per above in the first piece of code, the CommandVM's have different property values, but their properties won't change dynamically after that initialization. – Hank Feb 18 '18 at 07:32
  • Have you checked you this example? https://stackoverflow.com/questions/1361350/keyboard-shortcuts-in-wpf – TheGeneral Feb 18 '18 at 07:36
  • no, just taking a look now – Hank Feb 18 '18 at 07:38
  • Hmm just looks like more of the same standard stuff put a window keybindings with routed commands. – Hank Feb 18 '18 at 09:18
  • I think the point is, you will need to add the keyboard binding at the window level dynamically every time you refresh your list. and bind it to your command, the alternative is hook the keydown event, message your mainvm with the key and iterate through your list to find the key – TheGeneral Feb 18 '18 at 09:30
  • What about instead of writing out each KeyBinding in the XAML line by line, is there a way to dynamically add them in XAML using some sort of ItemsSource bound to my collection. – Hank Feb 18 '18 at 22:50
  • You wouldnt have to write our each binding, I'll have a look at work today for you – TheGeneral Feb 18 '18 at 23:03
  • Why are you defining key bindings in a view model in the first place...? These belong to the view. – mm8 Feb 19 '18 at 12:47
  • Hi @mm8 I'm not, I'm defining commands – Hank Feb 20 '18 at 01:18
  • I've added a little to the bottom of the question – Hank Feb 20 '18 at 01:45
  • Create the KeyBindings *programmatically* in the view. – mm8 Feb 20 '18 at 15:06
  • Can I not do it straight into XAML? If not, then I will it via c# – Hank Feb 21 '18 at 04:14

0 Answers0