How to use the keyboard in a Mashup

Do you want to add keyboard support in a Mashup? In this blog I’ll show how to get a keyboard key to invoke an event and how to user keyboard shortcuts to switch beteen tabs. I’ll talk about focus and how to user accelerator keys to jump to an input field and some other nice to know topics for Mashup and feature development.

Focus and accelerator keys

Pressing tab in a form will move the keyboard focus to the next control. In WPF there are two main concepts that pertain to focus: keyboard focus and logical focus. Keyboard focus refers to the element that receives keyboard input and logical focus refers to the element in a focus scope that has focus.

There can be multiple elements that have logical focus in an application, but there may only be one element that has logical focus in a particular focus scope.

You set a focus scope in XAML by using the FocusManager.IsFocusScope=”True”.

<StackPanel Name="focusScope1" 
            FocusManager.IsFocusScope="True"
            Height="200" Width="200">
  <Button Name="button1" Height="50" Width="50"/>
  <Button Name="button2" Height="50" Width="50"/>
</StackPanel>

Let’s look at the M3 Example List and edit customer. When the Mashup is loaded keyboard focus is… nowhere. Pressing tab once puts the focus in the first TextBox and then the default order is as follows:

DefaultFocusOrder

First I want to change so that the detail area below the list is one focus scope and I want the focus to move from Name to your reference and then Save because that is most common. First I set FocusManager.IsFocusScope=”True” on the MIPanel and then I set KeyboardNavigation.TabNavigation=”Cycle”. Then I set TabIndex=”1″, then 2, 3. I leave the rest blank beucase I don’t need to set all nor do they have to be en absolute order.

When the Mashup starts I want the focus to be in the search field. So I modify the start-up event on the List and set the Activate property.
Activate

Keybinding to a button

When it comes to buttons and you only have one save button you can set IsDefault=”true” on the button and it will be click upon enter. But in my example that would not work becuase I have the search button to consider as well. So I use the Activate property on the Startup Event on the list.

<StackPanel Orientation="Horizontal" Margin="8,16,8,8">
  <StackPanel.InputBindings>
    <KeyBinding Key="Enter" Command="mashup:MashupInstance.MashupCommand">
      <KeyBinding.CommandParameter>
        <mashup:Events>
          <mashup:Event Activate="CustomerList" TargetName="CustomerList" TargetEventName="List">
            <mashup:Parameter TargetKey="CUNO" Value="{Binding ElementName=SearchCustomerTextBox, Path=Text}" />
          </mashup:Event>
        </mashup:Events>
      </KeyBinding.CommandParameter>
    </KeyBinding>
  </StackPanel.InputBindings>
  <TextBox Name="SearchCustomerTextBox" Width="200" Margin="0" />
  <Button Content="{mashup:Constant Key=Search,File=Mango.UI}" Width="100" VerticalAlignment="Center" Margin="8,0,5,0">
      <Button.CommandParameter>
        <mashup:Events>
          <mashup:Event SourceEventName="Click" TargetName="CustomerList" TargetEventName="List">
            <mashup:Parameter TargetKey="CUNO" Value="{Binding ElementName=SearchCustomerTextBox, Path=Text}" />
          </mashup:Event>
        </mashup:Events>
      </Button.CommandParameter>
    </Button>
  </StackPanel>

I’ve added an Activate to put the focus in the list as well but that does not get applied. Probably because of the fact that focus will not be set when the list gets reloaded. So how should I move focus to the list? I can keep the focus in the textbox and tab to get the to the list or I need to try and use another event on the list. I tried to use the Running event on the list. When the Mashup is started the Startup event is mapped to the List Event. After the list received the List event it will change and trigger the Running event as it gets the list so the Running event can be used to manually clear other controls that have loaded dependent information from the list. Running is a standard event that will be set on controls as they are busy. Let’s see if it will work to set focus a bit later. Nope that did not work. I did not find a way to put focus to the list once it was loaded. Perhaps if I had a ListCompleted event. Perhaps something that can be enhanced 🙂

I like Ctrl + S to invoke the save command.
Since I want the keyboard shortcut to be available in the entire Mashup I add the key binding to the top Grid. As it requires a modifier and not a single Key I need to set Gesture=”CTRL+S” and not the Key.

