내용으로 건너뛰기
WPF XamOutlookBar에 바로 가기 키 지원 추가

WPF XamOutlookBar에 바로 가기 키 지원 추가

Microsoft Outlook 사용자인 경우 간단한 바로 가기 키를 사용하여 다른 Outlook 그룹으로 이동할 수 있다는 것을 알 수 있습니다.  이 블로그 게시물에서는 WPF XamOutlookBar에 키보드 단축키 지원을 쉽게 추가하는 방법을 설명합니다. 더 읽어보기.

11min read

최근에 Infragistics WPF xamOutlookBar에서 그룹을 변경하기 위한 바로 가기 키 지원에 관한 질문을 받았습니다. 예를 들어 CRTL+2를 눌러 일정 그룹으로 이동하거나 CRTL+3을 눌러 연락처 그룹으로 이동할 수 있습니다.  여기서 아이디어는 사용자가 키보드를 사용하여 Outlook 표시줄 탐색에 포함된 그룹을 탐색할 수 있다는 것입니다.  불행히도 WPF xamOutlookBar가 이 동작을 지원하지 않는다는 것을 발견했습니다.  그래서 해결책을 찾기 위한 탐구가 시작되었습니다.  나는 실제로 두 가지 해결책을 생각해 냈다.  나는 그들 모두를 설명 할 것이고 당신은 당신이 가장 좋아하는 접근 방식을 결정할 수 있습니다.

요구 사항

코딩 솔루션을 실행하기 전에 이 기능이 어떻게 작동해야 하는지에 대해 이야기해야 할 것입니다.

  1. 첫 번째 명백한 요구 사항은 키보드 단축키를 사용하여 xamOutlookBar 컨트롤 내에서 선택한 그룹을 변경할 수 있어야 한다는 것입니다.
  2. 키 수정 자 (CTRL, ALT, SHIFT)와 키보드 키 (A, B, C) 조합이 그룹 변경을 호출 할 수 있도록 할당하고 싶습니다.
  3. 단일 그룹에 여러 키 제스처 조합을 할당 할 수 있기를 원합니다.  따라서 CTRL + 1 및 SHIFT + 1을 사용하여 동일한 그룹으로 이동하도록하려면 지원해야합니다.
  4. WPF에서 포커스 및 키보드 이벤트와 관련하여 발생할 수있는 문제를 알고 있습니다.  따라서 어떤 컨트롤에 포커스가 있는지 또는 내 화면의 어디에 있든 상관없이 바로 가기 키 조합으로 Outlook 표시줄 그룹을 변경할 수 있어야 합니다.

그것은 그것을 다루는 것에 관한 것입니다.  문제 해결에 들어가 보겠습니다!

키보드 단축키에 대한 접근 방식 1

필자가 취한 첫 번째 방법은 기본 제공 WPF InputBindings를 활용하는 것이었습니다.  여기서는 Window 레벨에서 여러 KeyBindings를 정의할 것인데, 이는 내 뷰의 어디에 있든 포커스가 있든 상관없이 바로 가기가 실행되도록 하기 때문입니다.  즉, 내 KeyBindings에 바인딩하고 내 키 조합에 따라 그룹 변경을 호출하기 위해 ICommand가 필요합니다.  문제는 이 명령이 키 수정자, 누른 키를 알고 있어야 하며 xamOutlookBar 컨트롤의 모든 그룹에 액세스할 수 있어야 한다는 것입니다.  따라서 KeyBinding 객체 자체를 CommandParameter 로 사용하면 이러한 모든 항목을 명령에 제공 할 수 있습니다.  CommandTarget 속성을 통해 xamOutlookBar 컨트롤 자체를 저장하겠습니다.  그러면 내가 필요한 모든 것에 액세스 할 수 있습니다.  또한 기본 제공 InputBindings를 사용하여 바로 가기 조합 / 제스처를 OutlookBarGroup에 할당합니다.  코드를 살펴 보겠습니다.

