WinForms TabControl のフォーカス枠を消す方法|ShowFocusCues と WM_UPDATEUISTATE の実践解説

WinForms の TabControl を使っていると、タブ見出しに表示される「点線のフォーカス枠」がデザインを崩してしまうことがあります。本記事では VB.NET/C# の両方で、TabControl のフォーカスキューを実用的に非表示にする具体的な手順と、その裏側で動いている Windows の仕組みまで丁寧に解説します。

目次

WinForms の TabControl に表示される点線のフォーカス枠とは

WinForms の TabControl では、キーボード操作などでタブにフォーカスが当たると、タブ見出しの内側に点線の枠(フォーカス矩形、フォーカスキュー)が描画されます。

  • キーボード利用者に「今どこにフォーカスがあるか」を示すための視覚的なヒント
  • Windows 共通コントロール(ネイティブの Win32 コントロール)が自動で描画している

しかし、以下のような理由で「消したい」と感じるケースも多いでしょう。

  • フラットデザイン・モダンな UI では点線の枠が古くさく見える
  • 太字や背景色の切り替えなど、別の方法で選択タブを強調している
  • 少しのズレでもデザインレビューで指摘されるような、見た目最重視の業務アプリ

そこで最初に試したくなるのが ShowFocusCues のオーバーライドですが、TabControl に関してはこれだけではフォーカス枠が消えてくれません。

ShowFocusCues を False にしてもフォーカス枠が消えない理由

WinForms の多くのコントロールでは、ShowFocusCues プロパティをオーバーライドして False を返すと、フォーカスキューが非表示になります。しかし、TabControl のタブ見出しは内部的に「ネイティブの共通コントロール(SysTabControl32)」で描画されており、WinForms のプロパティだけでは制御できません。

フォーカス枠の表示・非表示は、Win32 が管理する「UI ステート(UI state)」という仕組みに従っています。具体的には、次のようなメッセージとフラグが関わっています。

項目内容
WM_UPDATEUISTATEフォーカス表示やアクセラレータ表示の状態を更新するためのメッセージ
UIS_SETUI ステートのフラグを「設定」することを意味するフラグ
UISF_HIDEFOCUSフォーカス表示(点線枠など)を隠すかどうかを表すフラグ

TabControl は「UI ステート」に従って、フォーカス枠を描くかどうかを決めます。そのため、

  • ShowFocusCues=False だけではネイティブ側の判断に介入できない
  • Windows 側の UI ステートを明示的に「フォーカスを隠す状態」に更新する必要がある

というのが、今回のポイントになります。

TabControl のフォーカス枠を消す基本方針

TabControl の点線フォーカス枠を実用的に消したい場合、本記事では次の 2 ステップを採用します。

  1. TabControl を Tab キーの移動対象から外す(必要に応じて)
    TabControl 自体にフォーカスが当たらなければ、そもそもフォーカス枠が出るタイミングが減ります。
  2. WM_UPDATEUISTATE をフックして UI ステートを常に「フォーカス非表示」に保つ
    TabControl のウィンドウプロシージャ(WndProc)をオーバーライドし、フォーカス表示フラグを毎回「隠す」状態に上書きします。

この 2 つを組み合わせることで、WinForms の TabControl でフォーカス枠をかなり確実に抑制できます。

実装例:派生 TabControl クラス(VB.NET)

まずは VB.NET での最小実装例です。標準の TabControl から派生させたクラスを 1 つ作成し、その中でフォーカスキューを制御します。

Friend Class CustomTabControl
    Inherits System.Windows.Forms.TabControl

    Public Sub New()
        MyBase.New()
        Me.Font = New Font("Segoe UI", 9)
    End Sub

    ' 見た目上のフォーカス枠は消したいので False を返す
    Protected Overrides ReadOnly Property ShowFocusCues As Boolean
        Get
            Return False
        End Get
    End Property

    ' キーボードのタブ移動対象から外す(必要に応じて)
    Protected Overrides Sub OnHandleCreated(e As EventArgs)
        MyBase.OnHandleCreated(e)
        Me.TabStop = False
    End Sub

    ' UI ステートでフォーカス表示を常に隠す
    Protected Overrides Sub WndProc(ByRef m As Message)
        Const WM_UPDATEUISTATE As Integer = &H128
        Const UIS_SET As Integer = &H1
        Const UISF_HIDEFOCUS As Integer = &H1

        If m.Msg = WM_UPDATEUISTATE Then
            m.WParam = New IntPtr(UIS_SET Or (UISF_HIDEFOCUS << 16))
        End If

        MyBase.WndProc(m)
    End Sub