There is another potential issue with adding this Save command. All bindings in the Detail area are two-way bindings that will be updated once the Keyboard focus leaves the input button. That means that if I change the Customer name and press Ctrl+S before tabbing to another textbox the value in the data behind the control is not changed yet. So when you introduce a shortcut key to save a form that you can edit you need to change the default behaviour of the UpdateSourceTrigger for the binding in the TextBox. The default value for most dependency properties is PropertyChanged, while the Text property has a default value of LostFocus.

So I have to set the UpdateSourceTrigger to Property changed on all TextBoxes.

<TextBox Name="itemNameTextBox"
         Text="{Binding [CUNM],UpdateSourceTrigger=PropertyChanged}" />

Access Keys (a.k.a. mnemonics).

To improve keyboard support we can add the use of mnemonics. Mnemonics is the ability to navigate to a control using a key stroke combination. Access keys allow you to hit Alt + SomeKey to quickly interact with a control in the UI, such as hitting Alt + O to click an “OK” button. Labels supports access keys. You can associate a Label with another control, such as a TextBox, ComboBox, and allow the user to type an access key defined by the Label to set focus to the other control. You define the access key by writing _ in the text. Pressing down Alt will display all access keys in the UI. I’ll change the Customer name to Customer _name and _Your reference. Then I have to set Target on the label to my TextBox.

<Label Content="Customer _name:" Target="{Binding ElementName=nameTextBox}" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" />
<TextBox MaxLength="36" TabIndex="1" Name="nameTextBox" Text="{Binding [CUNM]}" Grid.Row="1" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Center" />

The final result

