Search code examples
wpfmefinputbinding

How to add InputBindings to the Window after composition?


I am trying to master working with the MEF framework by implementing my own version of the well known Calculator example. The user interface is in WPF.

After composition the Viewmodel holds an ObservableCollection(Of IOperation) that is represented by a 'ListBox' of Buttons in the View. The text on each Button is a Char defined in IOperation as a property named Symbol. By binding the ListBox's SelectedItem to a property in the ViewModel I can just fire the Calculate method of the currently selected IOperation without knowing which Button was pressed. (Code illustrating this below.)

However, now I need to add InputBindings to the View , where each KeyBinding will be associated with the Symbol that is defined on the IOperation. It looks like that I cannot avoid implementing a Select Case(switch) statement to iterate through the Viewmodel's collection of IOperations to select the one on which the 'Calculate` method should be called.

Any ideas?

THE XAML:

<ListBox Grid.Column="1" Grid.Row="3" Name="OperationsList" 
         SelectedItem="{Binding ActiveOperation,Mode=TwoWay}"
         ItemsSource="{Binding Operations}">
         <ListBox.ItemsPanel>
             <ItemsPanelTemplate>
                 <UniformGrid IsItemsHost="True"/>
             </ItemsPanelTemplate>
         </ListBox.ItemsPanel>
         <ListBox.ItemTemplate>
             <DataTemplate>
                 <Button Content="{Binding Symbol}"
                         ToolTip="{Binding Description}"
                         Command="{Binding ElementName=OperationsList, Path=DataContext.ActivateOperation}"
                            Click="Button_Click_1"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

IOPERATION:

Public Interface IOperation
    Function Calculate() As Double
    Property Left As Double
    Property Right As Double
    Property Symbol As String
    Property Description As String
End Interface

VIEWMODEL:

Private _activateOperation As Command
    Public Property ActivateOperation As Command
        Get
            Return _activateOperation
        End Get
        Set(value As Command)
            _activateOperation = value
            OnPropertyChanged()
        End Set
    End Property

Public Property ActiveOperation As IOperation
    Get
        Return _compositor.ActiveOperation
    End Get
    Set(value As ICalculator)
        _compositor.ActiveOperation = value
        OnPropertyChanged()
    End Set
End Property

   Public ReadOnly Property Operations As ObservableCollection(Of IOperation)
        Get
            Return New ObservableCollection(Of ICalculator)(_compositor.Operations)
        End Get
    End Property

   Private Sub DoActivateOperation(Optional parameter As Object = Nothing)
        If Left.HasValue Then
            MakeCalculation()
        Else
            Left = CDbl(Display)
            ClearDisplay()
        End If
    End Sub

Solution

  • Code is a wee rough about the edges so adjust accordingly...

    // in ViewModel, after composition
    
    // map string symbols to input Keys (for brevity, using a Dictionary)
    Dictionary<string, System.Windows.Input.Key> SymbolsToKeys = new Dictionary<string, Key>
    {
      { "+", Key.Add },
      // etc.
    }
    
    // compose the list (expose it via a public property)
    // (ensure not to get confused: the KeyValuePair.Key is for the string symbol
    // ... and the KeyValuePair.Value is for the Sys.Win.Input.Key value)
    KeyBindings = (from symbolAndKey in SymbolsToKeys
                   join op in Operations on Equals(symbolAndKey.Key, op.Symbol)
                   select new KeyBinding(
                     new Command(op.Calculate)),
                     new KeyGesture(symbolAndKey.Value)
                  ).ToList();
    
    // in View, after Binding to ViewModel
    var vm = DataContext as YourViewModel;
    if(vm != null)
    {
       foreach(var keybinding in vm.KeyBindings){
           this.InputBindings.Add(keybinding);
       }
    }