在Winforms中实现水平文本的垂直选项卡控件

15

我希望我的TabControl标签可以显示在左侧或右侧。
然而,与System.Windows.Forms.TabControl不同的是,我希望文本保持水平,而不是被旋转90度或270度。

以下是几张说明这个概念的图片: Visual Studio中的垂直选项卡 Firefox中的垂直选项卡

虽然我可以自己编写代码实现这一点,大约需要一到两个小时,但我想先询问一下是否有任何现有的Winforms控件实现这样的功能。

NB:任何现有的解决方案最好是非商业化的。

谢谢。

5个回答

22

我不知道这个方法有多可靠,也不能声称是我创建的,但是...

http://www.dreamincode.net/forums/topic/125792-how-to-make-vertical-tabs/

下面是实现的方法:

首先,我们将把它的对齐方式改为左侧,通过设置属性:

Alignment = Left

如果您启用了XP主题,则可能会注意到选项卡控件的奇怪布局。别担心,我们会处理好它。

如您所见,选项卡是垂直的,而我们的要求是水平的。因此,我们可以更改选项卡的大小。但在此之前,我们必须设置SizeMode属性为:

SizeMode = Fixed

现在我们可以使用ItemSize属性来更改大小:

ItemSize = 30, 120 Width = 30 and Height = 120

将Alignment设置为Left后,选项卡控件会旋转标签,导致宽度和高度似乎被颠倒。这就是为什么当我们增加高度时,我们看到宽度在增加,而当我们增加宽度时,高度受影响的原因。

现在文本也将显示,但是垂直显示。不幸的是,解决这个问题没有简单的方法。为此,我们必须自己编写文本。为此,我们首先将设置DrawMode属性:

DrawMode = OwnerDrawFixed

01

Private Sub TabControl1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles TabControl1.DrawItem
    Dim g As Graphics
    Dim sText As String

    Dim iX As Integer
    Dim iY As Integer
    Dim sizeText As SizeF

    Dim ctlTab As TabControl

    ctlTab = CType(sender, TabControl)

    g = e.Graphics

    sText = ctlTab.TabPages(e.Index).Text
    sizeText = g.MeasureString(sText, ctlTab.Font)

    iX = e.Bounds.Left + 6
    iY = e.Bounds.Top + (e.Bounds.Height - sizeText.Height) / 2

    g.DrawString(sText, ctlTab.Font, Brushes.Black, iX, iY)
End Sub

谢谢你的帮助。我稍微调整了一下代码,但并没有像从头开始编写一个新控件那样费时。 - Alex Essilfie
@AlexEssilfie,你把它做得和你包含的截图完全一样了吗?如果是这样,你能分享一下代码吗? - Amit Andharia
@AmitAndharia:我并不希望它完全像截图中展示的那样,但我已经成功让它对我来说足够好用了。为了更广泛的社区利益,我会在今天晚些时候或最多三天内上传我的代码。 - Alex Essilfie
@AmitAndharia:我已经上传了代码。请查看我的回答获取详细信息。 - Alex Essilfie

9
这是一个我非常喜欢的自定义选项卡控件的代码。您需要将此代码复制并粘贴到一个新类中,然后重新构建项目。您将在工具箱中看到一个新的自定义用户控件。

Vertical Tab Control with Indicator and ImageList

    Imports System.Drawing.Drawing2D