<Window.InputBindings>
    
    <KeyBinding Modifiers="Control" Key="D1"
                Command="{Binding ChangeGroupCommand}"
                CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
                CommandTarget="{Binding ElementName=xamOutlookBar1}"/>
    <KeyBinding Modifiers="Control" Key="NumPad1"
                Command="{Binding ChangeGroupCommand}"
                CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
                CommandTarget="{Binding ElementName=xamOutlookBar1}"/>

    <KeyBinding Modifiers="Control" Key="D2"
                Command="{Binding ChangeGroupCommand}"
                CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
                CommandTarget="{Binding ElementName=xamOutlookBar1}"/>
    <KeyBinding Modifiers="Control" Key="NumPad2"
                Command="{Binding ChangeGroupCommand}"
                CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
                CommandTarget="{Binding ElementName=xamOutlookBar1}"/>

    <KeyBinding Modifiers="Control" Key="D3"
                Command="{Binding ChangeGroupCommand}"
                CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
                CommandTarget="{Binding ElementName=xamOutlookBar1}"/>
    <KeyBinding Modifiers="Control" Key="NumPad3"
                Command="{Binding ChangeGroupCommand}"
                CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
                CommandTarget="{Binding ElementName=xamOutlookBar1}"/>

    <KeyBinding Modifiers="Control" Key="D4"
                Command="{Binding ChangeGroupCommand}"
                CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
                CommandTarget="{Binding ElementName=xamOutlookBar1}"/>
    <KeyBinding Modifiers="Control" Key="NumPad4"
                Command="{Binding ChangeGroupCommand}"
                CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
                CommandTarget="{Binding ElementName=xamOutlookBar1}"/>

</Window.InputBindings>

여기에서 볼 수 있듯이 Window.InputBindings 요소에 여러 KeyBindings를 정의했습니다.  숫자를 사용하여 그룹을 변경하고 싶기 때문에 키보드 위쪽에 있는 숫자와 키보드의 숫자 패드에 있는 숫자를 모두 처리해야 합니다.  즉, 각 숫자에 대해 두 개의 KeyBindings가 있습니다.  KeyBinding을 ChangeGroupCommand 라는 ViewModel에 정의 된 Command에 데이터 바인딩하고 있습니다.  CommandParamter는 KeyBinding 개체 인스턴스가 됩니다.  마지막으로 CommandTarget은 xamOutlookBar 컨트롤이 됩니다.  CommandParameter는 KeyBinding 인스턴스이므로 이제 키 제스처(수정자 및 키 누름)와 ViewModel의 xamOutlookBar 컨트롤에 있는 모든 그룹에 액세스할 수 있습니다.  VewModel을 간단히 살펴보겠습니다.

public class MainViewModel
{
    public ICommand ChangeGroupCommand { get; set; }

    public MainViewModel()
    {
        ChangeGroupCommand = new RelayCommand<KeyBinding>(x => ChangeGroup(x));
    }

    private void ChangeGroup(KeyBinding keyBinding)
    {
        XamOutlookBar outlookBar = keyBinding.CommandTarget as XamOutlookBar;
        if (outlookBar != null)
        {
            foreach (var group in outlookBar.Groups)
            {
                foreach (KeyBinding binding in group.InputBindings)
                {
                    if (binding.Modifiers == keyBinding.Modifiers && binding.Key == keyBinding.Key)
                    {
                        group.IsSelected = true;
                        return;
                    }
                }
            }
        }
    }
}

이것은 단일 ICommand 속성이 정의 된 매우 간단한 ViewModel입니다.  ICommand는 RelayCommand 구현입니다.  RelayCommand에 익숙하지 않은 경우 Google/Bing이 친구입니다.  ChangeGroup 메서드는 KeyBinding 매개 변수를 받아 CommandTarget 속성에서 xamOutlookBar 컨트롤을 가져온 다음 xamOutlookBar의 각 그룹에서 들어오는 KeyBinding과 일치하는 InputBindings가 있는 그룹을 검색하기 시작합니다.  OutlookBarGroups에 KeyBindings를 어떻게 할당합니까?  다음과 같이 쉽습니다.