Here is the final result after all modifications.

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="clr-namespace:Mango.UI.Controls;assembly=Mango.UI" xmlns:mashup="clr-namespace:Mango.UI.Services.Mashup;assembly=Mango.UI" xmlns:m3="clr-namespace:MForms.Mashup;assembly=MForms">
  <Grid.Resources>
  </Grid.Resources>

  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="*" />
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
     <RowDefinition Height="Auto" />
    <RowDefinition Height="*" />
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
  </Grid.RowDefinitions>
  <Grid.InputBindings>
    <KeyBinding Gesture="CTRL+S" Command="mashup:MashupInstance.MashupCommand">
      <KeyBinding.CommandParameter>
        <mashup:Events>
          <mashup:Event TargetName="CustomerDetail" TargetEventName="Update" Activate="SearchCustomerTextBox">
          </mashup:Event>
        </mashup:Events>
      </KeyBinding.CommandParameter>
    </KeyBinding>
  </Grid.InputBindings>
  <StackPanel Orientation="Horizontal" Margin="8,16,8,8">
  <StackPanel.InputBindings>
    <KeyBinding Key="Enter" Command="mashup:MashupInstance.MashupCommand">
      <KeyBinding.CommandParameter>
        <mashup:Events>
          <mashup:Event TargetName="CustomerList" TargetEventName="List">
            <mashup:Parameter TargetKey="CUNO" Value="{Binding ElementName=SearchCustomerTextBox, Path=Text}" />
          </mashup:Event>
        </mashup:Events>
      </KeyBinding.CommandParameter>
    </KeyBinding>
  </StackPanel.InputBindings>
  <TextBox Name="SearchCustomerTextBox" Width="200" Margin="0" />
  <Button Content="{mashup:Constant Key=Search,File=Mango.UI}" Width="100" VerticalAlignment="Center" Margin="8,0,5,0">
      <Button.CommandParameter>
        <mashup:Events>
          <mashup:Event SourceEventName="Click" TargetName="CustomerList" TargetEventName="List">
            <mashup:Parameter TargetKey="CUNO" Value="{Binding ElementName=SearchCustomerTextBox, Path=Text}" />
          </mashup:Event>
        </mashup:Events>
      </Button.CommandParameter>
    </Button>
  </StackPanel>
  
  <m3:MIListPanel Name="CustomerList" Margin="8" Grid.Row="1">
    <m3:MIListPanel.Events>
      <mashup:Events>
        <mashup:Event SourceEventName="Startup" TargetEventName="List" Activate="SearchCustomerTextBox" SourceName="CustomerList" />
        <mashup:Event SourceName="CustomerDetail" TargetName="CustomerList" SourceEventName="UpdateComplete" TargetEventName="List">
          <mashup:Parameter SourceKey="CUNO" TargetKey="CUNO" />
        </mashup:Event>
      </mashup:Events>
    </m3:MIListPanel.Events>
    <m3:MIListPanel.DataSource>
      <m3:MIDataSource Program="CRS610MI" Transaction="LstByNumber" Type="List" InputFields="CUNO" OutputFields="CUNO,CUNM,CUA1,CUA2" MaxReturnedRecords="200" />
    </m3:MIListPanel.DataSource>
    <ListView ItemsSource="{Binding Items}" Style="{DynamicResource styleListView}" ItemContainerStyle="{DynamicResource styleListViewItem}">
      <ListView.View>
        <GridView ColumnHeaderContainerStyle="{DynamicResource styleGridViewColumnHeader}">
          <GridView.Columns>
            <GridViewColumn Header="Customer number" DisplayMemberBinding="{Binding [CUNO]}" />
            <GridViewColumn Header="Customer name" DisplayMemberBinding="{Binding [CUNM]}" />
            <GridViewColumn Header="Customer address 1" DisplayMemberBinding="{Binding [CUA1]}" />
            <GridViewColumn Header="Customer address 2" DisplayMemberBinding="{Binding [CUA2]}" />
          </GridView.Columns>
        </GridView>
      </ListView.View>
    </ListView>
  </m3:MIListPanel>
  <m3:MIPanel Name="CustomerDetail" FocusManager.IsFocusScope="True" KeyboardNavigation.TabNavigation="Cycle" Grid.Row="2" Margin="8,16,8,8">
    <m3:MIPanel.Events>
      <mashup:Events>
        <mashup:Event SourceName="CustomerList" TargetName="CustomerDetail" SourceEventName="CurrentItemChanged" TargetEventName="Get">
          <mashup:Parameter SourceKey="CUNO" TargetKey="CUNO" />
        </mashup:Event>
      </mashup:Events>
    </m3:MIPanel.Events>
    <m3:MIPanel.DataSources>
      <m3:MIDataSourceList>
        <m3:MIDataSource Program="CRS610MI" Transaction="GetBasicData" Type="Get" InputFields="CUNO" OutputFields="CUNM,CUNO,CUA1,CUA2,CUA3,CUA4,YREF,CSCD" />
        <m3:MIDataSource Program="CRS610MI" Transaction="ChgBasicData" Type="Update" InputFields="CUNO,CUA1,CUA2,CUA3,CUNM,CUA4,YREF,CSCD" MandatoryInputFields="CUNO" />
      </m3:MIDataSourceList>
    </m3:MIPanel.DataSources>
    <Grid>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="8" />
        <ColumnDefinition Width="200" />
        <ColumnDefinition Width="16" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="8" />
        <ColumnDefinition Width="200" />
        <ColumnDefinition Width="16" />
        <ColumnDefinition Width="*" />
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
      </Grid.RowDefinitions>
      <Label Content="Customer number:" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" />
      <TextBox MaxLength="10" Text="{Binding [CUNO]}" Grid.Row="0" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Center" IsEnabled="False" />
      <Label Content="Customer _name:" Target="{Binding ElementName=nameTextBox}" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" />
      <TextBox MaxLength="36" TabIndex="1" Name="nameTextBox" Text="{Binding [CUNM], UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Center" />
      <Label Content="Customer address 1:" Grid.Row="2" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" />
      <TextBox MaxLength="36" Text="{Binding [CUA1], UpdateSourceTrigger=PropertyChanged}" Grid.Row="2" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Center" />
      <Label Content="Customer address 2:" TabIndex="3" Grid.Row="3" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" />
      <TextBox MaxLength="36" Text="{Binding [CUA2], UpdateSourceTrigger=PropertyChanged}" Grid.Row="3" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Center" />
      <Label Content="Customer address 3:" Grid.Row="0" Grid.Column="4" HorizontalAlignment="Left" VerticalAlignment="Center" />
      <TextBox MaxLength="36" Text="{Binding [CUA3], UpdateSourceTrigger=PropertyChanged}" Grid.Row="0" Grid.Column="6" HorizontalAlignment="Stretch" VerticalAlignment="Center" />
      <Label Content="Customer address 4:" Grid.Row="1" Grid.Column="4" HorizontalAlignment="Left" VerticalAlignment="Center" />
      <TextBox MaxLength="36" Text="{Binding [CUA4], UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" Grid.Column="6" HorizontalAlignment="Stretch" VerticalAlignment="Center" />
      <Label Content="_Your reference 1:" Target="{Binding ElementName=referenceTextBox}" Grid.Row="2" Grid.Column="4" HorizontalAlignment="Left" VerticalAlignment="Center" />
      <TextBox MaxLength="30" TabIndex="2" Name="referenceTextBox" Text="{Binding [YREF], UpdateSourceTrigger=PropertyChanged}" Grid.Row="2" Grid.Column="6" HorizontalAlignment="Stretch" VerticalAlignment="Center" />
      <Label Content="Country code:" Grid.Row="3" Grid.Column="4" HorizontalAlignment="Left" VerticalAlignment="Center" />
    
    <m3:MIComboBox Grid.Row="3" Grid.Column="6" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="5" Width="190" SelectedValuePath="[STKY]" SelectedValue="{Binding [GetBasicData.CSCD], UpdateSourceTrigger=PropertyChanged}" SortField="TX15" InputValue1="CSCD" InputName1="STCO" DisplayMemberPath="[TX15]">
         <m3:MIComboBox.DataSource>
           <m3:MIDataSource Program="CRS175MI" Transaction="LstGeneralCode" IsCacheable="True" MaxReturnedRecords="0" />
         </m3:MIComboBox.DataSource>
      </m3:MIComboBox>
      <StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,16,0,0" Grid.ColumnSpan="7">
        <Button Content="{mashup:Constant Key=Save,File=Mango.UI}" Width="150" VerticalAlignment="Center" Margin="8,0,5,0">
          <Button.CommandParameter>
            <mashup:Events>
              <mashup:Event TargetEventName="Update" TargetName="CustomerDetail" />
            </mashup:Events>
          </Button.CommandParameter>
        </Button>
      </StackPanel>
    </Grid>
  </m3:MIPanel>
  <ui:StatusBar Name="StatusBar" Grid.Row="3" Grid.Column="0" />
