[C# WPF] FocusManager フォーカス周りのまとめ

2019年4月18日

  • 2017.04.03 サンプルがあまりにもあれだったので書き直しました。

WPFでちょっと描画領域の複雑なアプリを作っていて、フォーカスまわりがよくわかっていなかったのでサンプルを作りながらまとめました。
主にはMSDNのフォーカスの概要に書いてあります。
このページを読むと、論理フォーカスの辺りで「フォーカス」がゲシュタルト崩壊します。

スポンサーリンク

概要

WPFのフォーカスには、論理フォーカスとキーボードフォーカスがあります。キーボードフォーカスはデスクトップ上でひとつだけになります。論理フォーカスはFocusManagerに設定した任意のフォーカス範囲内にひとつだけ、となります。

ソースコード

以下にまとめたのは、UesrControlへのマウス左ボタンダウン時にキーボードフォーカスを設定するようにするサンプルです。
ウィンドウ内に機能拡張したUserControlを3つ配置し、Ctrl+Fを押すとフォーカスを持っているUserControlに合わせてダイアログが表示されます。

メインウィンドウのXaml

<Window x:Class="Sample_FocusManager.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Sample_FocusManager"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
        
    <!-- Main Window -->
    <Grid>
        <!-- Grid 定義 -->
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
    
        <Grid Grid.Row="0">
            <!-- うえ -->
            <local:TopView x:Name="xTopView" Focusable="True" /> 
        </Grid>
        
        <Grid Grid.Row="1">
            <!-- まんなか -->
            <local:CenterView x:Name="xCenterView" Focusable="True" />
        </Grid>
        
        <Grid Grid.Row="2">
            <!-- した -->
            <local:BottomView x:Name="xBottomView" Focusable="True" />
        </Grid>
    </Grid>
    
</Window>

メインウィンドウ

using System.Windows;
using System.Windows.Input;

namespace Sample_FocusManager
{
    public partial class MainWindow : Window
    {
        /// <summary>
        /// フォーカステストコマンド
        /// </summary>
        public static readonly ICommand FocusTestCommand = new FocusTestCommand();
        
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
            
            // "Ctrl + F"をFocusTestCommandに設定する
            KeyBinding keyBinding = new KeyBinding(
                FocusTestCommand,
                new KeyGesture(Key.F, ModifierKeys.Control));
                
            // BindingをMainWindowのInputBindingに追加する
            this.InputBindings.Add(keyBinding);
            
            // フォーカス範囲を設定する
            FocusManager.SetIsFocusScope(this, true);
            
            // 最初にフォーカスを持つ要素を設定する
            // ここでは1番上の要素
            FocusManager.SetFocusedElement(this, xTopView);
        }
    }
} 

マウス左ボタンダウン時にフォーカスを設定する拡張UserControlクラス

ここではキーボードフォーカスの設定に「Focus()」を使用していますが、論理フォーカスだけを設定したい場合は「FocusManager.SetFocusedElement()」を使用してください。

using System.Windows.Controls;
using System.Windows.Input;

namespace Sample_FocusManager
{
    public class FocusableUserControl : UserControl
    {
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public FocusableUserControl() { }
        
        /// <summary>
        /// マウス左ボタンダウン時のイベント
        /// </summary>
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            // マウス左ボタンダウン時にキーボードフォーカスを設定する
            if (Focus())
            {
                e.Handled = true;
            }
            base.OnMouseLeftButtonDown(e);
        }
    }
} 

フォーカス対象のViewのXaml

名称と色が違うだけなので1番上に表示しているコントロールだけ。
FocusableUserControlを継承しています。

<local:FocusableUserControl x:Class="Sample_FocusManager.TopView"
                            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                            xmlns:local="clr-namespace:Sample_FocusManager"
                            mc:Ignorable="d"
                            d:DesignHeight="300" d:DesignWidth="300">
                            
    <Grid>
        <!-- うえに表示するView -->
        <Canvas Background="CornflowerBlue" />
    </Grid>

</local:FocusableUserControl> 

フォーカス対象のView

クラス名が違うだけなので、1番上に表示しているコントロールだけ。
FocusableUserControlを継承しています。

namespace Sample_FocusManager
{
    /// <summary>
    /// TopView.xaml の相互作用ロジック
    /// </summary>
    public partial class TopView : FocusableUserControl
    {
        public TopView()
        {
            InitializeComponent();
        }
    }
} 

Ctrl+Fを押した際に呼び出されるコマンド

using System;
using System.Windows;
using System.Windows.Input;

namespace Sample_FocusManager
{
    /// <summary>
    /// フォーカステストコマンドクラス
    /// </summary>
    public class FocusTestCommand : ICommand
    {
        /// <summary>
        /// コマンドの実行可否
        /// </summary>
        /// <param name="parameter">パラメータ</param>
        /// <returns>常にtrue</returns>
        public bool CanExecute(object parameter)
        {
            return true;
        }
        
        /// <summary>
        /// コマンドの実行可否が切り替わった際に発行されるイベント
        /// </summary>
        /// <remarks>このサンプルでは使用しない</remarks>
        public event EventHandler CanExecuteChanged;
        
        /// <summary>
        /// コマンドを実行する
        /// </summary>
        /// <param name="parameter">パラメータ</param>
        public void Execute(object parameter)
        {
            // MainWindowを取得する
            var window = (MainWindow)Application.Current.MainWindow;            
            if (window == null)
            {
                return;
            }
            
            // フォーカス範囲内でフォーカスを持つ要素を取得する
            var element = FocusManager.GetFocusedElement(window);
            if (element == null)
            {
                return;
            }
            
            var topView = element as TopView;
            if (topView != null)
            {
                MessageBox.Show("うえ!");
                return;
            }
            
            var centerView = element as CenterView;
            if (centerView != null)
            {
                MessageBox.Show("まんなか!");
                return;
            }
            
            var bottomView = element as BottomView; 
            if (bottomView != null)
            {
                MessageBox.Show("した!");
                return;
            }
        }
    }
} 

だいじなこと

  • 論理フォーカス範囲を設定する (FocusManager.SetIsFocusScope())
  • 必要なタイミングでフォーカスを設定する (Focus() or FocusManager.SetFocusedElement())
  • フォーカスを設定したいコントロールは、Focusableをtrueにする
  • フォーカスを設定したくないコントロールは、Focusableをfalseにする (規定値はfalseですが、たまにデフォルトでtrueな人がいます)

よくわからなかったこと

MSDNにはFocusableをtrueにしないと動かないよ!と書いてあるのですが、今回作ったサンプルのMainWindow.xamlでコントロールに設定しているFocusableの値をfalseにしてみても、動作が変わりませんでした。
GetFocusedElement()で取得したオブジェクトの中身をデバッガでみても、Focusableの値はfalseになっています。
SetFocusedElement()するとFocusableに関係なくフォーカスが当たるようになるのでしょうか。
検証が足りていないので、わかったら追記します。

おしまい。

スポンサーリンク

C#, WPFC#, WPF

Posted by peliphilo