<igWPF:XamOutlookBar x:Name="xamOutlookBar1" HorizontalAlignment="Left">
    <igWPF:OutlookBarGroup Header="Group 1">
        <igWPF:OutlookBarGroup.InputBindings>
            <KeyBinding Modifiers="Control" Key="D1" />
            <KeyBinding Modifiers="Control" Key="NumPad1" />
        </igWPF:OutlookBarGroup.InputBindings>

        <Label Content="Content for Group 1"/>
    </igWPF:OutlookBarGroup>
    <igWPF:OutlookBarGroup Header="Group 2">
        <igWPF:OutlookBarGroup.InputBindings>
            <KeyBinding Modifiers="Control" Key="D2" />
            <KeyBinding Modifiers="Control" Key="NumPad2" />
        </igWPF:OutlookBarGroup.InputBindings>
        
        <Label Content="Content for Group 2"/>
    </igWPF:OutlookBarGroup>
    <igWPF:OutlookBarGroup Header="Group 3">
        <igWPF:OutlookBarGroup.InputBindings>
            <KeyBinding Modifiers="Control" Key="D3" />
            <KeyBinding Modifiers="Control" Key="NumPad3" />
        </igWPF:OutlookBarGroup.InputBindings>
        
        <Label Content="Content for Group 3"/>
    </igWPF:OutlookBarGroup>
    <igWPF:OutlookBarGroup Header="Group 4">
        <igWPF:OutlookBarGroup.InputBindings>
            <KeyBinding Modifiers="Control" Key="D4" />
            <KeyBinding Modifiers="Control" Key="NumPad4" />
        </igWPF:OutlookBarGroup.InputBindings>
        
        <Label Content="Content for Group 4"/>
    </igWPF:OutlookBarGroup>
</igWPF:XamOutlookBar>

OutlookBarGroup.InputBindings 컬렉션에 여러 개의 KeyBindings를 추가하기만 하면 됩니다.  숫자를 입력하는 방법이 다르기 때문에 각 키 조합에 대해 KeyBinding을 추가해야 합니다.  그게 전부입니다.  이제 앱을 실행하면 키보드 단축키가 예상대로 작동합니다.  CTRL+3을 누르면 OutlookBarGroup이 "그룹 3"으로 선택됩니다.

이제 앱을 실행하면 키보드 단축키가 예상대로 작동합니다.  CTRL+3을 누르면 OutlookBarGroup이 "그룹 3"으로 선택됩니다.

접근법 2

첫 번째 접근 방식은 잘 작동했지만 약간의 중복된 노력이 있었습니다.  KeyBindings를 두 번 이상 매핑하고 싶지 않았습니다.  나는 켜고 끌 수 있고 모든 것이 작동하도록하는 간단한 속성을 선호한다.  그래서 저는 코드를 조금 더 사용하지만 훨씬 더 유연하고 구현하기 쉬운 다른 접근 방식을 취하기로 결정했습니다.  이 두 번째 방법은 AttachedProperty와 기본 제공 KeyBindings를 활용합니다.  OutlookBarGroups의 KeyBindings는 조금도 변경되지 않았습니다.  그것들은 그들이 있는 곳에 머물러 있습니다.  그룹 변경을 호출할 바로 가기 키 제스처를 정의하는 데 여전히 사용됩니다.  다음으로 EnableInputBindings라는 AttachedProperty를 만들겠습니다.  코드를 제공 한 다음 다양한 섹션에 대해 이야기 할 것입니다.

public class InputBinding : DependencyObject
{
    public static readonly DependencyProperty InputBindingBehaviorProperty =
        DependencyProperty.RegisterAttached("InputBindingBehavior", typeof(XamOutlookBarKeyBindingBehavior), typeof(InputBinding), new PropertyMetadata(null));