Class DotNetBarTabcontrol
    Inherits TabControl

    Sub New()
        SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.ResizeRedraw Or ControlStyles.UserPaint Or ControlStyles.DoubleBuffer, True)
        DoubleBuffered = True
        SizeMode = TabSizeMode.Fixed
        ItemSize = New Size(44, 136)
    End Sub
    Protected Overrides Sub CreateHandle()
        MyBase.CreateHandle()
        Alignment = TabAlignment.Left
    End Sub

    Function ToPen(ByVal color As Color) As Pen
        Return New Pen(color)
    End Function

    Function ToBrush(ByVal color As Color) As Brush
        Return New SolidBrush(color)
    End Function

    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        Dim B As New Bitmap(Width, Height)
        Dim G As Graphics = Graphics.FromImage(B)
        Try : SelectedTab.BackColor = Color.White : Catch : End Try
        G.Clear(Color.White)
        G.FillRectangle(New SolidBrush(Color.FromArgb(246, 248, 252)), New Rectangle(0, 0, ItemSize.Height + 4, Height))
        'G.DrawLine(New Pen(Color.FromArgb(170, 187, 204)), New Point(Width - 1, 0), New Point(Width - 1, Height - 1))    'comment out to get rid of the borders
        'G.DrawLine(New Pen(Color.FromArgb(170, 187, 204)), New Point(ItemSize.Height + 1, 0), New Point(Width - 1, 0))                   'comment out to get rid of the borders
        'G.DrawLine(New Pen(Color.FromArgb(170, 187, 204)), New Point(ItemSize.Height + 3, Height - 1), New Point(Width - 1, Height - 1)) 'comment out to get rid of the borders
        G.DrawLine(New Pen(Color.FromArgb(170, 187, 204)), New Point(ItemSize.Height + 3, 0), New Point(ItemSize.Height + 3, 999))
        For i = 0 To TabCount - 1
            If i = SelectedIndex Then
                Dim x2 As Rectangle = New Rectangle(New Point(GetTabRect(i).Location.X - 2, GetTabRect(i).Location.Y - 2), New Size(GetTabRect(i).Width + 3, GetTabRect(i).Height - 1))
                Dim myBlend As New ColorBlend()
                myBlend.Colors = {Color.FromArgb(232, 232, 240), Color.FromArgb(232, 232, 240), Color.FromArgb(232, 232, 240)}
                myBlend.Positions = {0.0F, 0.5F, 1.0F}
                Dim lgBrush As New LinearGradientBrush(x2, Color.Black, Color.Black, 90.0F)
                lgBrush.InterpolationColors = myBlend
                G.FillRectangle(lgBrush, x2)
                G.DrawRectangle(New Pen(Color.FromArgb(170, 187, 204)), x2)


                G.SmoothingMode = SmoothingMode.HighQuality
                Dim p() As Point = {New Point(ItemSize.Height - 3, GetTabRect(i).Location.Y + 20), New Point(ItemSize.Height + 4, GetTabRect(i).Location.Y + 14), New Point(ItemSize.Height + 4, GetTabRect(i).Location.Y + 27)}
                G.FillPolygon(Brushes.White, p)
                G.DrawPolygon(New Pen(Color.FromArgb(170, 187, 204)), p)

                If ImageList IsNot Nothing Then
                    Try
                        If ImageList.Images(TabPages(i).ImageIndex) IsNot Nothing Then

                            G.DrawImage(ImageList.Images(TabPages(i).ImageIndex), New Point(x2.Location.X + 8, x2.Location.Y + 6))
                            G.DrawString("      " & TabPages(i).Text, Font, Brushes.DimGray, x2, New StringFormat With {.LineAlignment = StringAlignment.Center, .Alignment = StringAlignment.Center})
                        Else
                            G.DrawString(TabPages(i).Text, New Font(Font.FontFamily, Font.Size, FontStyle.Bold), Brushes.DimGray, x2, New StringFormat With {.LineAlignment = StringAlignment.Center, .Alignment = StringAlignment.Center})
                        End If
                    Catch ex As Exception
                        G.DrawString(TabPages(i).Text, New Font(Font.FontFamily, Font.Size, FontStyle.Bold), Brushes.DimGray, x2, New StringFormat With {.LineAlignment = StringAlignment.Center, .Alignment = StringAlignment.Center})
                    End Try
                Else
                    G.DrawString(TabPages(i).Text, New Font(Font.FontFamily, Font.Size, FontStyle.Bold), Brushes.DimGray, x2, New StringFormat With {.LineAlignment = StringAlignment.Center, .Alignment = StringAlignment.Center})
                End If

                G.DrawLine(New Pen(Color.FromArgb(200, 200, 250)), New Point(x2.Location.X - 1, x2.Location.Y - 1), New Point(x2.Location.X, x2.Location.Y))
                G.DrawLine(New Pen(Color.FromArgb(200, 200, 250)), New Point(x2.Location.X - 1, x2.Bottom - 1), New Point(x2.Location.X, x2.Bottom))
            Else
                Dim x2 As Rectangle = New Rectangle(New Point(GetTabRect(i).Location.X - 2, GetTabRect(i).Location.Y - 2), New Size(GetTabRect(i).Width + 3, GetTabRect(i).Height + 1))
                G.FillRectangle(New SolidBrush(Color.FromArgb(246, 248, 252)), x2)
                G.DrawLine(New Pen(Color.FromArgb(170, 187, 204)), New Point(x2.Right, x2.Top), New Point(x2.Right, x2.Bottom))
                If ImageList IsNot Nothing Then
                    Try
                        If ImageList.Images(TabPages(i).ImageIndex) IsNot Nothing Then
                            G.DrawImage(ImageList.Images(TabPages(i).ImageIndex), New Point(x2.Location.X + 8, x2.Location.Y + 6))
                            G.DrawString("      " & TabPages(i).Text, Font, Brushes.DimGray, x2, New StringFormat With {.LineAlignment = StringAlignment.Center, .Alignment = StringAlignment.Center})
                        Else
                            G.DrawString(TabPages(i).Text, Font, Brushes.DimGray, x2, New StringFormat With {.LineAlignment = StringAlignment.Center, .Alignment = StringAlignment.Center})
                        End If
                    Catch ex As Exception
                        G.DrawString(TabPages(i).Text, Font, Brushes.DimGray, x2, New StringFormat With {.LineAlignment = StringAlignment.Center, .Alignment = StringAlignment.Center})
                    End Try
                Else
                    G.DrawString(TabPages(i).Text, Font, Brushes.DimGray, x2, New StringFormat With {.LineAlignment = StringAlignment.Center, .Alignment = StringAlignment.Center})
                End If
            End If
        Next

        e.Graphics.DrawImage(B.Clone, 0, 0)
        G.Dispose() : B.Dispose()
    End Sub
