たきるブログ

C#やOracleなどの情報を書いています。

【C#】EnterキーでTabキーを押した時と全く同じフォーカス遷移を行う

EnterキーでTabキーを押した時と同じフォーカス遷移を行いたいという要望は、案外多い。
古いシステムからの名残りだったり、そもそも提案した人が古い人だったりで。
果てはWebシステムでもそれを望むこともあるが、今回はWindows Formsアプリケーションの場合の解決方法を記す。

Enterキーでフォーカス遷移する方法は、難しく考えすぎて意外とハマる。
他のサイトで調べた結果、最適解を導き出した。

参考サイト

Enterキーを押した時、まるでTabキーを押したかのように、次のコントロールにフォーカスを移す - .NET Tips (VB.NET,C#...)
色々あって全部試したわけではないが、コンポーネント単位に継承するのは非効率。
コメント欄に[たきる]ってなってるのは俺^^;

Windowsアプリケーションで[Enter]キーによるフォーカス移動を行うには?:.NET TIPS - @IT
C# - フォームで [Enter] キーが押された時にフォーカスを遷移させる
SelectNextControlメソッドを利用すると、『自分のオブジェクト配下に存在するコントロールを遷移する』だけであり、Tabキーと全く同じにはならない。

主なハマる点

基本的には、参考サイトにある方法論でも問題ない。
しかし、SplitContainerを利用した途端、『Tabキーを押した時と全く同じ』とはならない。
例えばこんな状態のフォーム内容の場合。(TextBox内の値が、Tabキー押下時に遷移するタブ順)
f:id:metroit:20150625102155p:plain

解決策

これが最も適切に動作すると思う。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// ダイアログ キーを処理します。
        /// </summary>
        /// <param name="keyData">処理するキーを表す Keys 値の 1 つ。</param>
        /// <returns>キーがコントロールによって処理された場合は true。それ以外の場合は false。</returns>
        protected override bool ProcessDialogKey(Keys keyData)
        {
            // Enterキー押下によるフォーカス移動
            if (((keyData & Keys.KeyCode) == Keys.Return) &&
                ((keyData & (Keys.Alt | Keys.Control)) == Keys.None))
            {
                this.MoveNextControl();
                return true;
            }

            return base.ProcessDialogKey(keyData);
        }

        /// <summary>
        /// 次のコントロールをアクティブにします。
        /// </summary>
        /// <param name="forward">タブオーダー内を前方に移動する場合はtrue。後方に移動する場合はfalse。</param>
        public void MoveNextControl(bool forward = true)
        {
            if (forward)
            {
                SendKeys.Send("{TAB}");
            }
            else
            {
                SendKeys.Send("+{TAB}");
            }
        }
    }
}

MultilineプロパティがtrueのTextBoxは、Ctrl+Enterで改行される。
MoveNextControl()メソッドは、SelectNextControl()メソッドの『Tabキー押下版』。

このロジックを実装したフォームを継承してアプリを作ることで、面倒なことが起こらない。
EnterFocusプロパティなどを設けて、trueの時だけ処理するとか、継承元で考慮しておけば楽。

2017/05/26 追記
TextBoxBase.AcceptsTabをtrueにしたコントロール上で行うと、Tab入力になってしまう。
ここは要検討かもしれない。

2018/05/10 追記
TextBoxBase.AcceptsTabをtrueのコントロールの場合、Ctrl+TabまたはCtrl+Shift+Tabでタブ遷移が行われるため、
SendKeys.Send("^{TAB}");、SendKeys.Send("^+{TAB}");で対応できる。

public void MoveNextControl(bool forward = true)
{
    // control は Form.ActiveControlなどを使った、現在フォーカスのあるコントロール
    var textBox = GetTextBox(control);
    if (textBox != null && textBox.Multiline && textBox.AcceptsTab)
    {
        if (forward)
        {
            SendKeys.Send("^{TAB}");
        }
        else
        {
            SendKeys.Send("^+{TAB}");
        }
        return;
    }

    // その他コントロール
    if (forward)
    {
        SendKeys.Send("{TAB}");
    }
    else
    {
        SendKeys.Send("+{TAB}");
    }
}

// 対象コントロールからアクティブなTextBoxオブジェクトを取得
private static TextBox GetTextBox(Control control)
{
    var targetControl = control as TextBox;
    if (targetControl != null)
    {
        return targetControl;
    }

    var form = control as Form;
    if (form == null)
    {
        return null;
    }
    targetControl = form.ActiveControl as TextBox;
    if (targetControl != null)
    {
        return targetControl;
    }

    var splitContainer = form.ActiveControl as SplitContainer;
    targetControl = splitContainer?.ActiveControl as TextBox;
    if (targetControl != null)
    {
        return targetControl;
    }

    return null;
}