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

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

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

rryofs0p

rryofs0p1#

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

c0vxltue

c0vxltue2#

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

yyyllmsg

yyyllmsg3#

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

  1. protected override void WndProc(ref Message m)
  2. {
  3. base.WndProc(ref m);
  4. if(m.Msg == (int) WindowsMessages.Win32Messages.WM_PAINT)
  5. {
  6. using (Graphics g = this.CreateGraphics())
  7. {
  8. //Double buffering stuff...
  9. BufferedGraphicsContext currentContext;
  10. BufferedGraphics myBuffer;
  11. currentContext = BufferedGraphicsManager.Current;
  12. myBuffer = currentContext.Allocate(g,
  13. this.ClientRectangle);
  14. Rectangle r = ClientRectangle;
  15. //Painting background
  16. if(Enabled)
  17. myBuffer.Graphics.FillRectangle(new SolidBrush(_backColor), r);
  18. else
  19. myBuffer.Graphics.FillRectangle(Brushes.LightGray, r);
  20. //Painting border
  21. r.Height = this.DisplayRectangle.Height +1; //Using display rectangle hight because it excludes the tab headers already
  22. r.Y = this.DisplayRectangle.Y - 1; //Same for Y coordinate
  23. r.Width -= 5;
  24. r.X += 1;
  25. if(Enabled)
  26. myBuffer.Graphics.DrawRectangle(new Pen(Color.FromArgb(255, 133, 158, 191), 1), r);
  27. else
  28. myBuffer.Graphics.DrawRectangle(Pens.DarkGray, r);
  29. myBuffer.Render();
  30. myBuffer.Dispose();
  31. //Actual painting of items after Background was painted
  32. foreach (int index in ItemArgs.Keys)
  33. {
  34. CustomDrawItem(ItemArgs[index]);
  35. }
  36. }
  37. }
  38. }

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

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


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

展开查看全部
w1e3prcc

w1e3prcc4#

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

属性

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

  1. public new TabDrawMode DrawMode
  2. {
  3. get
  4. {
  5. return TabDrawMode.OwnerDrawFixed;
  6. }
  7. set
  8. {
  9. // No you dont.
  10. }
  11. }
  12. public MyTabControl()
  13. {
  14. base.DrawMode = TabDrawMode.OwnerDrawFixed;
  15. }

字符串

DrawItemEventArgs

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

  1. private struct TabItemInfo
  2. {
  3. public Color BackColor;
  4. public Rectangle Bounds;
  5. public Font Font;
  6. public Color ForeColor;
  7. public int Index;
  8. public DrawItemState State;
  9. public TabItemInfo(DrawItemEventArgs e)
  10. {
  11. this.BackColor = e.BackColor;
  12. this.ForeColor = e.ForeColor;
  13. this.Bounds = e.Bounds;
  14. this.Font = e.Font;
  15. this.Index = e.Index;
  16. this.State = e.State;
  17. }
  18. }
  19. private Dictionary<int, TabItemInfo> _tabItemStateMap = new Dictionary<int, TabItemInfo>();

DrawItem事件处理函数

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

  1. protected override void OnDrawItem(DrawItemEventArgs e)
  2. {
  3. base.OnDrawItem(e);
  4. if (!_tabItemStateMap.ContainsKey(e.Index))
  5. {
  6. _tabItemStateMap.Add(e.Index, new TabItemInfo(e));
  7. }
  8. else
  9. {
  10. _tabItemStateMap[e.Index] = new TabItemInfo(e);
  11. }
  12. }

