DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

次のタブオーダーのコントロールをアクティブに(選択、フォーカスを移動)する

フォームがアクティブの時にタブキーを押すと、現在アクティブな(選択されている、フォーカスのある)コントロールの次のコントロールがアクティブになります。この順番を「タブオーダー」といいます。ここでは、タブオーダーに従って、次のコントロールをアクティブにする方法を紹介します。

なお、単純に指定したコントロールをアクティブにする方法は、「指定したコントロールをアクティブにする」で説明しています。

Control.SelectNextControlメソッドを使用する方法

指定したコントロールの次のコントロールをアクティブにするには、Control.SelectNextControlメソッドを使用します。

フォームに配置されているTextBox1コントロールの次のコントロールをアクティブにするには、次のようにします。なおこのコードは、フォームクラス内に書かれているものとします。

VB.NET
コードを隠すコードを選択
'TextBox1の次のコントロールを選択する
Me.SelectNextControl(Me.TextBox1, True, True, True, True)
C#
コードを隠すコードを選択
//TextBox1の次のコントロールを選択する
this.SelectNextControl(this.TextBox1, true, true, true, true);

SelectNextControlメソッドはパラメータの数が多いので、すべてパラメータについて説明しておきます。一番目のパラメータは、基準となるコントロールです。2番目のパラメータは、タブオーダーが次のコントロールをアクティブにするか、前のコントロールをアクティブにするかです。つまり、これをFalseにすると、Shiftキーを押しながらTabキーを押した時のように、前のコントロールがアクティブになります。3番目のパラメータは、TabStopがFalseのコントロールを無視するかどうか(無視する時は、True)です。4番目のパラメータは、子コントロールの子コントロールも含めるか(含めるときは、True)です。5番目のパラメータは、タブオーダーの最後のコントロールが基準とされている時に最初に戻るか(最初に戻る時は、True)です。

SelectNextControlメソッドは、次のコントロールをアクティブにした時にTrueを返し、しなかった時にFalseを返します。例えば5番目のパラメータがFalseで、1番目のパラメータが最後のコントロールであった場合は、次のコントロールがアクティブになりませんので、Falseを返します。

もう一つ、よくある例を示します。次の例では、現在選択されているコントロールの次のタブオーダーのコントロールをアクティブにしています。なお、現在選択されているコントロールを取得する方法は、「現在アクティブな(選択されている、フォーカスのある)コントロールを取得する」をご覧ください。

VB.NET
コードを隠すコードを選択
'次のコントロールを選択する
Me.SelectNextControl(Me.ActiveControl, True, True, True, True)
C#
コードを隠すコードを選択
//次のコントロールを選択する
this.SelectNextControl(this.ActiveControl, true, true, true, true);

Control.GetNextControlメソッドとSelectメソッドを使用する方法

Control.GetNextControlメソッドを使用すると、指定したコントロールの次のコントロールを取得することができます。よって、このメソッドと「指定したコントロールをアクティブにする」で紹介している方法を組み合わせることでも、SelectNextControlメソッドと同じことができます。

ただしGetNextControlメソッドは、タブオーダーが最後のコントロールの次のコントロールを取得しようとすると、NULLを返すことに注意してください。

VB.NET
コードを隠すコードを選択
'次のコントロールを取得する
Dim nextControl As Control = Me.GetNextControl(Me.ActiveControl, True)
'前のコントロールを取得する場合は、次のようにする
'Dim nextControl As Control = Me.GetNextControl(Me.ActiveControl, False)

'次のコントロールを選択する
If Not (nextControl Is Nothing) Then
    nextControl.Select()
End If
C#
コードを隠すコードを選択
//次のコントロールを取得する
Control nextControl = this.GetNextControl(this.ActiveControl, true);
//前のコントロールを取得する場合は、次のようにする
//Control nextControl = this.GetNextControl(this.ActiveControl, false);

//次のコントロールを選択する
if (nextControl != null)
{
    nextControl.Select();
}

ProcessTabKeyメソッドを使用する方法

ProcessTabKeyメソッドは、タブキーが押された時に呼び出されるメソッドです。よってこのメソッドを使用することで、次のコントロールをアクティブにすることができます。

このメソッドは、ContainerControlクラスにプロテクトメソッドとして存在します。つまり、このメソッドは、FormやSplitContainer、UserControlのようにContainerControlから継承したコントロールで使用できますが、外部のクラスからは呼び出すことができません。

以下のコードがフォームクラスに記述されている時、Form.ProcessTabKeyメソッドによって、次のコントロールがアクティブになります。

