ColorListBox






3.48/5 (14投票s)
2005 年 4 月 24 日
4分钟阅读

111690

1408
带有图标的彩色ListBox。
引言
另一个彩色列表框?有很多关于为ListBox
控件着色以及代码示例的文章。好吧,本文与其他文章的区别在于,所有这些文章及其提供的代码都只是演示。自己判断吧
- 水平滚动条消失了。只有小于控件宽度的固定长度字符串才能显示。如果调整控件大小会怎样?
- 如果您尝试使用鼠标滚轮,您可能已经注意到,当移动滚轮时,所选项目会不规则地上下移动。
- 可重写方法
OnPaint()
和OnPaintBackGround()
根本不起作用。它们没有连接到事件。背景仅通过Windows消息绘制。
令人沮丧的结论是,作为商业质量控件,它们完全没用。.NET ListBox
控件工作正常,但是作为进一步派生的基类,它存在根本缺陷。这种问题的根源在于Windows API ListBox。.NET ListBox
只是此控件的包装器。解决方案是什么?我们可以从头编写控件,或者尝试使用现有控件并尝试解决这些问题。基本上,本文是关于如何克服这些问题的指南。
代码
该控件派生自UserControl
。它具有ListBox
和水平滚动条作为其成员。为了使其成为自绘控件,我们需要一种绘制项目的方法。在此之前,必须将原始控件的DrawMode
设置为DrawMode.OwnerDrawVariable
。这将禁用项目的原始绘制,并且将激活MeasureItem()
方法。DrawItem
的代码如下所示。除了几行代码外,它或多或少是直截了当的。
protected void DrawListBoxItem(Graphics g,
Rectangle bounds, int Index, bool selected)
{
if (Index == -1)
return;
if (bounds.Top < 0)
return;
if (bounds.Bottom > (listBox.Bottom + listBox.ItemHeight))
return;
Graphics gr = null;
if (UseDoubleBuffering)
{
gr = DoubleBuff.BuffGraph;
}
else
{
gr = g;
}
int IconIndex;
Color TextColor;
string Text = GetObjString(Index, out IconIndex, out TextColor);
Image img = null;
if (selected)
{
if (listBox.Focused)
{
using(Brush b = new SolidBrush(_HighLightColor))
{
gr.FillRectangle(b, 0,
bounds.Top + 1, listBox.Width, bounds.Height - 1);
}
}
else
{
using(Brush b = new SolidBrush(Color.Gainsboro))
{
gr.FillRectangle(b, 0,
bounds.Top, listBox.Width, bounds.Height);
}
}
if (listBox.Focused)
{
using(Pen p = new Pen(Color.RoyalBlue))
{
gr.DrawRectangle(p, new Rectangle(0,
bounds.Top, listBox.Width, bounds.Height));
}
}
}
if (IconIndex != -1 && imageList1 != null)
{
img = imageList1.Images[IconIndex];
Rectangle imgRect = new Rectangle(bounds.Left - DrawingPos,
bounds.Top , img.Width, img.Height);
gr.DrawImage(img, imgRect, 0, 0, img.Width,
img.Height, GraphicsUnit.Pixel);
}
using(Brush b = new SolidBrush(TextColor))
{
gr.DrawString(Text, this.Font, b,
new Point(bounds.Left - DrawingPos +
XOffset_forIcon + 2, bounds.Top + 2));
}
}
以下是激活和调整滚动条大小的代码
private void ResizeListBoxAndHScrollBar()
{
listBox.Width = this.Width;
if (listBox.Width > (MaxStrignLen + XOffset_forIcon + 15))
{
hScrollBar1.Visible = false;
listBox.Height = this.Height;
}
else
{
hScrollBar1.Height = 18;
listBox.Height = this.Height - this.hScrollBar1.Height;
hScrollBar1.Top = this.Height - this.hScrollBar1.Height - 1;
hScrollBar1.Width = this.Width;
hScrollBar1.Visible = true;
hScrollBar1.Minimum = 0;
hScrollBar1.Maximum = MaxStrignLen + XOffset_forIcon + 15;
hScrollBar1.LargeChange = this.listBox.Width;
hScrollBar1.Value = 0;
}
}
就这些吗?现在我们有了项目绘制和滚动条的代码。不幸的是,它更复杂,这就是为什么其他ColorListBox
的实现没有在商业应用程序中使用的原因。我们刚刚创建的控件在调整大小或有时水平滚动条移动时会闪烁。无论您的应用程序有多好,GUI 上的一个闪烁控件都会使产品看起来不专业。它破坏了整体画面。
如何解决这个问题?有一种众所周知的消除闪烁的技术。它被称为双缓冲。其思想是在内存中进行实际绘制,完成后,将图像复制到GUI。让我们使用这种技术。为此,编写了类DoubleBuff
。它根据控件创建位图图像,并在需要时刷新它。
public class DoubleBuffer : IDisposable
{
private Graphics graphics;
private Bitmap bitmap;
private Control _ParentCtl;
private Graphics CtlGraphics;
public DoubleBuffer(Control ParentCtl)
{
_ParentCtl = ParentCtl;
bitmap = new Bitmap(_ParentCtl.Width , _ParentCtl.Height);
graphics = Graphics.FromImage(bitmap);
CtlGraphics = _ParentCtl.CreateGraphics();
}
public void CheckIfRefreshBufferRequired()
{
if ((_ParentCtl.Width != bitmap.Width) ||
(_ParentCtl.Height != bitmap.Height))
{
RefreshBuffer();
}
}
public void RefreshBuffer()
{
if (_ParentCtl == null)
return;
if (_ParentCtl.Width == 0 || _ParentCtl.Height == 0)// restoring event
return;
if (bitmap != null)
{
bitmap.Dispose();
bitmap = null;
}
if (graphics != null)
{
graphics.Dispose();
graphics = null;
}
bitmap = new Bitmap(_ParentCtl.Width, _ParentCtl.Height);
graphics = Graphics.FromImage(bitmap);
if (CtlGraphics != null)
{
CtlGraphics.Dispose();
}
CtlGraphics = _ParentCtl.CreateGraphics();
}
public void Render()
{
CtlGraphics.DrawImage(
bitmap,
_ParentCtl.Bounds,
0,
0,
_ParentCtl.Width,
_ParentCtl.Height,
GraphicsUnit.Pixel);
}
public Graphics BuffGraph
{
get
{
return graphics;
}
}
#region IDisposable Members
public void Dispose()
{
if (bitmap != null)
{
bitmap.Dispose();
}
if (graphics != null)
{
graphics.Dispose();
}
if (CtlGraphics != null)
{
CtlGraphics.Dispose();
}
}
#endregion
}
现在我们不直接在GUI上绘制项目。我们将其绘制在基于内存的位图上。但是我们的控件仍然会闪烁。为什么?还剩下一个步骤——原始控件重新绘制背景。但是如何停止这样做呢?正如我前面提到的,可重写方法OnPaintBackGround()
没有连接到事件,重写它们将没有任何作用。鉴于上述情况,阻止原始背景绘制的唯一方法是阻止WndProc()
方法中的WM_ERASEBKGND
事件。我们必须为此类专门创建的WndProc()
重写。
滚轮滚动
还应修复鼠标滚轮事件处理。最明智的方法是阻止WM_MOUSEWHEEL
事件并将其转换为垂直滚动条事件。使用Windows API SendMessage()
直接发送新创建的事件。
private void GetXY(IntPtr Param, out int X, out int Y)
{
byte[] byts = System.BitConverter.GetBytes((int)Param);
X = BitConverter.ToInt16(byts, 0);
Y = BitConverter.ToInt16(byts, 2);
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case (int)Msg.WM_ERASEBKGND:
if (_BlockEraseBackGnd)
{
return;
}
break;
case (int)Msg.WM_MOUSEWHEEL:
int X;
int Y;
_BlockEraseBackGnd = false;
GetXY(m.WParam, out X, out Y);
if (Y >0)
{
SendMessage(this.Handle, (int)Msg.WM_VSCROLL,
(IntPtr)SB_LINEUP,IntPtr.Zero);
}
else
{
SendMessage(this.Handle, (int)Msg.WM_VSCROLL,
(IntPtr)SB_LINEDOWN,IntPtr.Zero);
}
return;
case (int)Msg.WM_VSCROLL:
case (int)Msg.WM_KEYDOWN:
_BlockEraseBackGnd = false;
if (UpdateEv != null)
{
UpdateEv(null, null);
}
break;
}
base.WndProc (ref m);
}
填充控件
填充控件的主要方法是public void AddItem(object Item, int IconIndex, Color TxtColor)
。其中Item
可以是任何内容,以及任何具有ToString()
方法的类。您可以创建自己的类并重写ToString()
方法,或者您可以简单地使用字符串。
public void AddItem(object Item, int IconIndex, Color TxtColor)
{
ObjectHolder oh = new ObjectHolder(IconIndex, Item, TxtColor);
UseDoubleBuffering = false;
listBox.Items.Add(oh);
ResizeListBoxAndHScrollBar();
}
将internal
ListBox
和HScrollBar
设为public
以便可以访问它们。
最后的润色
新的控件现在工作正常。剩下的最后一件事是控件的外观。在Windows 2000上,它看起来很正常,但在Windows XP上,它看起来像下面的屏幕截图
垂直滚动条具有XP样式,但水平滚动条具有标准外观。要更改此设置,必须应用清单。基本上,清单文件指定包含公共控件的DLL。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="X86"
name=""
type="win32"
/>
<description>Your app description here</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="X86"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>
清单文件必须位于应用程序启动的同一目录中。为了避免在运行目录中出现此不方便的文件,可以将清单直接注入到可执行程序集中。为了自动化此任务,已经编写了实用程序DotNetManifester.exe。您可以使用此实用程序将清单直接注入程序。
可以在此处工具箱部分以及在DotNetManifester.exe中找到控件的完整源代码和最新二进制文件。