InputMan for WPF の定義済みショートカットコマンドと編集可能 ComboBox の問題 その弐
先日の件を GrapeCity さんに報告したところ、早速以下の回答を頂けました。有難いです。
結論から申し上げますと、ご指摘いただいた現象は現時点では制限事項となります。
まず、ComboBox の場合、Up、Down キーは既定動作として他の動作が割り当てられているため、このような予約済みのキーにはコマンドを設定することができないようです。なお、Up、Down キー以外にも、Ctrl+C などいくつか予約済みのキーが存在します。
また、予約済みキー以外に割り当てた場合でも、NextControl は動作いたしません。これはIsEditable が True の ComboBox では、MoveFocus による次のコントロールが内部の TextBox になってしまっているためです。こちらにつきまして、ドキュメ ント等に注意書きや制限事項の記載がございませんでしたので、次期サービスパックでの追加を検討させていただきます。
ComboBox で NextControl へのショートカットコマンドを動作させるためには、新しい CommandBinding クラスを定義し、NextControl の既定動作をオーバーライドする以下の二つの方法がございます。
【方法1】
public MainWindow() { InitializeComponent(); CommandManager.RegisterClassCommandBinding(typeof(ComboBox), new CommandBinding(ControlNavigationCommands.NextControl, new ExecutedRoutedEventHandler(NextControl_Executed))); } private static void NextControl_Executed(object sender,ExecutedRoutedEventArgs e) { FrameworkElement fe = Keyboard.FocusedElement as FrameworkElement; if (fe != null) { fe.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)); } }
【方法2】private void NextControl_Executed(object sender, ExecutedRoutedEventArgs e) { FrameworkElement fe = Keyboard.FocusedElement as FrameworkElement; if (fe != null) { fe.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)); } }<ComboBox IsEditable="True"> <ComboBox.CommandBindings> <CommandBinding Command="im:ControlNavigationCommands.NextControl"Executed="NextControl_Executed"/> </ComboBox.CommandBindings> <ComboBox.InputBindings> <KeyBinding Key="Enter" Command="im:ControlNavigationCommands.NextControl"/> </ComboBox.InputBindings> </ComboBox>
なるほど・・・やはり標準 ComboBox の構造的な問題でしたか。
こちらでさらに調べたところ、また一つ法則性がつかめてきました。
ControlTemplate を使い、ComboBox のスタイル設定個所に以下のように KeyBinding を設定します。ComboBox の規定値 IsEditable= False の場合 Enter・Shift + Enter・Up・Down 全てのキーでフォーカス移動ができるようになります。
<Style TargetType="{x:Type ComboBox}"> ・・・・・ <Setter Property="im:InputBindingHelper.InputBindings"> <Setter.Value> <InputBindingCollection> <KeyBinding Key="Enter" Command="im:ControlNavigationCommands.NextControl"/> <KeyBinding Key="Enter" Modifiers="Shift" Command="im:ControlNavigationCommands.PreviousControl"/> <KeyBinding Key="Up" Command="im:ControlNavigationCommands.PreviousControl"/> <KeyBinding Key="Down" Command="im:ControlNavigationCommands.NextControl"/> </InputBindingCollection> </Setter.Value> </Setter> ・・・・ </Style>
ただし、IsEditable= True の場合、Shift + Enter は有効ですが、Enter・Up・Down は無効になります。そこで「ComboBoxEditableTemplate」という編集用コンボボックスのテンプレート内で Enter キーの KeyBinding を設定します。これで編集可能コンボボックスで Enter キーによるフォーカス移動が有効になります。
<ControlTemplate x:Key="ComboBoxEditableTemplate" TargetType="{x:Type ComboBox}"> <Grid SnapsToDevicePixels="true"> ・・・・・・・ <TextBox x:Name="PART_EditableTextBox" ・・・> ・・・・・・・ <im:InputBindingHelper.InputBindings> <InputBindingCollection> <KeyBinding Key="Enter" Command="im:ControlNavigationCommands.NextControl"/> </InputBindingCollection> </im:InputBindingHelper.InputBindings> </TextBox> ・・・・・・ </Grid> ・・・・・・ </ControlTemplate>
結局 NextControl を上書きするのも嫌なので、ControlTemplate だけで対処しました。これなら GrapeCity さんの提示したコードを書く必要はありません。
以下 Enter キーと Up・Down キーでフォーカス移動できるようにした ComboBox のControlTemplate のサンプルです。ただし IsEditable = True の場合、Up・Down によるフォーカス移動はできません。悲しい・・・;;
<Style x:Key="ComboBoxFocusVisual"> <Setter Property="Control.Template"> <Setter.Value> <ControlTemplate> <Rectangle Margin="4,4,21,4" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/> </ControlTemplate> </Setter.Value> </Setter> </Style> <SolidColorBrush x:Key="ListBorder" Color="#FF7F9DB9"/> <Style x:Key="ComboBoxTransparentButtonStyle" TargetType="{x:Type ToggleButton}"> <Setter Property="MinWidth" Value="0"/> <Setter Property="MinHeight" Value="0"/> <Setter Property="Width" Value="Auto"/> <Setter Property="Height" Value="Auto"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="Focusable" Value="false"/> <Setter Property="ClickMode" Value="Press"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ToggleButton}"> <Grid Background="{TemplateBinding Background}" SnapsToDevicePixels="true"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition SharedSizeGroup="ComboBoxButton" Width="Auto"/> </Grid.ColumnDefinitions> <Microsoft_Windows_Themes:ScrollChrome x:Name="Chrome" Grid.Column="1" HasOuterBorder="false" Padding="1,0,0,0" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsChecked}" Microsoft_Windows_Themes:ScrollChrome.ScrollGlyph="DownArrow" ThemeColor="NormalColor" Width="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"/> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="ComboBoxEditableTextBox" TargetType="{x:Type TextBox}"> <Setter Property="OverridesDefaultStyle" Value="true"/> <Setter Property="AllowDrop" Value="true"/> <Setter Property="MinWidth" Value="0"/> <Setter Property="MinHeight" Value="0"/> <Setter Property="FocusVisualStyle" Value="{x:Null}"/> <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/> <Setter Property="Stylus.IsFlicksEnabled" Value="False"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TextBox}"> <ScrollViewer x:Name="PART_ContentHost" Background="Transparent" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/> </ControlTemplate> </Setter.Value> </Setter> </Style> <ControlTemplate x:Key="ComboBoxEditableTemplate" TargetType="{x:Type ComboBox}"> <Grid SnapsToDevicePixels="true"> <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="1"> <Grid Grid.IsSharedSizeScope="true"> <Grid.ColumnDefinitions> <ColumnDefinition Width="1"/> <ColumnDefinition Width="*"/> <ColumnDefinition SharedSizeGroup="ComboBoxButton" Width="Auto"/> </Grid.ColumnDefinitions> <TextBox x:Name="PART_EditableTextBox" Grid.Column="1" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsReadOnly="{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}" Margin="{TemplateBinding Padding}" Style="{StaticResource ComboBoxEditableTextBox}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"> <im:InputBindingHelper.InputBindings> <InputBindingCollection> <KeyBinding Key="Enter" Command="im:ControlNavigationCommands.NextControl"/> </InputBindingCollection> </im:InputBindingHelper.InputBindings> </TextBox> <ToggleButton Background="{x:Null}" Grid.ColumnSpan="3" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ComboBoxTransparentButtonStyle}"/> </Grid> </Border> <Popup x:Name="PART_Popup" AllowsTransparency="true" Focusable="false" IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom"> <Microsoft_Windows_Themes:SystemDropShadowChrome x:Name="Shdw" Color="Transparent" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{TemplateBinding ActualWidth}"> <Border x:Name="DropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"> <ScrollViewer x:Name="DropDownScrollViewer"> <Grid RenderOptions.ClearTypeHint="Enabled"> <Canvas HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0"> <Rectangle x:Name="OpaqueRect" Fill="{Binding Background, ElementName=DropDownBorder}" Height="{Binding ActualHeight, ElementName=DropDownBorder}" Width="{Binding ActualWidth, ElementName=DropDownBorder}"/> </Canvas> <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> </Grid> </ScrollViewer> </Border> </Microsoft_Windows_Themes:SystemDropShadowChrome> </Popup> </Grid> <ControlTemplate.Triggers> <Trigger Property="HasItems" Value="false"> <Setter Property="MinHeight" TargetName="DropDownBorder" Value="95"/> </Trigger> <Trigger Property="IsEnabled" Value="false"> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/> </Trigger> <Trigger Property="IsGrouping" Value="true"> <Setter Property="ScrollViewer.CanContentScroll" Value="false"/> </Trigger> <Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true"> <Setter Property="Margin" TargetName="Shdw" Value="0,0,5,5"/> <Setter Property="Color" TargetName="Shdw" Value="#71000000"/> </Trigger> <Trigger Property="ScrollViewer.CanContentScroll" SourceName="DropDownScrollViewer" Value="false"> <Setter Property="Canvas.Top" TargetName="OpaqueRect" Value="{Binding VerticalOffset, ElementName=DropDownScrollViewer}"/> <Setter Property="Canvas.Left" TargetName="OpaqueRect" Value="{Binding HorizontalOffset, ElementName=DropDownScrollViewer}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> <Style TargetType="{x:Type ComboBox}"> <Setter Property="FocusVisualStyle" Value="{StaticResource ComboBoxFocusVisual}"/> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/> <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/> <Setter Property="BorderBrush" Value="{StaticResource ListBorder}"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Padding" Value="1"/> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/> <Setter Property="ScrollViewer.CanContentScroll" Value="true"/> <Setter Property="ScrollViewer.PanningMode" Value="Both"/> <Setter Property="Stylus.IsFlicksEnabled" Value="False"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="im:InputBindingHelper.InputBindings"> <Setter.Value> <InputBindingCollection> <KeyBinding Key="Enter" Command="im:ControlNavigationCommands.NextControl"/> <KeyBinding Key="Enter" Modifiers="Shift" Command="im:ControlNavigationCommands.PreviousControl"/> <KeyBinding Key="Up" Command="im:ControlNavigationCommands.PreviousControl"/> <KeyBinding Key="Down" Command="im:ControlNavigationCommands.NextControl"/> </InputBindingCollection> </Setter.Value> </Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ComboBox}"> <Grid SnapsToDevicePixels="true"> <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="1"> <Grid Grid.IsSharedSizeScope="true"> <Grid.ColumnDefinitions> <ColumnDefinition Width="1"/> <ColumnDefinition Width="*"/> <ColumnDefinition SharedSizeGroup="ComboBoxButton" Width="Auto"/> </Grid.ColumnDefinitions> <Border x:Name="SelectedItemBorder" Grid.ColumnSpan="2" Margin="{TemplateBinding Padding}"/> <ContentPresenter ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" Content="{TemplateBinding SelectionBoxItem}" Grid.Column="1" ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> <ToggleButton Grid.ColumnSpan="3" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ComboBoxTransparentButtonStyle}"/> </Grid> </Border> <Popup x:Name="PART_Popup" AllowsTransparency="true" Focusable="false" IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom"> <Microsoft_Windows_Themes:SystemDropShadowChrome x:Name="Shdw" Color="Transparent" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{TemplateBinding ActualWidth}"> <Border x:Name="DropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"> <ScrollViewer x:Name="DropDownScrollViewer"> <Grid RenderOptions.ClearTypeHint="Enabled"> <Canvas HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0"> <Rectangle x:Name="OpaqueRect" Fill="{Binding Background, ElementName=DropDownBorder}" Height="{Binding ActualHeight, ElementName=DropDownBorder}" Width="{Binding ActualWidth, ElementName=DropDownBorder}"/> </Canvas> <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> </Grid> </ScrollViewer> </Border> </Microsoft_Windows_Themes:SystemDropShadowChrome> </Popup> </Grid> <ControlTemplate.Triggers> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsSelectionBoxHighlighted" Value="true"/> <Condition Property="IsDropDownOpen" Value="false"/> </MultiTrigger.Conditions> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/> </MultiTrigger> <Trigger Property="IsSelectionBoxHighlighted" Value="true"> <Setter Property="Background" TargetName="SelectedItemBorder" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/> </Trigger> <Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true"> <Setter Property="Margin" TargetName="Shdw" Value="0,0,5,5"/> <Setter Property="Color" TargetName="Shdw" Value="#71000000"/> </Trigger> <Trigger Property="HasItems" Value="false"> <Setter Property="MinHeight" TargetName="DropDownBorder" Value="95"/> </Trigger> <Trigger Property="IsEnabled" Value="false"> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/> </Trigger> <Trigger Property="IsGrouping" Value="true"> <Setter Property="ScrollViewer.CanContentScroll" Value="false"/> </Trigger> <Trigger Property="ScrollViewer.CanContentScroll" SourceName="DropDownScrollViewer" Value="false"> <Setter Property="Canvas.Top" TargetName="OpaqueRect" Value="{Binding VerticalOffset, ElementName=DropDownScrollViewer}"/> <Setter Property="Canvas.Left" TargetName="OpaqueRect" Value="{Binding HorizontalOffset, ElementName=DropDownScrollViewer}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="IsEditable" Value="true"> <Setter Property="IsTabStop" Value="false"/> <Setter Property="Padding" Value="0,1"/> <Setter Property="Template" Value="{StaticResource ComboBoxEditableTemplate}"/> </Trigger> </Style.Triggers> </Style>
しかし調べれば調べるほど、WPF の標準 ComboBox が Forms に比べて明らかにデグレードしているので、MS さんには何とかして頂きたいものです。