WndProc改进

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

  1. private const int WM_PAINT = 0x000F;
  2. private const int WM_ERASEBKGND = 0x0014;
  3. // Cache context to avoid repeatedly re-creating the object.
  4. // WM_PAINT is called frequently so it's better to declare it as a member.
  5. private BufferedGraphicsContext _bufferContext = BufferedGraphicsManager.Current;
  6. protected override void WndProc(ref Message m)
  7. {
  8. switch (m.Msg)
  9. {
  10. case WM_PAINT:
  11. {
  12. // Let system do its thing first.
  13. base.WndProc(ref m);
  14. // Custom paint Tab items.
  15. HandlePaint(ref m);
  16. break;
  17. }
  18. case WM_ERASEBKGND:
  19. {
  20. if (DesignMode)
  21. {
  22. // Ignore to prevent flickering in DesignMode.
  23. }
  24. else
  25. {
  26. base.WndProc(ref m);
  27. }
  28. break;
  29. }
  30. default:
  31. base.WndProc(ref m);
  32. break;
  33. }
  34. }
  35. private Color _backColor = Color.FromArgb(31, 31, 31);
  36. [Browsable(true)]
  37. [EditorBrowsable(EditorBrowsableState.Always)]
  38. public new Color BackColor
  39. {
  40. get
  41. {
  42. return _backColor;
  43. }
  44. set
  45. {
  46. _backColor = value;
  47. }
  48. }
  49. private void HandlePaint(ref Message m)
  50. {
  51. using (var g = Graphics.FromHwnd(m.HWnd))
  52. {
  53. SolidBrush backBrush = new SolidBrush(BackColor);
  54. Rectangle r = ClientRectangle;
  55. using (var buffer = _bufferContext.Allocate(g, r))
  56. {
  57. if (Enabled)
  58. {
  59. buffer.Graphics.FillRectangle(backBrush, r);
  60. }
  61. else
  62. {
  63. buffer.Graphics.FillRectangle(backBrush, r);
  64. }
  65. // Paint items
  66. foreach (int index in _tabItemStateMap.Keys)
  67. {
  68. DrawTabItemInternal(buffer.Graphics, _tabItemStateMap[index]);
  69. }
  70. buffer.Render();
  71. }
  72. backBrush.Dispose();
  73. }
  74. }
  75. private void DrawTabItemInternal(Graphics gr, TabItemInfo tabInfo)
  76. {
  77. /* Uncomment the two lines below to have each TabItem use the same height.
  78. ** The selected TabItem height will be slightly taller
  79. ** which makes unselected tabs float if you choose to
  80. ** have a different BackColor for the TabControl background
  81. ** and your TabItem background.
  82. */
  83. // int fullHeight = _tabItemStateMap[this.SelectedIndex].Bounds.Height;
  84. // tabInfo.Bounds.Height = fullHeight;
  85. SolidBrush backBrush = new SolidBrush(BackColor);
  86. // Paint selected.
  87. // You might want to choose a different color for the
  88. // background or the text.
  89. if ((tabInfo.State & DrawItemState.Selected) == DrawItemState.Selected)
  90. {
  91. gr.FillRectangle(backBrush, tabInfo.Bounds);
  92. gr.DrawString(this.TabPages[tabInfo.Index].Text, tabInfo.Font,
  93. SystemBrushes.ControlText, tabInfo.Bounds);
  94. }
  95. // Paint unselected.
  96. else
  97. {
  98. gr.FillRectangle(backBrush, tabInfo.Bounds);
  99. gr.DrawString(this.TabPages[tabInfo.Index].Text, tabInfo.Font,
  100. SystemBrushes.ControlText, tabInfo.Bounds);
  101. }
  102. backBrush.Dispose();
  103. }

进一步改进

SolidBrush

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

  1. private SolidBrush _backBrush;
  2. private SolidBrush _tabBackBrush;
  3. private SolidBrush _tabForeBrush;
  4. private Color _tabBackColor = Color.FromArgb(31, 31, 31);
  5. public Color TabBackColor
  6. {
  7. get
  8. {
  9. return _tabBackColor;
  10. }
  11. set
  12. {
  13. _tabBackColor = value;
  14. _tabBackBrush?.Dispose();
  15. _tabBackBrush = new SolidBrush(_tabBackColor);
  16. }
  17. }
  18. private Color _tabForeColor = Color.FromArgb(241, 241, 241);
  19. public Color TabForeColor
  20. {
  21. get
  22. {
  23. return _tabForeColor;
  24. }
  25. set
  26. {
  27. _tabForeColor = value;
  28. _tabForeBrush?.Dispose();
  29. _tabForeBrush = new SolidBrush(_tabForeColor);
  30. }
  31. }
  32. private Color _backColor = Color.FromArgb(31, 31, 31);
  33. [Browsable(true)]
  34. [EditorBrowsable(EditorBrowsableState.Always)]
  35. public new Color BackColor
  36. {
  37. get
  38. {
  39. return _backColor;
  40. }
  41. set
  42. {
  43. _backColor = value;
  44. _backBrush?.Dispose();
  45. _backBrush = new SolidBrush(_backColor);
  46. }
  47. }
  48. protected override void Dispose(bool disposing)
  49. {
  50. _backBrush.Dispose();
  51. _tabBackBrush.Dispose();
  52. _tabForeBrush.Dispose();
  53. base.Dispose(disposing);
  54. }

