바로 가기 키를 위한 KeyBinding이 있는 확장 MenuItem
15535 단어 VisualStudio.NETWPFXamlC#
개요
편의를 위해 앱에서 특정 MenuItem에 키보드 단축키(ex. Ctrl+O로 '열기')를 도입하는 경우가 많습니다.
그러나, WPF 표준의 MenuItem에서는 KeyGesture의 설명 표시는 할 수 있어도, 그 검출은 할 수 없습니다.
그 때문에 MenuItem과는 별도로, Window 바로 아래에 검출하는 KeyBinding를 써야 합니다.
이것은 유사한 설명을 멀리 떨어진 곳에 쓰게 되어 버그의 원인이 됩니다.
그래서 KeyBinding을받을 수있는 확장 MenuItem을 만들어이 문제를 해결합니다.
변경 전
실행 화면
이런 앱을 소재로 합니다.
Menu->Hoge를 선택하거나 Ctrl+H를 누르면 TextBox에 "-Hoge"가 추가됩니다.
보기
View에서는 Grid로 구분하여 위로 Menu, 아래에 TextBox가 놓여 있을 뿐입니다.
MainWindow.xaml<Window
x:Class="MenuExt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MenuExt"
Width="525"
Height="150">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.InputBindings>
<KeyBinding Command="{Binding HogeCommand}" Gesture="Ctrl+H" />
</Window.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="File(_F)">
<MenuItem
Header="Hoge"
Command="{Binding HogeCommand}"
InputGestureText="Ctrl+H" />
</MenuItem>
</Menu>
<TextBox Grid.Row="1" Text="{Binding MyText.Value}" />
</Grid>
</Window>
코드 숨김에는 아무것도 쓰지 않으므로 생략합니다.
ViewModel
ViewModel은 이번 주제와 별로 관련이 없지만 다음과 같습니다.HogeCommand
가 사용되면 MyText
에 "-Hoge"를 추가합니다.
ReactiveProperty를 사용하고 있지만 다른 MVVM 라이브러리에서도 이야기는 동일하다고 생각합니다.
MainWindowViewModelusing Reactive.Bindings;
using System;
namespace MenuExt
{
public class MainWindowViewModel
{
public ReactiveCommand HogeCommand { get; } = new ReactiveCommand();
public ReactiveProperty<string> MyText { get; } = new ReactiveProperty<string>();
public MainWindowViewModel()
{
HogeCommand.Subscribe(_ =>
Text.Value += "-Hoge");
}
}
}
문제점
View에는 Window 바로 아래의 KeyBinding과 MenuItem이 있습니다.
<KeyBinding Command="{Binding HogeCommand}" Gesture="Ctrl+H" />
<MenuItem
Header="Hoge"
Command="{Binding HogeCommand}"
InputGestureText="Ctrl+H" />
둘 다 HogeCommand
와 KeyGesture(*)를 포함합니다.
지금은 짧기 때문에 문제에는 느끼지 않지만, 이것이 방대한 Menu를 가지는 앱(ex. VisualStudio)이 되면, 양자가 엇갈려 있어도, 코드로부터 발견하는 것은 곤란할 것입니다.
(*) MenuItem의 InputGestureText는 단순히 지정된 텍스트를 Menu의 오른쪽에 표시하는 것만으로 기능은 없습니다.
InputGestureText에 썼는데 응답하지 않습니다! 라는 함정은 이것을 이용한 전원이 들어가는 통과 의례와 같습니다.
이름을 GestureCaption 라든지 가리키면, 좀 더 혼란이 적어 졌는데.
해결책
이 문제를 해결하는 첫 번째 방법은 MenuItem 자체에 KeyBinding 기능을 제공하는 것입니다.
그러나 KeyBinding은 그 때 Focus가 있는 컨트롤로만 검출할 수 있습니다.
즉 MenuItem에 구현해도 선택되고 있을 때 이외는 동작하지 않는 무의미한 KeyBinding이 되어 버립니다.
그래서 KeyBinding 자체는 평소대로 Window 바로 아래에서 구현하고, 그것을 x:Name으로 확장 MenuItem에 건네주고, 거기에서 Command와 KeyGesture를 동기화합니다.
확장 MenuItem 클래스
우선 KeyBinding를 받는 MenuItem을 상속한 확장 MenuItem입니다.
MenuItemKeyBinded.csusing System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace MenuExt
{
/// <summary>
/// キーバインド付きMenuItem
/// </summary>
public class MenuItemKeyBinded : MenuItem
{
/// <summary>
/// KeyBinding依存関係プロパティ
/// </summary>
public KeyBinding KeyBind
{
get { return (KeyBinding)GetValue(KeyBindProperty); }
set { SetValue(KeyBindProperty, value); }
}
public static readonly DependencyProperty KeyBindProperty =
DependencyProperty.Register(nameof(KeyBind), typeof(KeyBinding), typeof(MenuItemKeyBinded),
new PropertyMetadata(new KeyBinding(),
//KeyBindingが指定された時に呼ばれるコールバック
KeyBindPropertyChanged));
private static void KeyBindPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var menuItemKB = d as MenuItemKeyBinded;
var kb = e.NewValue as KeyBinding;
//KeyBindingに結び付けられたコマンドをこのMenuItemのCommandに反映
menuItemKB.Command = kb.Command;
//KeyBindingのローカライズされた文字列("Ctrl"など)をこのMenuItemのInputGestureTextに反映
menuItemKB.InputGestureText = (kb.Gesture as KeyGesture).GetDisplayStringForCulture(CultureInfo.CurrentCulture);
}
}
}
종속성 속성으로 KeyBinding을 받고 내용을 상속받은 MenuItem의 Command와 InputGestureText에 전달합니다.
InputGestureText에는 KeyGesture를 현재의 Culture로 지역화한 캐릭터 라인 (ex. "Ctrl")을 설정합니다.
또한 KeyGesture의 ModifierKeys와 DisplayString을 사용해도 "Control"이나 공백으로되어있어 잘 움직이지 않습니다.
보기
사용할 때는 Window의 KeyBinding 측에 Command와 Gesture를 써, x:Name 경유로 확장 MenuItem에 KeyBinding 마다 건네줍니다.
MainWindow.xaml<Window
x:Class="MenuExt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MenuExt"
Width="525"
Height="150">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.InputBindings>
<KeyBinding
x:Name="HogeKeyBinding"
Command="{Binding HogeCommand}"
Gesture="Ctrl+H" />
</Window.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="File(_F)">
<local:MenuItemKeyBinded
Header="Hoge"
KeyBind="{Binding ElementName=HogeKeyBinding}" />
</MenuItem>
</Menu>
<TextBox Grid.Row="1" Text="{Binding MyText.Value}" />
</Grid>
</Window>
ViewModel과 실행 결과는 동일합니다.
환경
VisualStudio2017
.NET Framework 4.6
C#6
별해
이번에는 KeyBinding에 x:Name을 붙여 KeyBinding 경유로 Command와 KeyGesture를 공유했습니다.
또 다른 방법은 Command 측에 KeyGesture를 포함하는 방법입니다.
How to Display Working Keyboard Shortcut for Menu Items?-stackoverflow
중단 당 CommandWithHotkey의 답변
Reference
이 문제에 관하여(바로 가기 키를 위한 KeyBinding이 있는 확장 MenuItem), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/soi/items/ec5982291154d94beb54
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
실행 화면
이런 앱을 소재로 합니다.
Menu->Hoge를 선택하거나 Ctrl+H를 누르면 TextBox에 "-Hoge"가 추가됩니다.
보기
View에서는 Grid로 구분하여 위로 Menu, 아래에 TextBox가 놓여 있을 뿐입니다.
MainWindow.xaml
<Window
x:Class="MenuExt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MenuExt"
Width="525"
Height="150">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.InputBindings>
<KeyBinding Command="{Binding HogeCommand}" Gesture="Ctrl+H" />
</Window.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="File(_F)">
<MenuItem
Header="Hoge"
Command="{Binding HogeCommand}"
InputGestureText="Ctrl+H" />
</MenuItem>
</Menu>
<TextBox Grid.Row="1" Text="{Binding MyText.Value}" />
</Grid>
</Window>
코드 숨김에는 아무것도 쓰지 않으므로 생략합니다.
ViewModel
ViewModel은 이번 주제와 별로 관련이 없지만 다음과 같습니다.
HogeCommand
가 사용되면 MyText
에 "-Hoge"를 추가합니다.ReactiveProperty를 사용하고 있지만 다른 MVVM 라이브러리에서도 이야기는 동일하다고 생각합니다.
MainWindowViewModel
using Reactive.Bindings;
using System;
namespace MenuExt
{
public class MainWindowViewModel
{
public ReactiveCommand HogeCommand { get; } = new ReactiveCommand();
public ReactiveProperty<string> MyText { get; } = new ReactiveProperty<string>();
public MainWindowViewModel()
{
HogeCommand.Subscribe(_ =>
Text.Value += "-Hoge");
}
}
}
문제점
View에는 Window 바로 아래의 KeyBinding과 MenuItem이 있습니다.
<KeyBinding Command="{Binding HogeCommand}" Gesture="Ctrl+H" />
<MenuItem
Header="Hoge"
Command="{Binding HogeCommand}"
InputGestureText="Ctrl+H" />
둘 다 HogeCommand
와 KeyGesture(*)를 포함합니다.
지금은 짧기 때문에 문제에는 느끼지 않지만, 이것이 방대한 Menu를 가지는 앱(ex. VisualStudio)이 되면, 양자가 엇갈려 있어도, 코드로부터 발견하는 것은 곤란할 것입니다.
(*) MenuItem의 InputGestureText는 단순히 지정된 텍스트를 Menu의 오른쪽에 표시하는 것만으로 기능은 없습니다.
InputGestureText에 썼는데 응답하지 않습니다! 라는 함정은 이것을 이용한 전원이 들어가는 통과 의례와 같습니다.
이름을 GestureCaption 라든지 가리키면, 좀 더 혼란이 적어 졌는데.
해결책
이 문제를 해결하는 첫 번째 방법은 MenuItem 자체에 KeyBinding 기능을 제공하는 것입니다.
그러나 KeyBinding은 그 때 Focus가 있는 컨트롤로만 검출할 수 있습니다.
즉 MenuItem에 구현해도 선택되고 있을 때 이외는 동작하지 않는 무의미한 KeyBinding이 되어 버립니다.
그래서 KeyBinding 자체는 평소대로 Window 바로 아래에서 구현하고, 그것을 x:Name으로 확장 MenuItem에 건네주고, 거기에서 Command와 KeyGesture를 동기화합니다.
확장 MenuItem 클래스
우선 KeyBinding를 받는 MenuItem을 상속한 확장 MenuItem입니다.
MenuItemKeyBinded.csusing System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace MenuExt
{
/// <summary>
/// キーバインド付きMenuItem
/// </summary>
public class MenuItemKeyBinded : MenuItem
{
/// <summary>
/// KeyBinding依存関係プロパティ
/// </summary>
public KeyBinding KeyBind
{
get { return (KeyBinding)GetValue(KeyBindProperty); }
set { SetValue(KeyBindProperty, value); }
}
public static readonly DependencyProperty KeyBindProperty =
DependencyProperty.Register(nameof(KeyBind), typeof(KeyBinding), typeof(MenuItemKeyBinded),
new PropertyMetadata(new KeyBinding(),
//KeyBindingが指定された時に呼ばれるコールバック
KeyBindPropertyChanged));
private static void KeyBindPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var menuItemKB = d as MenuItemKeyBinded;
var kb = e.NewValue as KeyBinding;
//KeyBindingに結び付けられたコマンドをこのMenuItemのCommandに反映
menuItemKB.Command = kb.Command;
//KeyBindingのローカライズされた文字列("Ctrl"など)をこのMenuItemのInputGestureTextに反映
menuItemKB.InputGestureText = (kb.Gesture as KeyGesture).GetDisplayStringForCulture(CultureInfo.CurrentCulture);
}
}
}
종속성 속성으로 KeyBinding을 받고 내용을 상속받은 MenuItem의 Command와 InputGestureText에 전달합니다.
InputGestureText에는 KeyGesture를 현재의 Culture로 지역화한 캐릭터 라인 (ex. "Ctrl")을 설정합니다.
또한 KeyGesture의 ModifierKeys와 DisplayString을 사용해도 "Control"이나 공백으로되어있어 잘 움직이지 않습니다.
보기
사용할 때는 Window의 KeyBinding 측에 Command와 Gesture를 써, x:Name 경유로 확장 MenuItem에 KeyBinding 마다 건네줍니다.
MainWindow.xaml<Window
x:Class="MenuExt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MenuExt"
Width="525"
Height="150">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.InputBindings>
<KeyBinding
x:Name="HogeKeyBinding"
Command="{Binding HogeCommand}"
Gesture="Ctrl+H" />
</Window.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="File(_F)">
<local:MenuItemKeyBinded
Header="Hoge"
KeyBind="{Binding ElementName=HogeKeyBinding}" />
</MenuItem>
</Menu>
<TextBox Grid.Row="1" Text="{Binding MyText.Value}" />
</Grid>
</Window>
ViewModel과 실행 결과는 동일합니다.
환경
VisualStudio2017
.NET Framework 4.6
C#6
별해
이번에는 KeyBinding에 x:Name을 붙여 KeyBinding 경유로 Command와 KeyGesture를 공유했습니다.
또 다른 방법은 Command 측에 KeyGesture를 포함하는 방법입니다.
How to Display Working Keyboard Shortcut for Menu Items?-stackoverflow
중단 당 CommandWithHotkey의 답변
Reference
이 문제에 관하여(바로 가기 키를 위한 KeyBinding이 있는 확장 MenuItem), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/soi/items/ec5982291154d94beb54
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
<KeyBinding Command="{Binding HogeCommand}" Gesture="Ctrl+H" />
<MenuItem
Header="Hoge"
Command="{Binding HogeCommand}"
InputGestureText="Ctrl+H" />
이 문제를 해결하는 첫 번째 방법은 MenuItem 자체에 KeyBinding 기능을 제공하는 것입니다.
그러나 KeyBinding은 그 때 Focus가 있는 컨트롤로만 검출할 수 있습니다.
즉 MenuItem에 구현해도 선택되고 있을 때 이외는 동작하지 않는 무의미한 KeyBinding이 되어 버립니다.
그래서 KeyBinding 자체는 평소대로 Window 바로 아래에서 구현하고, 그것을 x:Name으로 확장 MenuItem에 건네주고, 거기에서 Command와 KeyGesture를 동기화합니다.
확장 MenuItem 클래스
우선 KeyBinding를 받는 MenuItem을 상속한 확장 MenuItem입니다.
MenuItemKeyBinded.cs
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace MenuExt
{
/// <summary>
/// キーバインド付きMenuItem
/// </summary>
public class MenuItemKeyBinded : MenuItem
{
/// <summary>
/// KeyBinding依存関係プロパティ
/// </summary>
public KeyBinding KeyBind
{
get { return (KeyBinding)GetValue(KeyBindProperty); }
set { SetValue(KeyBindProperty, value); }
}
public static readonly DependencyProperty KeyBindProperty =
DependencyProperty.Register(nameof(KeyBind), typeof(KeyBinding), typeof(MenuItemKeyBinded),
new PropertyMetadata(new KeyBinding(),
//KeyBindingが指定された時に呼ばれるコールバック
KeyBindPropertyChanged));
private static void KeyBindPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var menuItemKB = d as MenuItemKeyBinded;
var kb = e.NewValue as KeyBinding;
//KeyBindingに結び付けられたコマンドをこのMenuItemのCommandに反映
menuItemKB.Command = kb.Command;
//KeyBindingのローカライズされた文字列("Ctrl"など)をこのMenuItemのInputGestureTextに反映
menuItemKB.InputGestureText = (kb.Gesture as KeyGesture).GetDisplayStringForCulture(CultureInfo.CurrentCulture);
}
}
}
종속성 속성으로 KeyBinding을 받고 내용을 상속받은 MenuItem의 Command와 InputGestureText에 전달합니다.
InputGestureText에는 KeyGesture를 현재의 Culture로 지역화한 캐릭터 라인 (ex. "Ctrl")을 설정합니다.
또한 KeyGesture의 ModifierKeys와 DisplayString을 사용해도 "Control"이나 공백으로되어있어 잘 움직이지 않습니다.
보기
사용할 때는 Window의 KeyBinding 측에 Command와 Gesture를 써, x:Name 경유로 확장 MenuItem에 KeyBinding 마다 건네줍니다.
MainWindow.xaml
<Window
x:Class="MenuExt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MenuExt"
Width="525"
Height="150">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.InputBindings>
<KeyBinding
x:Name="HogeKeyBinding"
Command="{Binding HogeCommand}"
Gesture="Ctrl+H" />
</Window.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="File(_F)">
<local:MenuItemKeyBinded
Header="Hoge"
KeyBind="{Binding ElementName=HogeKeyBinding}" />
</MenuItem>
</Menu>
<TextBox Grid.Row="1" Text="{Binding MyText.Value}" />
</Grid>
</Window>
ViewModel과 실행 결과는 동일합니다.
환경
VisualStudio2017
.NET Framework 4.6
C#6
별해
이번에는 KeyBinding에 x:Name을 붙여 KeyBinding 경유로 Command와 KeyGesture를 공유했습니다.
또 다른 방법은 Command 측에 KeyGesture를 포함하는 방법입니다.
How to Display Working Keyboard Shortcut for Menu Items?-stackoverflow
중단 당 CommandWithHotkey의 답변
Reference
이 문제에 관하여(바로 가기 키를 위한 KeyBinding이 있는 확장 MenuItem), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/soi/items/ec5982291154d94beb54
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
이번에는 KeyBinding에 x:Name을 붙여 KeyBinding 경유로 Command와 KeyGesture를 공유했습니다.
또 다른 방법은 Command 측에 KeyGesture를 포함하는 방법입니다.
How to Display Working Keyboard Shortcut for Menu Items?-stackoverflow
중단 당 CommandWithHotkey의 답변
Reference
이 문제에 관하여(바로 가기 키를 위한 KeyBinding이 있는 확장 MenuItem), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/soi/items/ec5982291154d94beb54텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)