End Class

フォーム上では、標準の TabControl の代わりにこの CustomTabControl を貼り付けて利用します。プロジェクト内の任意のクラスファイル(例:CustomControls.vb)にこのコードを置いておくと、デザイナからも選択できるようになります。

ShowFocusCues を False にする意味

ShowFocusCues のオーバーライドでは、見た目としてのフォーカスキューを描画しないよう、WinForms に対して「フォーカス枠は要りません」と宣言しています。ただし、先述の通り TabControl のタブ部分はネイティブ描画なので、このプロパティだけでは完全には抑制できません。

とはいえ、WinForms 側で描画されるケース(テーマや OS の違い、将来の挙動変更など)に備えて、ShowFocusCues=False も併用しておくのが安全です。

OnHandleCreated で TabStop=False を設定する理由

TabStop プロパティはデザイナからも設定できますが、TabControl のハンドル生成タイミングによっては値が上書きされてしまう場合があります。そのため、確実に反映させるために OnHandleCreated 内で設定します。

この行の役割は次の通りです。

  • Tab キーによるフォーカス移動の対象から TabControl を外す
  • ユーザーが Tab キーを押しても、TabControl 自体にはフォーカスが当たらない
  • 結果として、タブ見出しにフォーカス枠が表示されるタイミングが減る

ただし、後述の通りアクセシビリティ(キーボード操作)を重視する場合は、TabStop=True のままにして WndProc の制御だけを適用する選択肢もあります。

WndProc で WM_UPDATEUISTATE をフックする

フォーカス枠を決定づけているのが WM_UPDATEUISTATE メッセージです。TabControl にこのメッセージが届いたとき、wParam には「どの UI ステートをどう変更するか」が指定されています。

今回のコードでは、

If m.Msg = WM_UPDATEUISTATE Then
    m.WParam = New IntPtr(UIS_SET Or (UISF_HIDEFOCUS << 16))
End If

という処理で、UI ステートを強制的に「フォーカス表示を隠す」状態に書き換えています。ビットシフトで 16 ビット左にずらしているのは、UI ステートの仕様で上位ワードにフラグを入れる必要があるためです。

ビット意味
下位 16 ビットUIS_SET / UIS_CLEAR など「どう変更するか」
上位 16 ビットUISF_HIDEFOCUS など「どの UI ステートか」

このようにして、TabControl が内部で参照している UI ステートを常に「フォーカス非表示」に保つことで、点線のフォーカス枠を実用的に消すことができます。

C# 版のサンプルコード

同じことを C# で実現する場合は、次のような派生クラスになります。VB.NET と混在しているプロジェクトでも流用しやすいように紹介しておきます。

using System;
using System.Drawing;
using System.Windows.Forms;

public class CustomTabControl : TabControl
{
    public CustomTabControl()
        : base()
    {
        this.Font = new Font("Segoe UI", 9);
    }

    // 見た目上のフォーカス枠は消したいので false を返す
    protected override bool ShowFocusCues
    {
        get { return false; }
    }

    // キーボードのタブ移動対象から外す(必要に応じて)
    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        this.TabStop = false;
    }

    // UI ステートでフォーカス表示を常に隠す
    protected override void WndProc(ref Message m)
    {
        const int WM_UPDATEUISTATE = 0x0128;
        const int UIS_SET = 0x0001;
        const int UISF_HIDEFOCUS = 0x0001;

        if (m.Msg == WM_UPDATEUISTATE)
        {
            m.WParam = new IntPtr(UIS_SET | (UISF_HIDEFOCUS << 16));
        }

        base.WndProc(ref m);
    }
}

Windows フォームデザイナから利用したい場合は、このクラスをプロジェクトに追加してビルド後、ツールボックスに表示させるか、既存の TabControl を手動で差し替えてください。

TabStop=False にするかどうかの判断基準

TabStop=False にするかどうかは、アプリケーションの要件によって変わります。簡単に比較表にすると次のようになります。

