出现滚动条时如何正确应用修改后的 C# WinForm ListBox 边框颜色

How to apply modified C# WinForm ListBox border colour correctly when scroll bar appears

我被要求对 C# WinForms 应用程序执行一些现代化工作,我正在使用 VS2019 和 C#.Net 4.7.2。

项目所有者想要更改最初使用的所有遗留 Windows 控件的边框颜色。最初的想法是——使用 MetroIT 框架——在表单设计器中保留原始的 Windows 控件,但通过定义新的 classes 来覆盖它们,这些新的 classes 扩展了 MetroIT 的等价物但改变了 class Designer 的 InitializeComponent() 方法中声明的类型。

这种方法并不能真正作为“直接”替代,因为 MetroIT 控件倾向于子class 控件,而现有代码期望遗留 Windows properties/methods 到可用。

因此,我转而尝试重写 OnPaint 方法。这对 CheckBox 和 RadioButton 来说效果很好,但我现在正在努力让它为 ListBox 工作。据我所知,以下是;这肯定是不正确的,正如我将解释的那样,但感觉有点接近?

public class MyListBox : ListBox
{
    public MyListBox()
    {
        SetStyle(ControlStyles.UserPaint, true);
        this.DrawMode = DrawMode.OwnerDrawVariable;
        BorderStyle = BorderStyle.None;
    }
    
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        Brush textBrush = new SolidBrush(Color.Black);
        float y = 1;
        foreach (String strItem in Items)
        {
            e.Graphics.DrawString(strItem, DefaultFont, textBrush, 1, y);
            y += DefaultFont.Height;
        }

        Rectangle borderRectangle = this.ClientRectangle;
        ControlPaint.DrawBorder(e.Graphics, borderRectangle, Color.Blue, ButtonBorderStyle.Solid);
    }
}

最初,ListBox 中没有数据,控件绘制正确。

但是,一旦出现滚动条,基本 Windows 代码就会在我的边框顶部绘制滚动条,而不是在边框内部绘制(而未修改的 ListBox 会重新绘制边框滚动条的顶部、底部和右侧):

有没有办法更改我的代码,使边框围绕滚动条的边缘自动绘制而不是排除它?

我的代码错误的另一种方式是,一旦我开始滚动,边框绘制代码就会将自身应用于某些 ListBox 项目:

有什么办法可以完成这个,还是因为无法修改基本滚动条而浪费我的时间?

谢谢

编辑 1:

根据@GuidoG 的建议:

是的,说清楚,我真的只想把边框改成蓝色。很高兴保留列表框项目,因为它们通常是没有任何边框的。

所以,为了实现这一点,我删除了你建议的 DrawItem 代码,现在我只有绘制边框的代码 - 但显然我一定是做错了什么,因为列表框现在已经恢复为看起来就像标准的黑色边框一样。

这就是我现在拥有的:

在我的 Dialog.Designer.cs InitializeComponent():

    this.FieldsListBox = new System.Windows.Forms.ListBox();
    this.FieldsListBox.FormattingEnabled = true;
    this.FieldsListBox.Location = new System.Drawing.Point(7, 98);
    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);

在我的 Dialog.cs 表单声明中:

public SelectByAttributesDialog()
{
    InitializeComponent();
    BlueThemAll(this);
}

private void BlueThemAll(Control parent)
{
    foreach (Control item in parent.Controls)
    {
        Blue(item);

        if (item.HasChildren)
            BlueThemAll(item);
    }
}

private void Blue(Control control)
{
    Debug.WriteLine("Blue: " + control.Name.ToString());
    Rectangle r = new Rectangle(control.Left - 1, control.Top - 1, control.Width + 1, control.Height + 1);

    using (Graphics g = control.Parent.CreateGraphics())
    {
        using (Pen selPen = new Pen(Color.Blue))
        {
            g.DrawRectangle(selPen, r);
        }
    }
}

我知道此对话框中的所有控件都调用了 Blue(),因为 Debug.WriteLine() 正在输出每个控件名称,但没有边框被更改为蓝色(并且当然不是列表框)。

