コンテンツへスキップ
WPF XamOutlookBar へのキーボード ショートカット サポートの追加

WPF XamOutlookBar へのキーボード ショートカット サポートの追加

Microsoft Outlookのユーザーであれば、簡単なキーボードショートカットを使用してさまざまなOutlookグループに移動できることに気付いたかもしれません。 このブログ投稿では、WPF XamOutlookBar にキーボード ショートカット サポートを簡単に追加する方法について説明します。続きを読む。

11min read

最近、Infragistics WPF xamOutlookBarでグループを変更するためのキーボード ショートカットのサポートに関する質問を受けました。たとえば、CRTL+2 キーを押して [予定表] グループに移動したり、CRTL+3 キーを押して [連絡先] グループに移動したりできます。 ここでの考え方は、ユーザーがキーボードを使用して Outlook バー ナビゲーションに含まれるグループを移動できるということです。 残念ながら、WPF の xamOutlookBar がこの動作をサポートしていないことがわかりました。 こうして、解決策を見つけるための探求が始まりました。 実は、私は2つの解決策を思いつきました。 私はそれらの両方を説明します、そしてあなたはあなたが最も好きなアプローチを決定することができます。

必要条件

ソリューションのコーディングを開始する前に、この機能がどのように機能するかについて話し合う必要があります。

  1. 最初の明白な要件は、キーボード ショートカットを使用して xamOutlookBar コントロール内の選択したグループを変更できる必要があるということです。
  2. グループの変更を呼び出すキー修飾キー(CTRL、ALT、SHIFT)とキーボードキー(A、B、C)の組み合わせを割り当てることができるようにしたいと思います。
  3. 1 つのグループに複数のキー ジェスチャの組み合わせを割り当てることができるようにしたいと考えています。 したがって、CTRL + 1とSHIFT + 1で同じグループに移動する場合は、それをサポートする必要があります。
  4. 私は、WPFのフォーカスイベントとキーボードイベントで発生する可能性のある問題を知っています。 したがって、どのコントロールにフォーカスがある場合でも、画面上のどこにいても、ショートカットキーの組み合わせでOutlookバーグループを変更できることが重要です。

それについてはカバーしています。 問題解決に取り掛かりましょう!

キーボードショートカットへのアプローチ1

最初に行ったアプローチは、組み込みの WPF InputBindingsを利用することでした。 ここでは、ウィンドウ レベルでいくつかの KeyBinding を定義します。これは、ビュー内のどこにいてもフォーカスがある場所でもショートカットを実行できるようにするためです。 これは、KeyBindingsにバインドし、キーの組み合わせに応じてグループの変更を呼び出すためにICommandが必要になることを意味します。 問題は、このコマンドがキー修飾子、押されたキーを認識し、xamOutlookBar コントロール内のすべてのグループにアクセスできる必要があることです。 したがって、KeyBinding オブジェクト自体を CommandParameter として使用すると、これらすべての項目をコマンドに渡すことができます。 xamOutlookBar コントロール自体を CommandTarget プロパティを通じて格納します。 これにより、必要なものすべてにアクセスできるようになります。 また、組み込みの 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 要素でいくつかの KeyBinding を定義しました。 数字を使用してグループを変更したいので、キーボードの上部にある数字とキーボードのテンキーの数字の両方を処理する必要があります。 これは、各数値に対して 2 つの KeyBinding を持つことを意味します。 私は、ChangeGroupCommandというViewModelで定義されているコマンドにKeyBindingをデータバインドしています。 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;
                    }
                }
            }
        }
    }
}

これは、1 つの ICommand プロパティが定義されている非常に単純なビューモデルです。 ICommand は RelayCommand の実装です。 RelayCommand に詳しくない場合は、Google/Bing が友達です。 ChangeGroup メソッドは、KeyBinding パラメータを受け取り、CommandTarget プロパティから xamOutlookBar コントロールを取得して、xamOutlookBar の各グループの検索を開始し、入力 KeyBinding に一致する InputBinding を持つグループを探します。 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を複数回マップする必要はなかった。 私は、ON / OFFを切り替えて、すべてを機能させることができるシンプルなプロパティを好みます。 そこで、コードを少し増やすだけで、はるかに柔軟で実装が簡単な別のアプローチを取ることにしました。 この 2 番目の方法では、AttachedProperty と組み込みの KeyBindings を利用します。 OutlookBarGroups の KeyBinding は少しも変更されていません。 それらはそのまま残ります。 これらは、グループの変更を呼び出すショートカット キー ジェスチャを定義するために引き続き使用されます。 次に、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クラスを見ると、実際には単純であることがわかります。 これは、InputBindingBehaviorBase<T という抽象基本クラスから派生し>ターゲット オブジェクト (この場合は xamOutlookBar) への弱い参照を保持するだけです。 ビヘイビアがアタッチされると、最初に最上位の親 Window コントロールにインスタンスが取得されます。 ショートカットジェスチャは、イベントで何がフォーカスされているかに関係なく実行したいことを忘れないでください。 Window インスタンスを取得したら、KeyUp イベントにハンドラーを追加して、「処理された」イベントも処理するように指定できます。 KeyUp ハンドラーは、最初に修飾子を扱っていることを確認します。 何もない場合は、ロジックを実行したくありません。 その場合は、すべてのグループをループして、押されたばかりのショートカット キー ジェスチャに一致する KeyBinding を確認します。 一致する場合は、グループを選択します。 最後の手順では、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>

ご覧のとおり、このアプローチではセットアップに少し多くのコードが必要ですが、ショートカット ジェスチャのサポートを有効にするのがはるかに簡単になりました。 1 つの添付プロパティを True に設定し、グループと稼働中の KeyBinding を定義するだけです。 結果は同じですが、やり方が違うだけです。

結果は同じですが、やり方が違うだけです

ソースコードをダウンロードして楽しんでください。

好奇心から、あなたはどちらのアプローチを好みますか?

デモを予約