設定メリットデメリット向いているケース
TabStop = FalseTabControl 自体が Tab キーで選択されない フォーカス枠が表示されるチャンス自体が減る 視覚的には最もスッキリするTab キーだけでタブを切り替えられなくなる キーボード操作を重視するユーザーには不便マウス操作前提の業務アプリ、社内ツールなど
TabStop = TrueTab キーで TabControl にフォーカスを移動できる キーボードユーザーにも配慮できる環境によってはフォーカス枠が一部表示される可能性一般公開するアプリ、アクセシビリティ要求があるシステム

「デザイン優先」か「アクセシビリティ優先」かによって、どちらの設定を採用するかを決めるとよいでしょう。

OS・アクセシビリティ設定による挙動の違い

フォーカス枠の挙動は、Windows の設定によっても変化する可能性があります。特に注意したいのは次のようなケースです。

  • 高コントラストテーマを有効にしている
  • 「キーボードのフォーカスを常に表示する」系のアクセシビリティ機能を有効にしている
  • Windows 10 と Windows 11、またはテーマ(ライト / ダーク)による微妙な差

UI ステートを上書きしているとはいえ、OS がユーザーのアクセシビリティ設定を優先して強制的にフォーカス表示を有効にすることも理論上はあり得ます。特に多くの環境で配布する業務アプリでは、以下の点に注意しましょう。

  • 可能であれば高コントラストテーマ、画面拡大、IME あり/なしなど複数の環境で動作確認する
  • 「フォーカス枠を完全に殺す」よりも、「通常は消すが、OS の設定には逆らわない」方針も検討する

OwnerDraw でタブを完全に自前描画する方法

より厳密に制御したい場合は、TabControl の DrawMode を OwnerDrawFixed にし、自前でタブを描画する方法もあります。

  • メリット:フォーカス枠を描かないことを完全に保証しやすい
  • デメリット:テーマや DPI 対応、文字詰め、アイコン配置などを自分で実装する必要がある

VB.NET での簡易的な例を示します。

Friend Class OwnerDrawTabControl
    Inherits System.Windows.Forms.TabControl

    Public Sub New()
        MyBase.New()
        Me.DrawMode = TabDrawMode.OwnerDrawFixed
    End Sub

    Protected Overrides ReadOnly Property ShowFocusCues As Boolean
        Get
            Return False
        End Get
    End Property

    Protected Overrides Sub OnDrawItem(e As DrawItemEventArgs)
        MyBase.OnDrawItem(e)

        Dim tabRect = Me.GetTabRect(e.Index)
        Dim text = Me.TabPages(e.Index).Text

        ' 背景
        If (e.State And DrawItemState.Selected) = DrawItemState.Selected Then
            Using b As New SolidBrush(SystemColors.ControlLightLight)
                e.Graphics.FillRectangle(b, tabRect)
            End Using
        Else
            Using b As New SolidBrush(SystemColors.Control)
                e.Graphics.FillRectangle(b, tabRect)
            End Using
        End If

        ' テキスト(中央寄せ)
        TextRenderer.DrawText(
            e.Graphics,
            text,
            Me.Font,
            tabRect,
            SystemColors.ControlText,
            TextFormatFlags.HorizontalCenter Or TextFormatFlags.VerticalCenter
        )

        ' ※フォーカス矩形は描かない
    End Sub
End Class

この方法では、WM_UPDATEUISTATE をいじらなくてもフォーカス枠を抑制できますが、代わりに「見た目すべて」を自分で面倒を見る必要があります。ダークテーマ対応やタブの高さ調整、アイコン表示などが必要な場合は、それぞれ追加実装が必要です。

どの方法を選ぶべきかの指針

ここまで紹介した 3 つのアプローチを比較してみましょう。

方法概要手間柔軟性おすすめ度
ShowFocusCues だけ FalseWinForms 側でフォーカスキュー非表示を依頼非常に少ない低い(TabControl では効かないことが多い)補助的な対策としては可
UI ステート(WM_UPDATEUISTATE)で隠すWndProc でフォーカス非表示フラグを強制中程度中〜高(既存の外観をそのまま利用可能)実用性と保守性のバランスが良い
OwnerDraw で自前描画タブの描画をすべて自前で行う高い非常に高い(デザインを完全に自由にできる)デザインに強いこだわりがある場合に