End Class

当屏幕上的项目超出可见部分时,我该如何激活滚动条? - J. Rodríguez
@Jorny 提供了 C# 示例。 - Jonas
1
有很多缺失的Dispose()调用需要添加。我将其翻译成C#,但它到处都泄漏了。StringFormat、Font、Pen、Brush都需要调用Dispose()。 - DrBB
我能够创建这个控件,并且我使用了USING来确保控件不会泄漏任何资源。在运行时测试表明它确实没有泄漏。然而,当你在一个页面上放置多个tablelayoutpanels以控制表单的布局和缩放时,在设计模式下它会导致Visual Studio崩溃 - 严重的问题。我已经确定问题出现在绘制子程序中,该子程序似乎每隔半秒钟就被连续调用一次。 - John Tamburo
我成功创建了这个控件,并使用USING确保该控件不会泄漏任何资源。在运行时测试表明,它确实没有泄漏。然而,当您在一个页面上放置多个tablelayoutpanels以控制表单的布局和缩放时,在设计模式下它会导致Visual Studio崩溃。我已经确定问题出现在paint子程序中,该子程序似乎每半秒钟就被连续调用一次。 - John Tamburo

8
这是一个古老的问题,但我找到了一个看起来不错的C#类。在添加常规WinForms选项卡控件后,将其作为类添加到您的项目中,例如DotNetBarTabControl.cs。然后确保将Winforms选项卡控件替换为此类。
  1. this.tabControl1 = new System.Windows.Forms.TabControl();

    必须更改为:

    this.tabControl1 = new TabControls.DotNetBarTabControl();


  1. private System.Windows.Forms.TabControl tabControl1;

    必须更改为:

    private TabControls.DotNetBarTabControl tabControl1;


DotNetBarTabControl.cs类:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

// thanks to Mavamaarten~ for coding this

