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

TabControlのタブが横にあると正常に表示されない

ビジュアルスタイルが有効になっており、タブコントロールの外観が通常(TabControl.AppearanceプロパティがTabAppearance.Normal)のとき、タブを右または左に表示しようとすると(TabControl.AlignmentプロパティをTabAlignment.LeftまたはTabAlignment.Rightにすると)、テキストが表示されなくなります。また、タブを下に表示したとき(TabControl.AlignmentプロパティをTabAlignment.Bottomにしたとき)は、テキストは表示されますが、タブの向きが上下逆になります。これを回避するには、ビジュアルスタイルを無効にするか、タブを自分で描画します。ここではこれらの方法を説明します。

TabControlのビジュアルスタイルを無効にする

アプリケーションのビジュアルスタイルを無効にする方法は、「コントロールの外観をビジュアルスタイル(XPスタイル)にする」で説明しています。しかしこの方法では、すべてのコントロールのビジュアルスタイルが無効になってしまいます。

あるコントロールだけビジュアルスタイルを無効にするには、Win32 APIのSetWindowTheme関数を使います。

以下に、TabControl1のビジュアルスタイルを無効にする例を示します。

ビジュアルスタイルが無効のTabControl

VB.NET
コードを隠すコードを選択
<System.Runtime.InteropServices.DllImportAttribute("uxtheme.dll")> _
Private Shared Function SetWindowTheme( _
    ByVal hwnd As IntPtr, _
    ByVal subAppName As String, _
    ByVal subIdList As String) As Integer
End Function

'フォームのLoadイベントハンドラ
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) _
        Handles MyBase.Load
    'TabControl1のVisual Styleを無効にする
    SetWindowTheme(TabControl1.Handle, "", "")
End Sub
C#
コードを隠すコードを選択
[System.Runtime.InteropServices.DllImportAttribute("uxtheme.dll")]
private static extern int SetWindowTheme(
    IntPtr hwnd, string subAppName, string subIdList);

//フォームのLoadイベントハンドラ
private void Form1_Load(object sender, System.EventArgs e)
{
    //TabControl1のVisual Styleを無効にする
    SetWindowTheme(TabControl1.Handle, "", "");
}

この方法を使って、TabControlクラスの派生クラスとして、ビジュアルスタイルが無効になったタブコントロールを作成すると、次のようになります。なおこのクラスの使い方が分からないという方は、こちらをご覧ください。

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