VB.NET
コードを隠すコードを選択
'次のコントロールをアクティブにする
Me.ProcessTabKey(True)

'前のコントロールをアクティブにするには、次のようにする
'Me.ProcessTabKey(False)
C#
コードを隠すコードを選択
//次のコントロールをアクティブにする
this.ProcessTabKey(true);

//前のコントロールをアクティブにするには、次のようにする
//this.ProcessTabKey(false);
補足:フォームのProcessTabKeyメソッドは、最後のコントロールがアクティブの時に呼び出すと、最初のコントロールがアクティブになります。しかしそれ以外のほとんどのコントロールでは、最後のコントロールがアクティブの時に呼び出すと、フォーカスは移動せず、ProcessTabKeyメソッドはFalseを返します。

SendKeys.Sendメソッドを使用する方法

少し強引ですが、SendKeys.Sendメソッドを使用してタブキーを押したふりをすることで、次のコントロールをアクティブにするという方法もあります。

VB.NET
コードを隠すコードを選択
'Tabキーを押したふりをして、次のコントロールをアクティブにする
SendKeys.Send("{TAB}")

'Shift+Tabキーを押したふりをして、前のコントロールをアクティブにする
'SendKeys.Send("+{TAB}")
C#
コードを隠すコードを選択
//Tabキーを押したふりをして、次のコントロールをアクティブにする
SendKeys.Send("{TAB}");

//Shift+Tabキーを押したふりをして、前のコントロールをアクティブにする
//SendKeys.Send("+{TAB}");

SplitContainerコントロールでうまくいかない問題について

フォームにSplitContainerコントロールが配置されている時、今まで紹介してきたようなメソッドをフォームで使用すると、タブキーを押した時のようにはフォーカスが移動しないかもしれません。例えば、SplitContainerコントロール内のコントロールがアクティブな時にフォームのSelectNextControlメソッドを呼び出すと、たとえSplitContainerコントロール内に次のコントロールがあったとしても、SplitContainerコントロール外の次のコントロールがアクティブになります。

このような問題は、SendKeys.Sendメソッドを使った方法以外の、SelectNextControl、GetNextControl、ProcessTabKeyメソッドで起こります。

この問題はフォームのSelectNextControlメソッドを呼び出していることが原因ですので、SplitContainerコントロールのSelectNextControlメソッドを呼び出すことで解決できます。例えば、フォームにSplitContainerコントロールが一つだけある場合は、以下のようにして次のコントロールをアクティブにすることができます。

VB.NET
コードを隠すコードを選択
'SplitContainer1内のコントロールがアクティブか調べる
If Me.ActiveControl Is Me.SplitContainer1 Then
    'SplitContainer1のSelectNextControlを呼び出す
    If Me.SplitContainer1.SelectNextControl( _
        Me.SplitContainer1.ActiveControl, True, True, True, False) Then

        'SplitContainer1内の次のコントロールがアクティブになったら終了
        Return
    End If
End If

'フォームのSelectNextControlを呼び出す
Me.SelectNextControl(Me.ActiveControl, True, True, True, True)
C#
コードを隠すコードを選択
//splitContainer1内のコントロールがアクティブか調べる
if (this.ActiveControl == this.splitContainer1)
{
    //splitContainer1のSelectNextControlを呼び出す
    if (this.splitContainer1.SelectNextControl(
        this.splitContainer1.ActiveControl, true, true, true, false))
    {
        //splitContainer1内の次のコントロールがアクティブになったら終了
        return;
    }
}

//フォームのSelectNextControlを呼び出す
this.SelectNextControl(this.ActiveControl, true, true, true, true);

このようにSplitContainerコントロールが一つしかない時は簡単ですが、SplitContainerの中にSplitContainerがあるケースを考慮しなければならない場合は、複雑になります。そのようなケースも考慮して次のコントロールをアクティブするメソッドの例を以下に示します。

VB.NET
コードを隠すコードを選択
'Imports System.Windows.Forms
'Imports System.Collections.Generic