namespace TabControls
{
    internal class DotNetBarTabControl : TabControl
    {
        public DotNetBarTabControl()
        {
            SetStyle(
                ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.UserPaint |
                ControlStyles.DoubleBuffer, true);
            SizeMode = TabSizeMode.Fixed;
            ItemSize = new Size(44, 136);
            Alignment = TabAlignment.Left;
            SelectedIndex = 0;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            Bitmap b = new Bitmap(Width, Height);
            Graphics g = Graphics.FromImage(b);
            if (!DesignMode)
                SelectedTab.BackColor = SystemColors.Control;
            g.Clear(SystemColors.Control);
            g.FillRectangle(new SolidBrush(Color.FromArgb(246, 248, 252)),
                new Rectangle(0, 0, ItemSize.Height + 4, Height));
            g.DrawLine(new Pen(Color.FromArgb(170, 187, 204)), new Point(ItemSize.Height + 3, 0),
                new Point(ItemSize.Height + 3, 999));
            g.DrawLine(new Pen(Color.FromArgb(170, 187, 204)), new Point(0, Size.Height - 1),
                new Point(Width + 3, Size.Height - 1));
            for (int i = 0; i <= TabCount - 1; i++)
            {
                if (i == SelectedIndex)
                {
                    Rectangle x2 = new Rectangle(new Point(GetTabRect(i).Location.X - 2, GetTabRect(i).Location.Y - 2),
                        new Size(GetTabRect(i).Width + 3, GetTabRect(i).Height - 1));
                    ColorBlend myBlend = new ColorBlend();
                    myBlend.Colors = new Color[] { Color.FromArgb(232, 232, 240), Color.FromArgb(232, 232, 240), Color.FromArgb(232, 232, 240) };
                    myBlend.Positions = new float[] { 0f, 0.5f, 1f };
                    LinearGradientBrush lgBrush = new LinearGradientBrush(x2, Color.Black, Color.Black, 90f);
                    lgBrush.InterpolationColors = myBlend;
                    g.FillRectangle(lgBrush, x2);
                    g.DrawRectangle(new Pen(Color.FromArgb(170, 187, 204)), x2);

                    g.SmoothingMode = SmoothingMode.HighQuality;
                    Point[] p =
                    {
                        new Point(ItemSize.Height - 3, GetTabRect(i).Location.Y + 20),
                        new Point(ItemSize.Height + 4, GetTabRect(i).Location.Y + 14),
                        new Point(ItemSize.Height + 4, GetTabRect(i).Location.Y + 27)
                    };
                    g.FillPolygon(SystemBrushes.Control, p);
                    g.DrawPolygon(new Pen(Color.FromArgb(170, 187, 204)), p);

                    if (ImageList != null)
                    {
                        try
                        {
                            g.DrawImage(ImageList.Images[TabPages[i].ImageIndex],
                                new Point(x2.Location.X + 8, x2.Location.Y + 6));
                            g.DrawString("  " + TabPages[i].Text, Font, Brushes.Black, x2, new StringFormat
                            {
                                LineAlignment = StringAlignment.Center,
                                Alignment = StringAlignment.Center
                            });
                        }
                        catch (Exception)
                        {
                            g.DrawString(TabPages[i].Text, new Font(Font.FontFamily, Font.Size, FontStyle.Bold),
                                Brushes.Black, x2, new StringFormat
                                {
                                    LineAlignment = StringAlignment.Center,
                                    Alignment = StringAlignment.Center
                                });
                        }
                    }
                    else
                    {
                        g.DrawString(TabPages[i].Text, new Font(Font.FontFamily, Font.Size, FontStyle.Bold),
                            Brushes.Black, x2, new StringFormat
                            {
                                LineAlignment = StringAlignment.Center,
                                Alignment = StringAlignment.Center
                            });
                    }

                    g.DrawLine(new Pen(Color.FromArgb(200, 200, 250)), new Point(x2.Location.X - 1, x2.Location.Y - 1),
                        new Point(x2.Location.X, x2.Location.Y));
                    g.DrawLine(new Pen(Color.FromArgb(200, 200, 250)), new Point(x2.Location.X - 1, x2.Bottom - 1),
                        new Point(x2.Location.X, x2.Bottom));
                }
                else
                {
                    Rectangle x2 = new Rectangle(new Point(GetTabRect(i).Location.X - 2, GetTabRect(i).Location.Y - 2),
                        new Size(GetTabRect(i).Width + 3, GetTabRect(i).Height - 1));
                    g.FillRectangle(new SolidBrush(Color.FromArgb(246, 248, 252)), x2);
                    g.DrawLine(new Pen(Color.FromArgb(170, 187, 204)), new Point(x2.Right, x2.Top),
                        new Point(x2.Right, x2.Bottom));
                    if (ImageList != null)
                    {
                        try
                        {
                            g.DrawImage(ImageList.Images[TabPages[i].ImageIndex],
                                new Point(x2.Location.X + 8, x2.Location.Y + 6));
                            g.DrawString("  " + TabPages[i].Text, Font, Brushes.DimGray, x2, new StringFormat
                            {
                                LineAlignment = StringAlignment.Center,
                                Alignment = StringAlignment.Center
                            });
                        }
                        catch (Exception)
                        {
                            g.DrawString(TabPages[i].Text, Font, Brushes.DimGray, x2, new StringFormat
                            {
                                LineAlignment = StringAlignment.Center,
                                Alignment = StringAlignment.Center
                            });
                        }
                    }
                    else
                    {
                        g.DrawString(TabPages[i].Text, Font, Brushes.DimGray, x2, new StringFormat
                        {
                            LineAlignment = StringAlignment.Center,
                            Alignment = StringAlignment.Center
                        });
                    }
                }
            }

            e.Graphics.DrawImage(b, new Point(0, 0));
            g.Dispose();
            b.Dispose();
        }
    }
}

