You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在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的边框:

  1. 在设计器里给你的ListBox外面加一个Panel控件:
    • 设置Panel的BackColor为你想要的蓝色(比如Color.Blue
    • 设置Panel的Padding(1,1,1,1)(上下左右各留1像素的蓝色边框)
    • 设置ListBox的BorderStyleNone,然后把ListBox的Dock设为Fill,让它填满Panel的内部(刚好露出Panel的蓝色边框)
    • 调整Panel的大小,比原来的ListBox宽2、高2(因为Padding各1像素)

代码大概是这样(可以直接在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事件里,这样每次重绘都会重新画边框:

  1. 在窗体构造函数里订阅Paint事件:
public SelectByAttributesDialog() {
    InitializeComponent();
    this.Paint += Form_Paint; // 订阅窗体的Paint事件
}
  1. 修改你的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

火山引擎 最新活动