''' <summary>
''' フォーム上の次のコントロールをアクティブにする
''' </summary>
''' <param name="f">対象のフォーム</param>
''' <param name="forward">
''' 次のコントロールをアクティブにする場合は、True。
''' 前のコントロールをアクティブにする場合は、False。
''' </param>
Public Shared Sub SelectNextControlInForm(f As Form, forward As Boolean)
    'アクティブコントロールの親ContainerControlをすべてリストに追加する
    Dim ccList As New List(Of ContainerControl)()
    Dim cc As ContainerControl = f
    While TypeOf cc.ActiveControl Is ContainerControl
        cc = DirectCast(cc.ActiveControl, ContainerControl)
        ccList.Insert(0, cc)
    End While

    '深いContainerControlから順にSelectNextControlを呼び出す
    For Each ct As ContainerControl In ccList
        If ct.SelectNextControl( _
            ct.ActiveControl, forward, True, True, False) Then

            '次のコントロールがアクティブになったら、終了
            Return
        End If
    Next

    'フォームのSelectNextControlを呼び出す
    f.SelectNextControl(f.ActiveControl, forward, True, True, True)
End Sub
C#
コードを隠すコードを選択
//using System.Windows.Forms;
//using System.Collections.Generic;

/// <summary>
/// フォーム上の次のコントロールをアクティブにする
/// </summary>
/// <param name="f">対象のフォーム</param>
/// <param name="forward">
/// 次のコントロールをアクティブにする場合は、True。
/// 前のコントロールをアクティブにする場合は、False。
/// </param>
public static void SelectNextControlInForm(Form f, bool forward)
{
    //アクティブコントロールの親ContainerControlをすべてリストに追加する
    List<ContainerControl> ccList = new List<ContainerControl>();
    ContainerControl cc = f;
    while (cc.ActiveControl is ContainerControl)
    {
        cc = (ContainerControl)cc.ActiveControl;
        ccList.Insert(0, cc);
    }

    //深いContainerControlから順にSelectNextControlを呼び出す
    foreach (ContainerControl ct in ccList)
    {
        if (ct.SelectNextControl(ct.ActiveControl,
            forward, true, true, false))
        {
            //次のコントロールがアクティブになったら、終了
            return;
        }
    }

    //フォームのSelectNextControlを呼び出す
    f.SelectNextControl(f.ActiveControl, forward, true, true, true);
}

上記のような方法にも、問題があります。SelectNextControlメソッドでは、SplitContainerの分割線を選択状態にすることができません。

SplitContainerの分割線を選択状態にするには、SplitContainerのProcessTabKeyメソッドを使うしかなさそうです(または、自分でタブオーダーを調べて、「指定したコントロールをアクティブにする」で紹介している方法でコントロールをアクティブにするという方法もあります)。しかしProcessTabKeyメソッドはプロテクトメソッドですので、SplitContainerクラスを継承したクラスを作成してProcessTabKeyメソッドを呼び出すメソッドを公開するか、「隠蔽されている非パブリックメンバを呼び出す」のような方法で強引に呼び出すかといった手間が必要になります。

隠蔽されている非パブリックメンバを呼び出す」の方法でProcessTabKeyメソッドを呼び出す例を示します。このメソッドをSelectNextControlの代わりに使用すると、SplitContainerの分割線も選択されるようになります。

VB.NET
コードを隠すコードを選択
'Imports System.Windows.Forms

''' <summary>
''' 指定したContainerControlのProcessTabKeyメソッドを呼び出す
''' </summary>
''' <param name="cc">対象のContainerControl</param>
''' <param name="forward">前方に移動する時はTrue。後方はFalse。</param>
''' <returns>コントロールが選択された時はTrue。</returns>
Public Shared Function InvokeProcessTabKey( _
        cc As ContainerControl, forward As Boolean) As Boolean
    Return CBool(cc.GetType().InvokeMember("ProcessTabKey", _
        System.Reflection.BindingFlags.NonPublic Or _
        System.Reflection.BindingFlags.Instance Or _
        System.Reflection.BindingFlags.InvokeMethod, _
        Nothing, cc, New Object() {forward}))
End Function
C#
コードを隠すコードを選択
//using System.Windows.Forms;

/// <summary>
/// 指定したContainerControlのProcessTabKeyメソッドを呼び出す
/// </summary>
/// <param name="cc">対象のContainerControl</param>
/// <param name="forward">前方に移動する時はTrue。後方はFalse。</param>
/// <returns>コントロールが選択された時はTrue。</returns>
public static bool InvokeProcessTabKey(ContainerControl cc, bool forward)
{
    return (bool)cc.GetType().InvokeMember("ProcessTabKey",
        System.Reflection.BindingFlags.NonPublic |
        System.Reflection.BindingFlags.Instance |
        System.Reflection.BindingFlags.InvokeMethod,
        null, cc, new object[] { forward });
}

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • コードの先頭に記述されている「Imports ??? がソースファイルの一番上に書かれているものとする」(C#では、「using ???; がソースファイルの一番上に書かれているものとする」)の意味が分からないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。