    public static readonly DependencyProperty EnableKeyBindingsProperty =
        DependencyProperty.RegisterAttached("EnableKeyBindings", typeof(bool), typeof(InputBinding), new PropertyMetadata(false, new PropertyChangedCallback(EnableKeyBindingsChanged)));
    public static bool GetEnableKeyBindings(DependencyObject obj)
    {
        return (bool)obj.GetValue(EnableKeyBindingsProperty);
    }
    public static void SetEnableKeyBindings(DependencyObject obj, bool value)
    {
        obj.SetValue(EnableKeyBindingsProperty, value);
    }

    private static void EnableKeyBindingsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        XamOutlookBar outlookBar = d as XamOutlookBar;
        bool isEnabled = (bool)e.NewValue;

        if (outlookBar != null)
        {
            XamOutlookBarKeyBindingBehavior behavior = GetOrCreateBehavior(outlookBar);

            if (isEnabled)
                behavior.Attach();
            else
                behavior.Dettach();
        }
    }

    private static XamOutlookBarKeyBindingBehavior GetOrCreateBehavior(XamOutlookBar outlookBar)
    {
        XamOutlookBarKeyBindingBehavior behavior = outlookBar.GetValue(InputBindingBehaviorProperty) as XamOutlookBarKeyBindingBehavior;
        if (behavior == null)
        {
            behavior = new XamOutlookBarKeyBindingBehavior(outlookBar);
            outlookBar.SetValue(InputBindingBehaviorProperty, behavior);
        }

        return behavior;
    }
}

public class XamOutlookBarKeyBindingBehavior : InputBindingBehaviorBase<XamOutlookBar>
{
    Window _parentWindow;

    public XamOutlookBarKeyBindingBehavior(XamOutlookBar outlookBar)
        : base(outlookBar)
    {

    }

    public override void Attach()
    {
        //since we want to listen for all key events no matter which control has focus, we need to listen at the Window level
        //otherwise the KeyUp event will never execute
        if (_parentWindow == null)
            _parentWindow = Window.GetWindow(TargetObject);

        if (_parentWindow != null)
            _parentWindow.AddHandler(Keyboard.KeyUpEvent, (KeyEventHandler)HandleKeyUp, true);
    }

    public override void Dettach()
    {
        if (_parentWindow != null)
            _parentWindow.RemoveHandler(Keyboard.KeyUpEvent, (KeyEventHandler)HandleKeyUp);
    }