''' <summary>
''' ビジュアルスタイルが無効のTabControl
''' </summary>
Public Class NoVisualTabControl
    Inherits TabControl
    <System.Runtime.InteropServices.DllImportAttribute("uxtheme.dll")> _
    Private Shared Function SetWindowTheme( _
        ByVal hwnd As IntPtr, _
        ByVal subAppName As String, _
        ByVal subIdList As String) As Integer
    End Function

    Protected Overrides Sub OnHandleCreated(ByVal e As EventArgs)
        SetWindowTheme(Me.Handle, "", "")
        MyBase.OnHandleCreated(e)
    End Sub
End Class
C#
コードを隠すコードを選択
using System;
using System.Windows.Forms;

/// <summary>
/// ビジュアルスタイルが無効のTabControl
/// </summary>
public class NoVisualTabControl : TabControl
{
    [System.Runtime.InteropServices.DllImportAttribute("uxtheme.dll")]
    private static extern int SetWindowTheme(
        IntPtr hwnd, string subAppName, string subIdList);

    protected override void OnHandleCreated(EventArgs e)
    {
        SetWindowTheme(this.Handle, "", "");
        base.OnHandleCreated(e);
    }
}

TabControlを自分でビジュアルスタイルで描画する

次に、TabControlを自分で描画して、ビジュアルスタイルっぽくする方法を考えてみます。TabControlをオーナードローする方法は「TabControlのタブを自分で描画する」で、タブをビジュアルスタイルで描画する方法は「Visualスタイルでコントロールを描画する」で紹介していますので、両者を使えばできそうですが、実際にはそう簡単ではありません。DrawItemイベントでタブを描画する方法ではタブの枠を描画できませんので、TabRenderer.DrawTabItemメソッドでビジュアルスタイルのタブを描画してもおかしくなってしまいます。

そこで、DrawItemイベントではなく、PaintイベントでTabControl全体を描画することにします。TabControlでは通常Paintイベントが発生しませんので、ControlStyles.UserPaintをTrueにする必要があります。

このような方法でビジュアルスタイルのタブを描画する例を示します。このクラスはTabControlから派生しています。OnPaintメソッドでは、TabRenderer.DrawTabItemメソッドで描画した画像をタブの向きに合わせて回転させて表示しています。細かい位置の修正なども行っていますが(試行錯誤で行ないました)、詳しくはコメントをご覧ください。AppearanceプロパティがTabAppearance.Normal以外でもタブを描画したり、ImageListプロパティを無視して画像を表示しなかったりと手抜きも多いですが、あくまでサンプルということで、大目に見てください。

なおこのクラスの使い方が分からないという方は、こちらをご覧ください。

タブが横や下にあっても正常に表示されるTabControl

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

''' <summary>
''' タブが横や下にあっても正常に表示するTabControl
''' </summary>
Public Class VisualTabControl
    Inherits TabControl

    Public Sub New()
        MyBase.New()
        'Paintイベントで描画できるようにする
        Me.SetStyle(ControlStyles.UserPaint, True)
        'ダブルバッファリングを有効にする
        Me.DoubleBuffered = True
        'Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
        'Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
        ''Me.SetStyle(ControlStyles.DoubleBuffer, True)
        'リサイズで再描画する
        Me.ResizeRedraw = True
        'Me.SetStyle(ControlStyles.ResizeRedraw, True)

        'ControlStyles.UserPaintをTrueすると、
        'SizeModeは強制的にTabSizeMode.Fixedにされる
        Me.SizeMode = TabSizeMode.Fixed
        Me.ItemSize = New Size(80, 18)
        Me.Appearance = TabAppearance.Normal
        Me.Multiline = True
    End Sub

    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        MyBase.OnPaint(e)

        'TabControlの背景を塗る
        e.Graphics.FillRectangle(SystemBrushes.Control, Me.ClientRectangle)

        If Me.TabPages.Count = 0 Then
            Return
        End If

        'TabPageの枠を描画する
        Dim page As TabPage = Me.TabPages(Me.SelectedIndex)
        Dim pageRect As New Rectangle(page.Bounds.X - 2, _
                                      page.Bounds.Y - 2, _
                                      page.Bounds.Width + 5, _
                                      page.Bounds.Height + 5)
        TabRenderer.DrawTabPage(e.Graphics, pageRect)

        'タブを描画する
        For i As Integer = 0 To Me.TabPages.Count - 1
            page = Me.TabPages(i)
            Dim tabRect As Rectangle = Me.GetTabRect(i)

            '表示するタブの状態を決定する
            Dim state As System.Windows.Forms.VisualStyles.TabItemState
            If Not Me.Enabled Then
                state = System.Windows.Forms.VisualStyles.TabItemState.Disabled
            ElseIf Me.SelectedIndex = i Then
                state = System.Windows.Forms.VisualStyles.TabItemState.Selected
            Else
                state = System.Windows.Forms.VisualStyles.TabItemState.Normal
            End If

            '選択されたタブとページの間の境界線を消すために、
            '描画する範囲を大きくする
            If Me.SelectedIndex = i Then
                If Me.Alignment = TabAlignment.Top Then
                    tabRect.Height += 1
                ElseIf Me.Alignment = TabAlignment.Bottom Then
                    tabRect.Y -= 2
                    tabRect.Height += 2
                ElseIf Me.Alignment = TabAlignment.Left Then
                    tabRect.Width += 1
                ElseIf Me.Alignment = TabAlignment.Right Then
                    tabRect.X -= 2
                    tabRect.Width += 2
                End If
            End If

            '画像のサイズを決定する
            Dim imgSize As Size
            If Me.Alignment = TabAlignment.Left OrElse _
                Me.Alignment = TabAlignment.Right Then
                imgSize = New Size(tabRect.Height, tabRect.Width)
            Else
                imgSize = tabRect.Size
            End If

            'Bottomの時はTextを表示しない(Textを回転させないため)
            Dim tabText As String = page.Text
            If Me.Alignment = TabAlignment.Bottom Then
                tabText = ""
            End If

            'タブの画像を作成する
            Dim bmp As New Bitmap(imgSize.Width, imgSize.Height)
            Dim g As Graphics = Graphics.FromImage(bmp)
            '高さに1足しているのは、下にできる空白部分を消すため
            TabRenderer.DrawTabItem(g, _
                New Rectangle(0, 0, bmp.Width, bmp.Height + 1), _
                tabText, _
                page.Font, _
                False, _
                state)
            g.Dispose()

            '画像を回転する
            If Me.Alignment = TabAlignment.Bottom Then
                bmp.RotateFlip(RotateFlipType.Rotate180FlipNone)
            ElseIf Me.Alignment = TabAlignment.Left Then
                bmp.RotateFlip(RotateFlipType.Rotate270FlipNone)
            ElseIf Me.Alignment = TabAlignment.Right Then
                bmp.RotateFlip(RotateFlipType.Rotate90FlipNone)
            End If

            'Bottomの時はTextを描画する
            If Me.Alignment = TabAlignment.Bottom Then
                Dim sf As New StringFormat()
                sf.Alignment = StringAlignment.Center
                sf.LineAlignment = StringAlignment.Center
                g = Graphics.FromImage(bmp)
                g.DrawString(page.Text, _
                             page.Font, _
                             SystemBrushes.ControlText, _
                             New RectangleF(0, 0, bmp.Width, bmp.Height), _
                             sf)
                g.Dispose()
                sf.Dispose()
            End If

            '画像を描画する
            e.Graphics.DrawImage(bmp, tabRect.X, tabRect.Y, bmp.Width, bmp.Height)

            bmp.Dispose()
        Next
    End Sub
End Class
C#
コードを隠すコードを選択
using System;
using System.Drawing;
using System.Windows.Forms;

