如何在Winforms中更改选项卡控件的背景颜色?

tjjdgumg  于 2023-08-07  发布在  其他
关注(0)|答案(7)|浏览(167)

有没有办法改变winforms中标签控件的背景颜色,使其周围没有白色边框?
我已经尝试了几种不同的方法,但它们都导致显示相同的白色边框。

rryofs0p

rryofs0p1#

TabControl对定制的支持非常差。我使用this custom tab control取得了很好的成功。如果你想像我一样改变外观,代码是非常有用的。

c0vxltue

c0vxltue2#

我只能想到将Appearance属性更改为Buttons
MSDN TabControl Appearance

yyyllmsg

yyyllmsg3#

首先,你需要从TabControl创建一个派生类。到目前为止一切都很好,但现在它变得肮脏。
因为TabControl不会调用OnPaint,所以我们必须重写WndProc来处理WM_PAINT消息。在那里,我们继续用我们喜欢的颜色涂背景。

protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);
        if(m.Msg == (int) WindowsMessages.Win32Messages.WM_PAINT)
        {
            using (Graphics g = this.CreateGraphics())
            {
                //Double buffering stuff...
                BufferedGraphicsContext currentContext;
                BufferedGraphics myBuffer;
                currentContext = BufferedGraphicsManager.Current;
                myBuffer = currentContext.Allocate(g,
                   this.ClientRectangle);

                Rectangle r = ClientRectangle;

                //Painting background
                if(Enabled)
                    myBuffer.Graphics.FillRectangle(new SolidBrush(_backColor), r);
                else
                    myBuffer.Graphics.FillRectangle(Brushes.LightGray, r);

                //Painting border
                r.Height = this.DisplayRectangle.Height +1; //Using display rectangle hight because it excludes the tab headers already
                r.Y = this.DisplayRectangle.Y - 1; //Same for Y coordinate
                r.Width -= 5;
                r.X += 1;

                if(Enabled)
                    myBuffer.Graphics.DrawRectangle(new Pen(Color.FromArgb(255, 133, 158, 191), 1), r);
                else
                    myBuffer.Graphics.DrawRectangle(Pens.DarkGray, r);

                myBuffer.Render();
                myBuffer.Dispose();

                //Actual painting of items after Background was painted
                foreach (int index in ItemArgs.Keys)
                {
                    CustomDrawItem(ItemArgs[index]);
                }

            }
        }    
    }

字符串
我在这个方法中做了进一步的绘制,所以它看起来有点矫枉过正,但忽略不必要的东西。还要注意foreach循环。我以后再谈这个。
问题是TabControl在它自己的WM_PAINT之前**绘制了它的项目(标签页标题),所以我们的背景将被绘制在顶部,这使得它们不可见。为了解决这个问题,我为DrawItem做了一个EventHandler,如下所示:

private void DrawItemHandler(object sender, DrawItemEventArgs e)
    {
        //Save information about item in dictionary but dont do actual drawing
        if (!ItemArgs.ContainsKey(e.Index))
            ItemArgs.Add(e.Index, e);
        else
            ItemArgs[e.Index] = e;
    }


我将DrawItemEventArgs保存到一个字典中(在我的例子中称为“ItemArgs”),以便以后可以访问它们。这就是几秒钟前的foreach发挥作用的地方。它调用了一个方法,我正在绘制选项卡标题,该方法接受我们之前保存的DrawItemEventArgs作为参数,以正确的状态和位置绘制项目。
因此,简单地说,我们正在拦截标签标题的绘制,以延迟它,直到我们完成绘制背景。
这个解决方案不是最佳的,但它的工作和它的唯一的事情,你可以做更多的控制TabControl(lol)没有画它从头开始。

w1e3prcc

w1e3prcc4#

添加到@janhildebrandt的答案,因为它缺少一些关键部分,实际上使它的工作。

属性

TabControlDrawMode属性必须设置为TabDrawMode.OwnerDrawFixed,否则DrawItem事件处理程序将不会触发。
只需像这样重写派生的TabControl类中的属性:

public new TabDrawMode DrawMode
{
    get
    {
        return TabDrawMode.OwnerDrawFixed;
    }
    set
    {
        // No you dont.
    }
}

public MyTabControl() 
{
    base.DrawMode = TabDrawMode.OwnerDrawFixed;
}

字符串

DrawItemEventArgs

我不知道代码是在哪个版本中编写的,但存储TabItemGraphics对象在2023年在.Net 4.5及以上版本下将无法工作,也不需要。
相反,考虑使用如下结构:

private struct TabItemInfo
{
    public Color            BackColor;
    public Rectangle        Bounds;
    public Font             Font;
    public Color            ForeColor;
    public int              Index;
    public DrawItemState    State;

    public TabItemInfo(DrawItemEventArgs e)
    {
        this.BackColor  = e.BackColor;
        this.ForeColor  = e.ForeColor;
        this.Bounds     = e.Bounds;
        this.Font       = e.Font;
        this.Index      = e.Index;
        this.State      = e.State;
    }
}