</Grid>

Additional resources

Focus overview on MSDN
AccessKeys
MSDN Update Source trigger

For an example of Tab navigation and more stuff you can do check out the Tab Control (Common Examples) under the Help menu in the Mashup Designer.

8 thoughts on “How to use the keyboard in a Mashup

  1. mostafa

    Hi,
    Thank you for you effort.
    I have Jscript call mashup.
    From the mashup the user can write value on specific text field then he presses a button (as Copy value button), then the value returned to Jscript. I did it, but I need to press a key like “F5” to act like the button.
    how to bind key (like F5 ) to button click?

    1. karinpb Post author

      Hi,
      You do that with a keybinding but the question is – what panel is the button on? You do keybindings to a command so what is the command you would like to do on the click? You don’t think of it as F5 executing the click you think of it as mapped to the same command/action as the click.

      1. mostafa

        I have added event listener for button on JScript code that call the mashup. so when user press the button, a method in JScript invoked. but i didn’t know how to add event listener if key pressed. So i have two ways, first bind key to button click, second add key listener for key “F5” on JScript. but i don’t know how to code any of them.

  2. karinpb Post author

    Hi,
    I’m not sure if you can write a command in jscript. I think the approach you can try is to add an event handler on the form for F5. The event is keydown and you add it to the panel or the entire window. https://msdn.microsoft.com/en-us/library/system.windows.uielement.keydown(v=vs.110).aspx. Would it be enough to handle F5 in the M3 panel or would you like to handle it in the entire Mashup? Also there might be an issue with adding keyhandlers as M3 has it’s own keyhandler and F5 is a valid key for some programs.

    I might be able to do an example but not until next week.

      1. karinpb Post author

        Hi,
        The accelerator key should still work even if you have a binding or extension – but the text must have an _ (underscore) – before the accelerator key. Standard constants don’t have that. But you can translate and use accelerator-key. It takes som serious design work to decide those keys for a screen – in all languages.

    1. karinpb Post author

      Did you see my reply above? If you re-use exisiting MVXCON then the answer is no. If you create your own constants then yes – but you should consider using the translation capabilities within the Mashup framework – so you can decide where to put the underscore to get the accelerator key.

Comments are closed.