多くの場面では、

  • UI ステートでフォーカス枠を隠す(本記事のメインの方法)
  • 必要であれば OwnerDraw で細かいデザインを調整

という組み合わせが現実的です。まずは UI ステートをいじる方法で実装し、どうしてもデザイン上の要件を満たせない場合に OwnerDraw へ進む、というステップをおすすめします。

実案件でよくあるユースケースと選択例

社内向けの業務アプリで見た目をシンプルにしたい

開発チームしか利用しない、あるいは限定されたメンバーだけが使う社内ツールの場合、

  • マウス操作がメインである
  • UI レビューよりも開発スピードとシンプルさが優先される

といった特徴があります。この場合、次のような設定が扱いやすいでしょう。

  • TabStop = False で TabControl を Tab 移動の対象から外す
  • WM_UPDATEUISTATE でフォーカス枠を常に隠す
  • ShowFocusCues は念のため False を返す

これにより、ほとんどのケースでフォーカス枠を目にすることはなくなり、デザインもすっきりします。

一般ユーザーも利用するアプリでアクセシビリティにも配慮したい

不特定多数のユーザーが利用する場合や、公共性の高いシステムの場合、アクセシビリティを無視することはできません。この場合は次のような妥協点を探るのがおすすめです。

  • TabStop = True のままにして、Tab キーで TabControl にフォーカスを移動できるようにする
  • UI ステートの強制は行うが、OS のアクセシビリティ設定によってはフォーカスが表示される可能性も許容する
  • タブの選択状態を背景色・太字・アイコンなどで十分に強調する

完全にフォーカス枠を殺すことよりも、「普段は邪魔にならないが、必要な人にはきちんと情報が届く」状態を目指す方が、ユーザー体験としてもバランスがよくなります。

実装時のちょっとしたテクニックと注意点

カスタムコントロール化して再利用性を高める

TabControl ごとに同じコードを書くのは非効率なので、本記事のように「派生クラスとして 1 か所にまとめる」ことを強くおすすめします。

  • CustomTabControl クラスを 1 箇所にまとめる
  • プロジェクト内のすべての TabControl を順次置き換える
  • 将来フォーカス表示のポリシーを変更したくなった場合も、1 クラスを修正するだけで済む

特に大規模な業務システムでは、「後から方針を変えたくなる」のが常なので、最初からカスタムコントロール化しておくと後々とても楽になります。

フォーカスを完全に隠さず、別の形で分かりやすく見せる

点線の枠を消すだけだと、ユーザーが「今どのタブが選択されているのか」を見失ってしまう可能性があります。そのため、フォーカス枠の代わりに次のような工夫を組み合わせるとよいでしょう。

  • 選択中のタブの背景色を少し明るくする
  • 選択タブだけ太字にする
  • 選択タブの下に細いラインを描画する( OwnerDraw 時)

これらはデザイン上も自然ですし、キーボードユーザーだけでなくマウス中心のユーザーに対しても視認性を高める効果があります。

まとめ:WinForms の TabControl フォーカス枠を安全に消すポイント

WinForms の TabControl に表示される点線のフォーカス枠は、単に ShowFocusCues=False とするだけでは消えてくれないケースが多く、Windows の UI ステートとネイティブ描画に踏み込んだ対処が必要になります。

本記事で紹介したように、

  • 派生クラスを作成し、ShowFocusCues を False にする
  • 必要に応じて TabStop を False にして Tab キー移動の対象から外す
  • WndProc をオーバーライドして WM_UPDATEUISTATE の wParam を上書きし、UISF_HIDEFOCUS を常にセットする

という 3 つのポイントを押さえることで、TabControl のフォーカス枠を実用的に非表示にできます。また、より厳密な見た目を求める場合は、OwnerDraw を併用してタブ自体を完全に自前描画することも検討できます。

最後にもう一度強調すると、「フォーカス枠を消すこと」自体が目的ではなく、

  • ユーザーが今どこを操作しているかを正しく理解できること
  • アプリ全体のデザインポリシーと一貫性があること
  • 将来の要件変更や OS の設定変更にも対応しやすい実装になっていること

が本当のゴールです。本記事のサンプルをたたき台に、自分のプロジェクトの要件に合わせて微調整してみてください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次