private Dictionary<int, TabItemInfo> _tabItemStateMap = new Dictionary<int, TabItemInfo>();

DrawItem事件处理函数

当已经从Control本身派生时,不要分配事件处理程序。使用OnDrawItem(DrawItemEventArgs)方法:

protected override void OnDrawItem(DrawItemEventArgs e)
{
    base.OnDrawItem(e);
    if (!_tabItemStateMap.ContainsKey(e.Index)) 
    {
        _tabItemStateMap.Add(e.Index, new TabItemInfo(e));
    }
    else
    {
        _tabItemStateMap[e.Index] = new TabItemInfo(e);
    }
}

WndProc改进

您的TabControl将在设计模式下 Flink 。
通过检查WM_ERASEBKGND消息,可以轻松避免这种情况。只需在DesignMode期间忽略它即可:

private const int WM_PAINT      = 0x000F;
private const int WM_ERASEBKGND = 0x0014;        

// Cache context to avoid repeatedly re-creating the object.
// WM_PAINT is called frequently so it's better to declare it as a member.
private BufferedGraphicsContext _bufferContext = BufferedGraphicsManager.Current;

protected override void WndProc(ref Message m)
{            
    switch (m.Msg)
    {
        case WM_PAINT:
            {
                // Let system do its thing first.
                base.WndProc(ref m);
                
                // Custom paint Tab items.
                HandlePaint(ref m);

                break;
            }                    
        case WM_ERASEBKGND:
            {
                if (DesignMode)
                {
                    // Ignore to prevent flickering in DesignMode.
                }
                else
                {
                    base.WndProc(ref m);
                }
                break;
            }                    
        default:
            base.WndProc(ref m);
            break;
    }
}

private Color _backColor = Color.FromArgb(31, 31, 31);
[Browsable(true)]
[EditorBrowsable(EditorBrowsableState.Always)]
public new Color BackColor
{
    get
    {
        return _backColor;
    }
    set
    {
        _backColor = value;
    }
}

private void HandlePaint(ref Message m)
{
    using (var g = Graphics.FromHwnd(m.HWnd))
    {
        SolidBrush backBrush = new SolidBrush(BackColor);
        Rectangle r = ClientRectangle;
        using (var buffer = _bufferContext.Allocate(g, r))
        {                    
            if (Enabled)
            {
                buffer.Graphics.FillRectangle(backBrush, r);
            }
            else
            {
                buffer.Graphics.FillRectangle(backBrush, r);
            }

            // Paint items
            foreach (int index in _tabItemStateMap.Keys)
            {
                DrawTabItemInternal(buffer.Graphics, _tabItemStateMap[index]);
            }

            buffer.Render();
        }
        backBrush.Dispose();        
    }
}

private void DrawTabItemInternal(Graphics gr, TabItemInfo tabInfo) 
{
    /* Uncomment the two lines below to have each TabItem use the same height.
    ** The selected TabItem height will be slightly taller
    ** which makes unselected tabs float if you choose to 
    ** have a different BackColor for the TabControl background
    ** and your TabItem background. 
    */  
    
    // int fullHeight = _tabItemStateMap[this.SelectedIndex].Bounds.Height;
    // tabInfo.Bounds.Height = fullHeight;
    
    SolidBrush backBrush = new SolidBrush(BackColor);
    
    // Paint selected. 
    // You might want to choose a different color for the 
    // background or the text.
    if ((tabInfo.State & DrawItemState.Selected) == DrawItemState.Selected)
    {
        gr.FillRectangle(backBrush, tabInfo.Bounds);
        gr.DrawString(this.TabPages[tabInfo.Index].Text, tabInfo.Font, 
            SystemBrushes.ControlText, tabInfo.Bounds);
    }
    // Paint unselected.
    else
    {
        gr.FillRectangle(backBrush, tabInfo.Bounds);
        gr.DrawString(this.TabPages[tabInfo.Index].Text, tabInfo.Font, 
            SystemBrushes.ControlText, tabInfo.Bounds);
    }
    
    backBrush.Dispose();
}

进一步改进

SolidBrush

与其重新创建SolidBrush对象,不如考虑将它们声明为类的成员。
范例:

private SolidBrush _backBrush;
private SolidBrush _tabBackBrush;
private SolidBrush _tabForeBrush;

private Color _tabBackColor = Color.FromArgb(31, 31, 31);
public Color TabBackColor
{
    get
    {
        return _tabBackColor;
    }
    set
    {
        _tabBackColor = value;
        _tabBackBrush?.Dispose();
        _tabBackBrush = new SolidBrush(_tabBackColor);
    }
}

private Color _tabForeColor = Color.FromArgb(241, 241, 241);
public Color TabForeColor
{
    get
    {
        return _tabForeColor;
    }
    set
    {
        _tabForeColor = value;
        _tabForeBrush?.Dispose();
        _tabForeBrush = new SolidBrush(_tabForeColor);
    }
}