    void HandleKeyUp(object sender, System.Windows.Input.KeyEventArgs e)
    {
        try
        {
            //We only want to check for shorcuts if we are dealing with modifier keys.
            if (Keyboard.Modifiers == ModifierKeys.None)
                return;

            foreach (OutlookBarGroup group in TargetObject.Groups)
            {
                foreach (KeyBinding binding in group.InputBindings)
                {
                    if (binding.Modifiers == Keyboard.Modifiers && binding.Key == e.Key)
                    {
                        group.IsSelected = true;
                        return;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
    }
}

public abstract class InputBindingBehaviorBase<T> where T : UIElement
{
    private readonly WeakReference _targetObject;
    protected T TargetObject
    {
        get { return _targetObject.Target as T; }
    }

    public InputBindingBehaviorBase(T targetObject)
    {
        _targetObject = new WeakReference(targetObject);
    }

    public abstract void Attach();
    public abstract void Dettach();

}

여기서 우리가 가지고 있는 것은 약간의 InputBinding 프레임워크를 만드는 것입니다.  먼저 입력 바인딩을 켜거나 끌 수있는 AttachedProperty 가 있습니다.  또한 InputBindingBehavior라는 기능을 제공할 동작 클래스의 인스턴스를 단순히 보유하는 연결된 속성을 정의했습니다.

EnableKeyBindings 연결 속성이 설정되면 새 XamOutlookBarKeyBindingBehavior 클래스 인스턴스가 생성되어 InputBindingBehavior 연결 속성에 저장됩니다.  EnableKeyBindings가 true이면 동작이 연결되고, false이면 분리됩니다.  XamOutlookBarKeyBindingBehvaior 클래스를 보면 실제로 qute simple임을 알 수 있습니다.  이 클래스는 InputBindingBehaviorBase<T>라는 추상 기본 클래스에서 파생되며, 이 클래스는 단순히 대상 개체(이 경우 xamOutlookBar)에 대한 약한 참조를 보유합니다.  동작이 연결되면 먼저 맨 위에 있는 부모 Window 컨트롤에 대한 인스턴스를 가져옵니다.  우리는 이벤트에서 초점이 맞춰진 내용에 관계없이 바로 가기 제스처를 실행하고 싶다는 것을 기억하십시오.  Window 인스턴스가 있으면 이제 KeyUp 이벤트에 핸들러를 추가하여 "처리된" 이벤트도 처리하도록 지정할 수 있습니다.  KeyUp 핸들러는 먼저 수정자를 처리하고 있는지 확인합니다.  아무 것도 없으면 logic를 실행하고 싶지 않습니다.  그렇다면 모든 그룹을 반복하고 방금 누른 바로 가기 키 제스처와 일치하는 KeyBindings가 있는지 확인합니다.  일치하는 항목이 있으면 그룹을 선택합니다.  마지막 단계는 xamOutlookBar 컨트롤에서 연결된 속성을 설정하는 것입니다.

<igWPF:XamOutlookBar x:Name="xamOutlookBar1" HorizontalAlignment="Left"
                     local:InputBinding.EnableInputBindings="True">
    
    <igWPF:OutlookBarGroup Header="Group 1">
        <igWPF:OutlookBarGroup.InputBindings>
            <KeyBinding Modifiers="Control" Key="D1" />
            <KeyBinding Modifiers="Control" Key="NumPad1" />
        </igWPF:OutlookBarGroup.InputBindings>
        <Label Content="Content for Group 1"/>
    </igWPF:OutlookBarGroup>
    <igWPF:OutlookBarGroup Header="Group 2">
        <igWPF:OutlookBarGroup.InputBindings>
            <KeyBinding Modifiers="Control" Key="D2" />
            <KeyBinding Modifiers="Control" Key="NumPad2" />
                        </igWPF:OutlookBarGroup.InputBindings>
        <Label Content="Content for Group 2"/>
    </igWPF:OutlookBarGroup>
    <igWPF:OutlookBarGroup Header="Group 3">
        <igWPF:OutlookBarGroup.InputBindings>
            <KeyBinding Modifiers="Control" Key="D3" />
            <KeyBinding Modifiers="Control" Key="NumPad3" />
                        </igWPF:OutlookBarGroup.InputBindings>
        <Label Content="Content for Group 3"/>
    </igWPF:OutlookBarGroup>
    <igWPF:OutlookBarGroup Header="Group 4">
        <igWPF:OutlookBarGroup.InputBindings>
            <KeyBinding Modifiers="Control" Key="D4" />
            <KeyBinding Modifiers="Control" Key="NumPad4" />
                        </igWPF:OutlookBarGroup.InputBindings>
        <Label Content="Content for Group 4"/>
    </igWPF:OutlookBarGroup>
</igWPF:XamOutlookBar>

보시다시피 이 방법은 설정하는 데 조금 더 많은 코드가 필요하지만 이제 바로 가기 제스처 지원을 훨씬 쉽게 활성화할 수 있습니다.  단일 연결된 속성을 True로 설정하고 그룹에서 KeyBindings를 정의하면 실행됩니다.  같은 결과, 단지 다른 방법이있을 뿐입니다.

같은 결과, 단지 다른 방법이합니다

소스 코드를 다운로드하고 재미있게 보내세요.

궁금해서 어떤 접근 방식을 선호하십니까?

데모 요청