有没有办法改变winforms中标签控件的背景颜色,使其周围没有白色边框?我已经尝试了几种不同的方法,但它们都导致显示相同的白色边框。
rryofs0p1#
TabControl对定制的支持非常差。我使用this custom tab control取得了很好的成功。如果你想像我一样改变外观,代码是非常有用的。
TabControl
c0vxltue2#
我只能想到将Appearance属性更改为ButtonsMSDN TabControl Appearance
yyyllmsg3#
首先,你需要从TabControl创建一个派生类。到目前为止一切都很好,但现在它变得肮脏。因为TabControl不会调用OnPaint,所以我们必须重写WndProc来处理WM_PAINT消息。在那里,我们继续用我们喜欢的颜色涂背景。
OnPaint
WndProc
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]); } } } }
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;
myBuffer.Graphics.DrawRectangle(new Pen(Color.FromArgb(255, 133, 158, 191), 1), r);
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,如下所示:
foreach
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; }
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);
ItemArgs[e.Index] = e;
型我将DrawItemEventArgs保存到一个字典中(在我的例子中称为“ItemArgs”),以便以后可以访问它们。这就是几秒钟前的foreach发挥作用的地方。它调用了一个方法,我正在绘制选项卡标题,该方法接受我们之前保存的DrawItemEventArgs作为参数,以正确的状态和位置绘制项目。因此,简单地说,我们正在拦截标签标题的绘制,以延迟它,直到我们完成绘制背景。这个解决方案不是最佳的,但它的工作和它的唯一的事情,你可以做更多的控制TabControl(lol)没有画它从头开始。
DrawItemEventArgs
w1e3prcc4#
添加到@janhildebrandt的答案,因为它缺少一些关键部分,实际上使它的工作。
TabControl的DrawMode属性必须设置为TabDrawMode.OwnerDrawFixed,否则DrawItem事件处理程序将不会触发。只需像这样重写派生的TabControl类中的属性:
DrawMode
TabDrawMode.OwnerDrawFixed
public new TabDrawMode DrawMode{ get { return TabDrawMode.OwnerDrawFixed; } set { // No you dont. }}public MyTabControl() { base.DrawMode = TabDrawMode.OwnerDrawFixed;}
public new TabDrawMode DrawMode
get
return TabDrawMode.OwnerDrawFixed;
set
// No you dont.
public MyTabControl()
base.DrawMode = TabDrawMode.OwnerDrawFixed;
字符串
我不知道代码是在哪个版本中编写的,但存储TabItem的Graphics对象在2023年在.Net 4.5及以上版本下将无法工作,也不需要。相反,考虑使用如下结构:
TabItem
Graphics
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>();
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>();
型
当已经从Control本身派生时,不要分配事件处理程序。使用OnDrawItem(DrawItemEventArgs)方法:
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); }}
protected override void OnDrawItem(DrawItemEventArgs e)
base.OnDrawItem(e);
if (!_tabItemStateMap.ContainsKey(e.Index))
_tabItemStateMap.Add(e.Index, new TabItemInfo(e));
_tabItemStateMap[e.Index] = new TabItemInfo(e);
您的TabControl将在设计模式下 Flink 。通过检查WM_ERASEBKGND消息,可以轻松避免这种情况。只需在DesignMode期间忽略它即可:
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();}
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;
switch (m.Msg)
case WM_PAINT:
// Let system do its thing first.
// Custom paint Tab items.
HandlePaint(ref m);
break;
case WM_ERASEBKGND:
if (DesignMode)
// Ignore to prevent flickering in DesignMode.
default:
private Color _backColor = Color.FromArgb(31, 31, 31);
[Browsable(true)]
[EditorBrowsable(EditorBrowsableState.Always)]
public new Color BackColor
return _backColor;
_backColor = value;
private void HandlePaint(ref Message m)
using (var g = Graphics.FromHwnd(m.HWnd))
SolidBrush backBrush = new SolidBrush(BackColor);
using (var buffer = _bufferContext.Allocate(g, r))
if (Enabled)
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;
// 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.
与其重新创建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);}
private SolidBrush _backBrush;
private SolidBrush _tabBackBrush;
private SolidBrush _tabForeBrush;
private Color _tabBackColor = Color.FromArgb(31, 31, 31);
public Color TabBackColor
return _tabBackColor;
_tabBackColor = value;
_tabBackBrush?.Dispose();
_tabBackBrush = new SolidBrush(_tabBackColor);
private Color _tabForeColor = Color.FromArgb(241, 241, 241);
public Color TabForeColor
return _tabForeColor;
_tabForeColor = value;
_tabForeBrush?.Dispose();
_tabForeBrush = new SolidBrush(_tabForeColor);
_backBrush?.Dispose();
_backBrush = new SolidBrush(_backColor);
protected override void Dispose(bool disposing)
_backBrush.Dispose();
_tabBackBrush.Dispose();
_tabForeBrush.Dispose();
base.Dispose(disposing);
使用ControlStyles.OptimizedDoubleBuffer可能会进一步减少 Flink (如果有的话)。
ControlStyles.OptimizedDoubleBuffer
public MyTabControl() { this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);}
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
在绘制TabItem的文本时传递一个StringFormat对象,以像使用Label一样定位文本
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); }}
private StringFormat _tabTextFormat = new StringFormat();
private void UpdateTextAlign()
switch (this.TextAlign)
case ContentAlignment.TopLeft:
_tabTextFormat.Alignment = StringAlignment.Near;
_tabTextFormat.LineAlignment = StringAlignment.Near;
case ContentAlignment.TopCenter:
_tabTextFormat.Alignment = StringAlignment.Center;
case ContentAlignment.TopRight:
_tabTextFormat.Alignment = StringAlignment.Far;
case ContentAlignment.MiddleLeft:
_tabTextFormat.LineAlignment = StringAlignment.Center;
case ContentAlignment.MiddleCenter:
case ContentAlignment.MiddleRight:
case ContentAlignment.BottomLeft:
_tabTextFormat.LineAlignment = StringAlignment.Far;
case ContentAlignment.BottomCenter:
case ContentAlignment.BottomRight:
private ContentAlignment _textAlign = ContentAlignment.TopLeft;
public ContentAlignment TextAlign
return _textAlign;
if (value != _textAlign)
_textAlign = value;
UpdateTextAlign();
gr.FillRectangle(_tabBackBrush, tabInfo.Bounds);
_tabForeBrush, tabInfo.Bounds, _tabTextFormat);
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 BackColorprivate void tabpage_Paint(object sender, PaintEventArgs e){ SolidBrush fillBrush = new SolidBrush(BackColor); e.Graphics.FillRectangle(fillBrush, e.ClipRectangle);}
// 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);
rpppsulh6#
将Panel拖放到选项卡控件的顶部(而不是内部),并在属性中设置颜色。根据需要调用Panelx.Hide()和Panelx.Show()。
kiayqfof7#
不幸的是,backcolor属性是在绘制控件时处理的。我的建议是做我已经做过的事情,创建一个用户控件来模仿选项卡控制器。我使用了一个菜单条作为选项卡,并将第二个用户控件作为填充停靠到父用户控件。在第二个用户控件中,我可以添加所需的任何选项卡。更难的是,你必须构建所有的功能,使它作为一个选项卡控件工作。
7条答案
按热度按时间rryofs0p1#
TabControl
对定制的支持非常差。我使用this custom tab control取得了很好的成功。如果你想像我一样改变外观,代码是非常有用的。c0vxltue2#
我只能想到将Appearance属性更改为Buttons
MSDN TabControl Appearance
yyyllmsg3#
首先,你需要从TabControl创建一个派生类。到目前为止一切都很好,但现在它变得肮脏。
因为TabControl不会调用
OnPaint
,所以我们必须重写WndProc
来处理WM_PAINT消息。在那里,我们继续用我们喜欢的颜色涂背景。字符串
我在这个方法中做了进一步的绘制,所以它看起来有点矫枉过正,但忽略不必要的东西。还要注意
foreach
循环。我以后再谈这个。问题是
TabControl
在它自己的WM_PAINT之前**绘制了它的项目(标签页标题),所以我们的背景将被绘制在顶部,这使得它们不可见。为了解决这个问题,我为DrawItem
做了一个EventHandler
,如下所示:型
我将
DrawItemEventArgs
保存到一个字典中(在我的例子中称为“ItemArgs”),以便以后可以访问它们。这就是几秒钟前的foreach
发挥作用的地方。它调用了一个方法,我正在绘制选项卡标题,该方法接受我们之前保存的DrawItemEventArgs
作为参数,以正确的状态和位置绘制项目。因此,简单地说,我们正在拦截标签标题的绘制,以延迟它,直到我们完成绘制背景。
这个解决方案不是最佳的,但它的工作和它的唯一的事情,你可以做更多的控制
TabControl
(lol)没有画它从头开始。w1e3prcc4#
添加到@janhildebrandt的答案,因为它缺少一些关键部分,实际上使它的工作。
属性
TabControl
的DrawMode
属性必须设置为TabDrawMode.OwnerDrawFixed
,否则DrawItem
事件处理程序将不会触发。只需像这样重写派生的TabControl类中的属性:
字符串
DrawItemEventArgs
我不知道代码是在哪个版本中编写的,但存储
TabItem
的Graphics
对象在2023年在.Net 4.5及以上版本下将无法工作,也不需要。相反,考虑使用如下结构:
型
DrawItem事件处理函数
当已经从Control本身派生时,不要分配事件处理程序。使用
OnDrawItem(DrawItemEventArgs)
方法:型
WndProc改进
您的
TabControl
将在设计模式下 Flink 。通过检查
WM_ERASEBKGND
消息,可以轻松避免这种情况。只需在DesignMode
期间忽略它即可:型
进一步改进
SolidBrush
与其重新创建
SolidBrush
对象,不如考虑将它们声明为类的成员。范例:
型
设置样式
使用
ControlStyles.OptimizedDoubleBuffer
可能会进一步减少 Flink (如果有的话)。型
特色
TabItem中的TextAlignment
在绘制TabItem的文本时传递一个
StringFormat
对象,以像使用Label
一样定位文本型
jucafojl5#
更简单(IMO):向TabPage(不是顶层TabControl,而是其中的TabPage)添加绘制处理程序,然后以所需的颜色绘制背景矩形。
1.在设计器中或“手动”向TabPage添加Paint事件处理程序:
字符串
1.在paint方法中,将页面矩形绘制为您想要的颜色(在我的示例中,我希望它遵循标准BackColor):
型
rpppsulh6#
将Panel拖放到选项卡控件的顶部(而不是内部),并在属性中设置颜色。根据需要调用Panelx.Hide()和Panelx.Show()。
kiayqfof7#
不幸的是,backcolor属性是在绘制控件时处理的。我的建议是做我已经做过的事情,创建一个用户控件来模仿选项卡控制器。
我使用了一个菜单条作为选项卡,并将第二个用户控件作为填充停靠到父用户控件。在第二个用户控件中,我可以添加所需的任何选项卡。
更难的是,你必须构建所有的功能,使它作为一个选项卡控件工作。