出现滚动条时如何正确应用修改后的 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);
}
}
}
这看起来像这样
我被要求对 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);
}
}
}
这看起来像这样