/// <summary>
/// タブが横や下にあっても正常に表示するTabControl
/// </summary>
public class VisualTabControl : TabControl
{
    public VisualTabControl()
        : base()
    {
        //Paintイベントで描画できるようにする
        this.SetStyle(ControlStyles.UserPaint, true);
        //ダブルバッファリングを有効にする
        this.DoubleBuffered = true;
        //this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
        //this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        ////this.SetStyle(ControlStyles.DoubleBuffer, true);
        //リサイズで再描画する
        this.ResizeRedraw = true;
        //this.SetStyle(ControlStyles.ResizeRedraw, true);

        //ControlStyles.UserPaintをTrueすると、
        //SizeModeは強制的にTabSizeMode.Fixedにされる
        this.SizeMode = TabSizeMode.Fixed;
        this.ItemSize = new Size(80, 18);
        this.Appearance = TabAppearance.Normal;
        this.Multiline = true;
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        //TabControlの背景を塗る
        e.Graphics.FillRectangle(SystemBrushes.Control, this.ClientRectangle);

        if (this.TabPages.Count == 0)
            return;

        //TabPageの枠を描画する
        TabPage page = this.TabPages[this.SelectedIndex];
        Rectangle pageRect = new Rectangle(
            page.Bounds.X - 2,
            page.Bounds.Y - 2,
            page.Bounds.Width + 5,
            page.Bounds.Height + 5);
        TabRenderer.DrawTabPage(e.Graphics, pageRect);

        //タブを描画する
        for (int i = 0; i < this.TabPages.Count; i++)
        {
            page = this.TabPages[i];
            Rectangle tabRect = this.GetTabRect(i);

            //表示するタブの状態を決定する
            System.Windows.Forms.VisualStyles.TabItemState state;
            if (!this.Enabled)
            {
                state = System.Windows.Forms.VisualStyles.TabItemState.Disabled;
            }
            else if (this.SelectedIndex == i)
            {
                state = System.Windows.Forms.VisualStyles.TabItemState.Selected;
            }
            else
            {
                state = System.Windows.Forms.VisualStyles.TabItemState.Normal;
            }

            //選択されたタブとページの間の境界線を消すために、
            //描画する範囲を大きくする
            if (this.SelectedIndex == i)
            {
                if (this.Alignment == TabAlignment.Top)
                {
                    tabRect.Height += 1;
                }
                else if (this.Alignment == TabAlignment.Bottom)
                {
                    tabRect.Y -= 2;
                    tabRect.Height += 2;
                }
                else if (this.Alignment == TabAlignment.Left)
                {
                    tabRect.Width += 1;
                }
                else if (this.Alignment == TabAlignment.Right)
                {
                    tabRect.X -= 2;
                    tabRect.Width += 2;
                }
            }

            //画像のサイズを決定する
            Size imgSize;
            if (this.Alignment == TabAlignment.Left ||
                this.Alignment == TabAlignment.Right)
            {
                imgSize = new Size(tabRect.Height, tabRect.Width);
            }
            else
            {
                imgSize = tabRect.Size;
            }

            //Bottomの時はTextを表示しない(Textを回転させないため)
            string tabText = page.Text;
            if (this.Alignment == TabAlignment.Bottom)
            {
                tabText = "";
            }

            //タブの画像を作成する
            Bitmap bmp = new Bitmap(imgSize.Width, imgSize.Height);
            Graphics g = Graphics.FromImage(bmp);
            //高さに1足しているのは、下にできる空白部分を消すため
            TabRenderer.DrawTabItem(g,
                new Rectangle(0, 0, bmp.Width, bmp.Height + 1),
                tabText,
                page.Font,
                false,
                state);
            g.Dispose();

            //画像を回転する
            if (this.Alignment == TabAlignment.Bottom)
            {
                bmp.RotateFlip(RotateFlipType.Rotate180FlipNone);
            }
            else if (this.Alignment == TabAlignment.Left)
            {
                bmp.RotateFlip(RotateFlipType.Rotate270FlipNone);
            }
            else if (this.Alignment == TabAlignment.Right)
            {
                bmp.RotateFlip(RotateFlipType.Rotate90FlipNone);
            }

            //Bottomの時はTextを描画する
            if (this.Alignment == TabAlignment.Bottom)
            {
                StringFormat sf = new StringFormat();
                sf.Alignment = StringAlignment.Center;
                sf.LineAlignment = StringAlignment.Center;
                g = Graphics.FromImage(bmp);
                g.DrawString(page.Text,
                    page.Font,
                    SystemBrushes.ControlText,
                    new RectangleF(0, 0, bmp.Width, bmp.Height),
                    sf);
                g.Dispose();
                sf.Dispose();
            }

            //画像を描画する
            e.Graphics.DrawImage(bmp, tabRect.X, tabRect.Y, bmp.Width, bmp.Height);

            bmp.Dispose();
        }
    }
}

サードパーティ製のタブコントロールを使用する

ビジュアルスタイルを無効にしたくはないし、自分で描画するのも面倒だという場合は、サードパーティ製のタブコントロールを使用するという選択肢もあります。以下に無料で使えるタブコントロールを紹介します。

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

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。