设置样式

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

  1. public MyTabControl()
  2. {
  3. this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
  4. }

特色

TabItem中的TextAlignment

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

  1. private StringFormat _tabTextFormat = new StringFormat();
  2. private void UpdateTextAlign()
  3. {
  4. switch (this.TextAlign)
  5. {
  6. case ContentAlignment.TopLeft:
  7. _tabTextFormat.Alignment = StringAlignment.Near;
  8. _tabTextFormat.LineAlignment = StringAlignment.Near;
  9. break;
  10. case ContentAlignment.TopCenter:
  11. _tabTextFormat.Alignment = StringAlignment.Center;
  12. _tabTextFormat.LineAlignment = StringAlignment.Near;
  13. break;
  14. case ContentAlignment.TopRight:
  15. _tabTextFormat.Alignment = StringAlignment.Far;
  16. _tabTextFormat.LineAlignment = StringAlignment.Near;
  17. break;
  18. case ContentAlignment.MiddleLeft:
  19. _tabTextFormat.Alignment = StringAlignment.Near;
  20. _tabTextFormat.LineAlignment = StringAlignment.Center;
  21. break;
  22. case ContentAlignment.MiddleCenter:
  23. _tabTextFormat.Alignment = StringAlignment.Center;
  24. _tabTextFormat.LineAlignment = StringAlignment.Center;
  25. break;
  26. case ContentAlignment.MiddleRight:
  27. _tabTextFormat.Alignment = StringAlignment.Far;
  28. _tabTextFormat.LineAlignment = StringAlignment.Center;
  29. break;
  30. case ContentAlignment.BottomLeft:
  31. _tabTextFormat.Alignment = StringAlignment.Near;
  32. _tabTextFormat.LineAlignment = StringAlignment.Far;
  33. break;
  34. case ContentAlignment.BottomCenter:
  35. _tabTextFormat.Alignment = StringAlignment.Center;
  36. _tabTextFormat.LineAlignment = StringAlignment.Far;
  37. break;
  38. case ContentAlignment.BottomRight:
  39. _tabTextFormat.Alignment = StringAlignment.Far;
  40. _tabTextFormat.LineAlignment = StringAlignment.Far;
  41. break;
  42. }
  43. }
  44. private ContentAlignment _textAlign = ContentAlignment.TopLeft;
  45. public ContentAlignment TextAlign
  46. {
  47. get
  48. {
  49. return _textAlign;
  50. }
  51. set
  52. {
  53. if (value != _textAlign)
  54. {
  55. _textAlign = value;
  56. UpdateTextAlign();
  57. }
  58. }
  59. }
  60. private void DrawTabItemInternal(Graphics gr, TabItemInfo tabInfo)
  61. {
  62. if ((tabInfo.State & DrawItemState.Selected) == DrawItemState.Selected)
  63. {
  64. gr.FillRectangle(_tabBackBrush, tabInfo.Bounds);
  65. gr.DrawString(this.TabPages[tabInfo.Index].Text, tabInfo.Font,
  66. _tabForeBrush, tabInfo.Bounds, _tabTextFormat);
  67. }
  68. else
  69. {
  70. gr.FillRectangle(_tabBackBrush, tabInfo.Bounds);
  71. gr.DrawString(this.TabPages[tabInfo.Index].Text, tabInfo.Font,
  72. _tabForeBrush, tabInfo.Bounds, _tabTextFormat);
  73. }
  74. }

展开查看全部
jucafojl

jucafojl5#

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

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

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

  1. // force the tab background to the current BackColor
  2. private void tabpage_Paint(object sender, PaintEventArgs e)
  3. {
  4. SolidBrush fillBrush = new SolidBrush(BackColor);
  5. e.Graphics.FillRectangle(fillBrush, e.ClipRectangle);
  6. }

展开查看全部
rpppsulh

rpppsulh6#

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

kiayqfof

kiayqfof7#

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

相关问题