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

原文地址:https://www.cnblogs.com/Dincat/p/13443851.html