我已经离开 Windows 表单编程很长时间了,为此我深表歉意,我显然做错了什么,但不知道是什么。

解决方案一:让表格画蓝色边框:

这意味着您不必对任何控件进行子类化,但您可以在窗体上放置一些代码,为您在每个控件周围绘制边框。
这是一个对我有用的例子

// method that draws a blue border around a control
private void Blue(Control control)
{
    Rectangle r = new Rectangle(control.Left - 1, control.Top - 1, control.Width + 1, control.Height + 1);

    using (Graphics g = control.Parent.CreateGraphics())
    {
        using (Pen selPen = new Pen(Color.Blue))
        {
            g.DrawRectangle(selPen, r);
        }
    }
}

// recursive method that finds all controls and call the method Blue on each found control
private void BlueThemAll(Control parent)
{
    foreach (Control item in parent.Controls)
    {
        Blue(item);

        if (item.HasChildren)
            BlueThemAll(item);
    }
}

在哪里调用它?

    // draw the blue borders when the form is resized
    protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);

        panel3.Invalidate(); // on panel3 there is a control that moves position because it is anchored to the bottom
        Update();
        BlueThemAll(this);
    }

    // draw the borders when the form is moved, this is needed when the form moved off the screen and back
    protected override void OnMove(EventArgs e)
    {
        base.OnMove(e);

        BlueThemAll(this);
    }

    // draw the borders for the first time
    protected override void OnShown(EventArgs e)
    {
        base.OnShown(e);
        BlueThemAll(this);
    }

如果您还希望列表框的每个项目周围都有蓝色边框:

要在列表框中的每个项目周围添加一些边框,请使用 DrawItem 事件而不是绘制事件,也许 this link 会有所帮助。

我修改了代码让它绘制蓝色边框

listBox1.DrawMode = DrawMode.OwnerDrawFixed;
listBox1.DrawItem += ListBox1_DrawItem;

private void ListBox1_DrawItem(object sender, DrawItemEventArgs e)
{
    try
    {
        e.DrawBackground();

        Brush myBrush = new SolidBrush(e.ForeColor);

        Rectangle r = new Rectangle(e.Bounds.X, e.Bounds.Y, e.Bounds.Width - 1, e.Bounds.Height);
        using (Pen selPen = new Pen(Color.Blue))
        {
            e.Graphics.DrawRectangle(selPen, r);
        }
        e.Graphics.DrawString(((ListBox)sender).Items[e.Index].ToString(), e.Font, myBrush, e.Bounds, StringFormat.GenericDefault);

        e.DrawFocusRectangle();
    }
    catch { }
}

看起来像这样

解决方案 2:子类化列表框:

如果您需要在子类列表框中使用它,那么您可以这样做。
但是,这仅在列表框周围至少有一个像素 space 时有效,因为要绕过滚动条,您必须在列表框的父级上绘制,而不是在列表框本身上绘制。滚动条将始终在您在那里绘制的任何内容上绘制自己。

    public class myListBox : ListBox
    {
        public myListBox(): base()
        {
            this.DrawMode = DrawMode.OwnerDrawFixed;
            BorderStyle = BorderStyle.None;
            this.DrawItem += MyListBox_DrawItem;
        }

        private void MyListBox_DrawItem(object sender, DrawItemEventArgs e)
        {
            e.DrawBackground();
            Brush myBrush = new SolidBrush(e.ForeColor);
            e.Graphics.DrawString(this.Items[e.Index].ToString(), e.Font, myBrush, e.Bounds, StringFormat.GenericDefault);
            e.DrawFocusRectangle();

            Rectangle r = new Rectangle(this.Left - 1, this.Top - 1, this.Width + 2, this.Height + 2);
            using (Graphics g = this.Parent.CreateGraphics())
            {
                ControlPaint.DrawBorder(g, r, Color.Blue, ButtonBorderStyle.Solid);
            }
        }
    }

这看起来像这样