ColorListBox
介绍 还有另一个颜色 列表框吗?有很多文章是关于列表框控件着色和代码示例的。本文与其他文章的不同之处在于,所有这些文章及其附带的代码都只是演示。做出自己的判断: 水平滚动条消失了。只有小于控制宽度的固定长度的字符串可以被显示。如果控件的大小被调整了呢? 如果您尝试使用鼠标滚轮,您可能会注意到,当滚动滚轮移动时,所选项目会不规律地上下移动。 可重写的方法OnPaint()和OnPaintBackGround()根本不起作用。他们对这些事件不感兴趣。背景只能通过Windows消息绘制。 . net ListBox控件工作得很好,但是作为进一步派生的基类,它从根本上是有缺陷的。这个问题的根源在于Windows API的列表框。。net列表框只是这个控件的包装。解决方案是什么?我们可以从头开始编写控件,或者尝试使用现有的控件并尝试解决这些问题。基本上,这篇文章是一本关于如何克服这些问题的手册。 的代码 控件是从UserControl派生的。它有一个列表框和一个水平滚动条作为它的成员。为了使它的所有者绘制,我们需要一个绘制项目的方法。在此之前,原始控件的DrawMode必须设置为DrawMode. ownerdrawvariable。这将禁用该项的原始绘制,并且方法MeasureItem()将被激活。DrawItem代码如下。除了几行之外,它或多或少是简单的。隐藏,收缩,复制Code
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)); } }
下面是用于激活和调整滚动条大小的代码:复制Code
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中。让我们使用这个技巧。为此,我们编写了《双刃剑》。它从控件创建一个位图图像,并在需要时刷新它。隐藏,收缩,复制Code
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上绘制项目。我们把它们画在一个基于内存的位图上。但我们的控制系统仍在闪烁。为什么?还有一个步骤——原始控件重新绘制背景。但是如何停止这样做呢?正如我前面提到的,overridable方法OnPaintBackGround()没有连接到事件,重写它们不会执行任何操作。鉴于以上所述,阻止原始背景绘制的唯一方法是在WndProc()方法中阻止WM_ERASEBKGND事件。我们必须重写专门为该类创建的WndProc()。 车轮滚动 鼠标滚轮事件处理也应该得到修复。最明智的方法是阻止WM_MOUSEWHEEL事件并将其转换为垂直滚动条事件。使用Windows API SendMessage()直接发送新创建的事件。隐藏,收缩,复制Code
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(对象项,int IconIndex, Color TxtColor)。其中项可以是任何具有ToString()方法的类。您可以创建自己的类并重写ToString()方法,也可以简单地使用字符串。隐藏,复制Code
public void AddItem(object Item, int IconIndex, Color TxtColor) { ObjectHolder oh = new ObjectHolder(IconIndex, Item, TxtColor); UseDoubleBuffering = false; listBox.Items.Add(oh); ResizeListBoxAndHScrollBar(); }
内部列表框和HScrollBar是公开的,可以访问它们。 最后的联系 新的控件现在可以正常工作了。剩下的最后一件事是控件的外观。在Windows 2000上,它看起来很正常,但在Windows XP上,它看起来像下面的截图: 垂直滚动条有XP风格,但水平滚动条有标准外观。要更改这一点,必须应用清单。基本上,清单文件指定公共控件驻留的DLL。隐藏,收缩,复制Code
<?xmlversion="1.0"encoding="UTF-8"standalone="yes"?> <assemblyxmlns="urn:schemas-microsoft-com:asm.v1"manifestVersion="1.0"> <assemblyIdentityversion="1.0.0.0"processorArchitecture="X86"name=""type="win32"/> <description>Your app description here</description> <dependency> <dependentAssembly> <assemblyIdentitytype="win32"name="Microsoft.Windows.Common-Controls"version="6.0.0.0"processorArchitecture="X86"publicKeyToken="6595b64144ccf1df"language="*"/> </dependentAssembly> </dependency> </assembly>
清单文件必须位于应用程序启动的同一目录中。为了避免在run目录中出现这个不方便的文件,清单可以直接注入到可执行程序集中。为了自动化此任务,已经编写了实用程序dotnetmanifest .exe。您可以使用此实用程序将清单直接注入程序。 控件的完整源代码和最新二进制文件可以在这里的工具箱部分以及dotnetmanifest .exe中找到。 本文转载于:http://www.diyabc.com/frontweb/news366.html