enter image description here

你也可以制作一个深色主题。在照片中,Form2使用了Material Skin。它也可以在NuGet上找到。

5
我决定分享我开发的代码,因为一些人(例如Amit Andharia)想从中受益。
这是我实现Rob P.的答案后的结果。 Vertical Tabs Control screenshot 发布说明: - 全面支持设计时 - 标签自动调整大小(最大128px宽) - 实现了标签图标 - 隐藏了未使用的属性
可以在这里下载代码。

谢谢Alex。我一直在想它会与你发布的截图类似,因此对查看代码很感兴趣,不过你分享的代码真的很有帮助。 - Amit Andharia
1
@AmitAndharia:我曾经写过一个复制 Visual Studio 风格的插件,但不幸的是我丢失了那段代码,所以我只能修复这个插件并将其打包成 .vb 文件供使用。如果你需要,我可以重新实现 VS 风格的插件,但我不能保证很快完成。 - Alex Essilfie
谢谢,如果您能做类似的事情并分享,那将是非常棒的。 - Amit Andharia
@dahsra 我还没有写C#版本。我需要花一两天时间进行转换,并提供给您链接。 - Alex Essilfie
死链,请更新。 - Gray Programmerz
@GrayProgrammerz:如果网站在几天内无法恢复在线状态,我将检查我的代码存档并重新上传文件。 - Alex Essilfie

3

微软在MSDN上提供了一篇教程,介绍如何在现有TabControl上进行操作。C#和Visual Basic .NET两种编程语言均已提供示例代码。他们的方法基于使用拥有者绘制技术。以下是他们的步骤总结:

  1. Set the TabControl's Alignment property to Right.

  2. Ensure all tabs are the same horizontal width by setting the SizeMode property to Fixed.

  3. Set the ItemSize property to your preferred size for the tabs, keeping in mind that the width and height are reversed.

  4. Set the DrawMode property to OwnerDrawFixed.

  5. Set up an event handler for the TabControl's DrawItem event, and place your owner drawing code in there dictating how each tab should be displayed. Their C# sample code for the event handler is reproduced below for convenience (it assumes your TabControl is named tabControl1:

    private void tabControl1_DrawItem(Object sender, System.Windows.Forms.DrawItemEventArgs e)
    {
        Graphics g = e.Graphics;
        Brush _textBrush;
    
        // Get the item from the collection.
        TabPage _tabPage = tabControl1.TabPages[e.Index];
    
        // Get the real bounds for the tab rectangle.
        Rectangle _tabBounds = tabControl1.GetTabRect(e.Index);
    
        if (e.State == DrawItemState.Selected)
        {
    
            // Draw a different background color, and don't paint a focus rectangle.
            _textBrush = new SolidBrush(Color.Red);
            g.FillRectangle(Brushes.Gray, e.Bounds);
        }
        else
        {
            _textBrush = new System.Drawing.SolidBrush(e.ForeColor);
            e.DrawBackground();
        }
    
        // Use our own font.
        Font _tabFont = new Font("Arial", (float)10.0, FontStyle.Bold, GraphicsUnit.Pixel);
    
        // Draw string. Center the text.
        StringFormat _stringFlags = new StringFormat();
        _stringFlags.Alignment = StringAlignment.Center;
        _stringFlags.LineAlignment = StringAlignment.Center;
        g.DrawString(_tabPage.Text, _tabFont, _textBrush, _tabBounds, new StringFormat(_stringFlags));
    }
    
你可以尝试使用ItemSize属性和上面代码中的_tabFont值来微调选项卡的外观,以满足您的需求。如果需要更花哨的样式,建议从此MSDN文章开始查看。
(来源:如何:使用TabControl显示侧向对齐的选项卡 (MSDN)

你读过Rob P.的回答吗?你在他发表回答近六年后几乎重复了完全相同的内容 - Alex Essilfie
2
@AlexEssilfie 我不会说完全一样。首先,我提供的代码是用C#编写的,而他提供的是用VB.NET编写的。其次,我使用MSDN作为我的来源,而不是来自其他地方的论坛帖子,因此我展示的代码版本更有可能代表最佳实践来处理这个问题。第三,如果你愿意看的话,代码中有微妙的差异(使用GetTabRect()而不是e.Bounds,代码考虑选定状态等)。基本上,我发布我的答案是因为我相信我所分享的信息更加完整和高质量。 - Knowledge Cube

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接