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 さんには何とかして頂きたいものです。