如何在C# WinForm ListBox出现滚动条时正确修改其边框颜色
我来帮你解决这个WinForms ListBox边框的问题,之前你踩的坑我也遇到过,咱们一步步来捋清楚:
首先,先说说你之前两种方案的问题所在:
为什么父控件绘制边框的方法没用?
你用control.Parent.CreateGraphics()画边框,这属于临时绘制——WinForms的UI是基于Paint事件循环的,只要控件或父控件触发重绘(比如窗口最小化再恢复、ListBox滚动),之前画的边框就会被默认绘制覆盖掉,所以只能看到一瞬间的蓝色,之后就没了。而且ListBox滚动时自身会重绘区域,也会把父控件画的边框覆盖。
为什么自定义ListBox的OnPaint会被滚动条覆盖?
你之前用ClientRectangle画边框,但当ListBox出现滚动条时,ClientRectangle是去掉滚动条后的内部区域,所以边框画在了滚动条的内侧,自然会被系统绘制的滚动条盖住。另外,自己遍历绘制项的方式也不对,WinForms的OwnerDraw有专门的方法来处理项的绘制,手动遍历会导致滚动、选中状态等逻辑混乱。
推荐的三种解决方案,按简单程度排序:
方案1:用Panel嵌套模拟边框(最简单,零代码修改控件)
这个方法完全绕开ListBox原生滚动条的问题,思路是给ListBox套一个Panel,用Panel的“边框”来代替ListBox的边框:
- 在设计器里给你的ListBox外面加一个Panel控件:
- 设置Panel的
BackColor为你想要的蓝色(比如Color.Blue) - 设置Panel的
Padding为(1,1,1,1)(上下左右各留1像素的蓝色边框) - 设置ListBox的
BorderStyle为None,然后把ListBox的Dock设为Fill,让它填满Panel的内部(刚好露出Panel的蓝色边框) - 调整Panel的大小,比原来的ListBox宽2、高2(因为Padding各1像素)
- 设置Panel的
代码大概是这样(可以直接在Designer.cs里改):
this.panelListBoxContainer = new System.Windows.Forms.Panel(); this.FieldsListBox = new System.Windows.Forms.ListBox(); this.panelListBoxContainer.SuspendLayout(); // // panelListBoxContainer // this.panelListBoxContainer.BackColor = System.Drawing.Color.Blue; this.panelListBoxContainer.Location = new System.Drawing.Point(7, 98); this.panelListBoxContainer.Padding = new System.Windows.Forms.Padding(1); this.panelListBoxContainer.Name = "panelListBoxContainer"; this.panelListBoxContainer.Size = new System.Drawing.Size(190, 123); // 原ListBox是188x121,这里宽高各加2 this.panelListBoxContainer.TabIndex = 12; // // FieldsListBox // this.FieldsListBox.BorderStyle = System.Windows.Forms.BorderStyle.None; this.FieldsListBox.Dock = System.Windows.Forms.DockStyle.Fill; this.FieldsListBox.FormattingEnabled = true; this.FieldsListBox.Location = new System.Drawing.Point(1, 1); this.FieldsListBox.Name = "FieldsListBox"; this.FieldsListBox.Size = new System.Drawing.Size(188, 121); this.FieldsListBox.Sorted = true; this.FieldsListBox.TabIndex = 11; this.FieldsListBox.SelectedIndexChanged += new System.EventHandler(this.FieldsListBox_SelectedIndexChanged); // this.panelListBoxContainer.Controls.Add(this.FieldsListBox); this.Controls.Add(this.panelListBoxContainer); this.panelListBoxContainer.ResumeLayout(false);
这样不管ListBox有没有滚动条,蓝色边框都会完整显示,完全不受滚动条影响。
方案2:修复父控件绘制的方法(适合批量给多个控件加边框)
如果想给所有控件都加蓝色边框,那得把绘制逻辑放到父控件的Paint事件里,这样每次重绘都会重新画边框:
- 在窗体构造函数里订阅Paint事件:
public SelectByAttributesDialog() { InitializeComponent(); this.Paint += Form_Paint; // 订阅窗体的Paint事件 }
- 修改你的BlueThemAll和Blue方法,用Paint事件传入的Graphics来绘制:
private void Form_Paint(object sender, PaintEventArgs e) { BlueThemAll(this, e.Graphics); } private void BlueThemAll(Control parent, Graphics g) { foreach (Control item in parent.Controls) { Blue(item, g); if (item.HasChildren) BlueThemAll(item, g); } } private void Blue(Control control, Graphics g) { // 计算控件在父控件中的位置,边框要比控件大一圈 Rectangle borderRect = new Rectangle(control.Left - 1, control.Top - 1, control.Width + 2, control.Height + 2); using (Pen bluePen = new Pen(Color.Blue, 1)) { g.DrawRectangle(bluePen, borderRect); } }
这个方法能让所有控件都显示蓝色边框,但要注意:如果控件自身重绘(比如ListBox滚动),可能会暂时覆盖边框,不过窗体下次Paint时会重新画回来。
方案3:正确的自定义ListBox(最灵活,适合深度定制)
如果想彻底自定义ListBox的边框和外观,那得用正确的OwnerDraw方式:
public class BlueBorderListBox : ListBox { public BlueBorderListBox() { BorderStyle = BorderStyle.None; // 关掉原生边框 // 如果需要自定义项的外观,打开OwnerDraw,否则可以注释掉下面两行 DrawMode = DrawMode.OwnerDrawVariable; this.ItemHeight = this.Font.Height + 2; // 自定义项的高度 } protected override void OnPaint(PaintEventArgs e) { // 先让系统绘制ListBox的内容(包括滚动条、项) base.OnPaint(e); // 绘制整个控件的边框,坐标相对于控件自身(0,0是控件左上角) var borderRect = new Rectangle(0, 0, this.Width - 1, this.Height - 1); using (var bluePen = new Pen(Color.Blue, 1)) { e.Graphics.DrawRectangle(bluePen, borderRect); } } // 如果开启了OwnerDrawVariable,必须重写这个方法来指定项的高度 protected override void OnMeasureItem(MeasureItemEventArgs e) { base.OnMeasureItem(e); e.ItemHeight = this.Font.Height + 2; // 调整项的高度,让文字更舒服 } // 自定义项的绘制,处理选中、焦点等状态 protected override void OnDrawItem(DrawItemEventArgs e) { base.OnDrawItem(e); if (e.Index < 0) return; // 没有项时直接返回 var itemText = Items[e.Index]?.ToString() ?? string.Empty; // 根据选中状态设置背景和文字颜色 var backColor = (e.State & DrawItemState.Selected) == DrawItemState.Selected ? SystemColors.Highlight : this.BackColor; var foreColor = (e.State & DrawItemState.Selected) == DrawItemState.Selected ? SystemColors.HighlightText : this.ForeColor; // 绘制项的背景和文字 using (var backBrush = new SolidBrush(backColor)) using (var foreBrush = new SolidBrush(foreColor)) { e.Graphics.FillRectangle(backBrush, e.Bounds); e.Graphics.DrawString(itemText, this.Font, foreBrush, e.Bounds.X + 2, e.Bounds.Y + 1); } // 绘制焦点矩形(可选) e.DrawFocusRectangle(); } }
然后在设计器里把原来的ListBox替换成这个自定义控件,或者在InitializeComponent里修改声明:
this.FieldsListBox = new BlueBorderListBox();
这个方法的好处是边框和ListBox完全绑定,不管滚动条怎么动,边框都会完整显示,还能自定义项的外观。
内容的提问来源于stack exchange,提问作者Gaz