private Color _backColor = Color.FromArgb(31, 31, 31);
[Browsable(true)]
[EditorBrowsable(EditorBrowsableState.Always)]
public new Color BackColor
{
    get
    {
        return _backColor;
    }
    set
    {
        _backColor = value;
        _backBrush?.Dispose();
        _backBrush = new SolidBrush(_backColor);
    }
}

protected override void Dispose(bool disposing)
{
    _backBrush.Dispose();
    _tabBackBrush.Dispose();
    _tabForeBrush.Dispose();
    
    base.Dispose(disposing);
}

设置样式

使用ControlStyles.OptimizedDoubleBuffer可能会进一步减少 Flink (如果有的话)。

public MyTabControl() 
{
    this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}

特色

TabItem中的TextAlignment

在绘制TabItem的文本时传递一个StringFormat对象,以像使用Label一样定位文本

private StringFormat _tabTextFormat = new StringFormat();

private void UpdateTextAlign()
{
    switch (this.TextAlign)
    {
        case ContentAlignment.TopLeft:
            _tabTextFormat.Alignment = StringAlignment.Near;
            _tabTextFormat.LineAlignment = StringAlignment.Near;
            break;
        case ContentAlignment.TopCenter:
            _tabTextFormat.Alignment = StringAlignment.Center;
            _tabTextFormat.LineAlignment = StringAlignment.Near;
            break;
        case ContentAlignment.TopRight:
            _tabTextFormat.Alignment = StringAlignment.Far;
            _tabTextFormat.LineAlignment = StringAlignment.Near;
            break;
        case ContentAlignment.MiddleLeft:
            _tabTextFormat.Alignment = StringAlignment.Near;
            _tabTextFormat.LineAlignment = StringAlignment.Center;
            break;
        case ContentAlignment.MiddleCenter:
            _tabTextFormat.Alignment = StringAlignment.Center;
            _tabTextFormat.LineAlignment = StringAlignment.Center;
            break;
        case ContentAlignment.MiddleRight:
            _tabTextFormat.Alignment = StringAlignment.Far;
            _tabTextFormat.LineAlignment = StringAlignment.Center;
            break;
        case ContentAlignment.BottomLeft:
            _tabTextFormat.Alignment = StringAlignment.Near;
            _tabTextFormat.LineAlignment = StringAlignment.Far;
            break;
        case ContentAlignment.BottomCenter:
            _tabTextFormat.Alignment = StringAlignment.Center;
            _tabTextFormat.LineAlignment = StringAlignment.Far;
            break;
        case ContentAlignment.BottomRight:
            _tabTextFormat.Alignment = StringAlignment.Far;
            _tabTextFormat.LineAlignment = StringAlignment.Far;
            break;
    }
}

private ContentAlignment _textAlign = ContentAlignment.TopLeft;
public ContentAlignment TextAlign
{
    get
    {
        return _textAlign;
    }
    set
    {
        if (value != _textAlign)
        {
            _textAlign = value;
            UpdateTextAlign();
        }                
    }
}

private void DrawTabItemInternal(Graphics gr, TabItemInfo tabInfo) 
{
    if ((tabInfo.State & DrawItemState.Selected) == DrawItemState.Selected)
    {
        gr.FillRectangle(_tabBackBrush, tabInfo.Bounds);
        gr.DrawString(this.TabPages[tabInfo.Index].Text, tabInfo.Font, 
            _tabForeBrush, tabInfo.Bounds, _tabTextFormat);
    }
    else
    {
        gr.FillRectangle(_tabBackBrush, tabInfo.Bounds);
        gr.DrawString(this.TabPages[tabInfo.Index].Text, tabInfo.Font, 
            _tabForeBrush, tabInfo.Bounds, _tabTextFormat);
    }
}

jucafojl

jucafojl5#

更简单(IMO):向TabPage(不是顶层TabControl,而是其中的TabPage)添加绘制处理程序,然后以所需的颜色绘制背景矩形。
1.在设计器中或“手动”向TabPage添加Paint事件处理程序:

Page1.Paint += tabpage_Paint; // custom paint event so we get the backcolor we want

字符串
1.在paint方法中,将页面矩形绘制为您想要的颜色(在我的示例中,我希望它遵循标准BackColor):

// force the tab background to the current BackColor
private void tabpage_Paint(object sender, PaintEventArgs e)
{
    SolidBrush fillBrush = new SolidBrush(BackColor);

    e.Graphics.FillRectangle(fillBrush, e.ClipRectangle);
}

rpppsulh

rpppsulh6#

将Panel拖放到选项卡控件的顶部(而不是内部),并在属性中设置颜色。根据需要调用Panelx.Hide()和Panelx.Show()。

kiayqfof

kiayqfof7#

不幸的是,backcolor属性是在绘制控件时处理的。我的建议是做我已经做过的事情,创建一个用户控件来模仿选项卡控制器。
我使用了一个菜单条作为选项卡,并将第二个用户控件作为填充停靠到父用户控件。在第二个用户控件中,我可以添加所需的任何选项卡。
更难的是,你必须构建所有的功能,使它作为一个选项卡控件工作。

相关问题