• C# C# WinForm控件美化扩展系列之ImageComboBox


    前面介绍了两篇关于ComboBox扩展和美化的文章:C# WinForm控件美化扩展系列之ComboBox组合框控件C# WinForm控件美化扩展系列之给ComboBox加水印,今天将在前两篇的基础上实现一个ImageComboBox控件,ImageComboBox控件拥有以下功能:

    (1)  美化下拉按钮和边框,前面的文章已经实现。

    (2)  当ComboBox没有控件选择项和没有焦点时显示提示用户操作信息,前面的也文章已经实现。

    (3)  在下拉列表框的项中显示图标,项可以缩进。

    (4)  在ComboBox控件中也显示图标。

    来看看最终需要实现的效果:

     

    图1 ImageComboBox DropDownList效果
     


     

    图2 ImageComboBox DropDown效果
     



    这篇文章中我们重点需要实现的是(3)、(4)两项功能,下面我们来介绍具体实现的方法。

    第一步,实现ImageComboBoxItem类。

    要实现显示图标,当然要给每个项添加与图标相关的信息了,ImageComboBoxItem类应该包括以下内容:文本(Text)、缩进的级别(Level)、图标的索引(ImageIndex、ImageKey),用户数据(Tag)。ImageComboBoxItem类实现了ISerializable接口,实现自定义序列化。ImageComboBoxItem类的类视图如下:

     

    图3 ImageComboxItem类视图


     

    ImageComboBoxItem类的代码如下: 

    [Serializable]
    [DefaultProperty("Text")]
    [TypeConverter(
    typeof(ExpandableObjectConverter))]
    public class ImageComboBoxItem :
    IDisposable, ISerializable
    ...{
    Fields#region Fields

    private ImageComboBox _imageComboBox;
    private string _text = "ImageComboBoxItem";
    private ImageComboBoxItemImageIndexer _imageIndexer;
    private object _tag;
    private int _level;

    #endregion

    Constructors#region Constructors

    public ImageComboBoxItem()
    ...{
    }

    public ImageComboBoxItem(string text)
    : this(text, -1, 0)
    ...{
    }

    public ImageComboBoxItem(
    string text, int imageIndex)
    : this(text, imageIndex, 0)
    ...{
    }

    public ImageComboBoxItem(
    string text, string imageKey)
    : this(text, imageKey, 0)
    ...{
    }

    public ImageComboBoxItem(
    string text, int imageIndex, int level)
    : this()
    ...{
    _text = text;
    ImageIndexer.Index = imageIndex;
    _level = level;
    }

    public ImageComboBoxItem(
    string text, string imageKey, int level)
    : this()
    ...{
    _text = text;
    ImageIndexer.Key = imageKey;
    _level = level;
    }

    protected ImageComboBoxItem(
    SerializationInfo info,
    StreamingContext context)
    : this()
    ...{
    Deserialize(info, context);
    }

    #endregion

    Properties#region Properties

    [Localizable(true)]
    public string Text
    ...{
    get
    ...{
    if (_text != null)
    ...{
    return _text;
    }
    return "";
    }
    set
    ...{
    _text = value;
    }
    }

    [Bindable(true)]
    [Localizable(false)]
    [DefaultValue("")]
    [TypeConverter(typeof(StringConverter))]
    [DesignerSerializationVisibility(
    DesignerSerializationVisibility.Hidden)]
    public object Tag
    ...{
    get ...{ return _tag; }
    set ...{ _tag = value; }
    }

    [DefaultValue(0)]
    [Localizable(true)]
    [RefreshProperties(RefreshProperties.Repaint)]
    [DesignerSerializationVisibility(
    DesignerSerializationVisibility.Hidden)]
    public int Level
    ...{
    get ...{ return _level; }
    set
    ...{
    if (_level < 0)
    ...{
    throw new ArgumentOutOfRangeException("level");
    }
    _level = value;
    }
    }

    [DefaultValue(-1)]
    [Localizable(true)]
    [RelatedImageList("ImageComboBox.ImageList")]
    [Editor(
    EditorAssemblyName.ImageIndexEditor,
    typeof(UITypeEditor))]
    [RefreshProperties(RefreshProperties.Repaint)]
    [DesignerSerializationVisibility(
    DesignerSerializationVisibility.Hidden)]
    [TypeConverter(typeof(NoneExcludedImageIndexConverter))]
    public int ImageIndex
    ...{
    get
    ...{
    if (((ImageIndexer.Index != -1) &&
    (ImageList != null)) &&
    (ImageIndexer.Index >= ImageList.Images.Count))
    ...{
    return ImageList.Images.Count - 1;
    }
    return ImageIndexer.Index;
    }
    set
    ...{
    if (value < -1)
    ...{
    throw new ArgumentOutOfRangeException("ImageIndex");
    }
    ImageIndexer.Index = value;
    }
    }

    [DefaultValue("")]
    [Localizable(true)]
    [RelatedImageList("ImageComboBox.ImageList")]
    [RefreshProperties(RefreshProperties.Repaint)]
    [Editor(
    EditorAssemblyName.ImageIndexEditor,
    typeof(UITypeEditor))]
    [DesignerSerializationVisibility(
    DesignerSerializationVisibility.Hidden)]
    [TypeConverter(typeof(ImageKeyConverter))]
    public string ImageKey
    ...{
    get
    ...{
    return ImageIndexer.Key;
    }
    set
    ...{
    ImageIndexer.Key = value;
    }
    }

    [Browsable(false)]
    public ImageComboBox ImageComboBox
    ...{
    get ...{ return _imageComboBox; }
    }

    internal Image Image
    ...{
    get
    ...{
    int actualIndex = ImageIndexer.ActualIndex;
    if (ImageList != null &&
    ImageList.Images.Count > 0 &&
    actualIndex != -1)
    ...{
    return ImageList.Images[actualIndex];
    }
    return null;
    }
    }

    [Browsable(false)]
    internal ImageList ImageList
    ...{
    get
    ...{
    if (ImageComboBox != null)
    ...{
    return ImageComboBox.ImageList;
    }
    return null;
    }
    }

    internal ImageComboBoxItemImageIndexer ImageIndexer
    ...{
    get
    ...{
    if (_imageIndexer == null)
    ...{
    _imageIndexer =
    new ImageComboBoxItemImageIndexer(this);
    }
    return _imageIndexer;
    }
    }

    #endregion

    Methods#region Methods

    public override string ToString()
    ...{
    return _text;
    }

    internal void Host(ImageComboBox parent)
    ...{
    _imageComboBox = parent;
    }

    [SecurityPermission(
    SecurityAction.Demand,
    Flags = SecurityPermissionFlag.SerializationFormatter),
    SecurityPermission(
    SecurityAction.InheritanceDemand,
    Flags = SecurityPermissionFlag.SerializationFormatter)]
    protected virtual void Serialize(
    SerializationInfo info, StreamingContext context)
    ...{
    info.AddValue("Text", Text);
    info.AddValue("Level", Level);
    info.AddValue("ImageIndex", ImageIndexer.Index);
    if (!string.IsNullOrEmpty(ImageIndexer.Key))
    ...{
    info.AddValue("ImageKey", ImageIndexer.Key);
    }
    }

    protected virtual void Deserialize(
    SerializationInfo info,
    StreamingContext context)
    ...{
    string imageKey = null;
    int imageIndex = -1;
    SerializationInfoEnumerator enumerator = info.GetEnumerator();
    while (enumerator.MoveNext())
    ...{
    SerializationEntry current = enumerator.Current;
    if (current.Name == "Text")
    ...{
    Text = info.GetString(current.Name);
    }
    else if (current.Name == "Level")
    ...{
    Level = info.GetInt32(current.Name);
    }
    else
    ...{
    if (current.Name == "ImageIndex")
    ...{
    imageIndex = info.GetInt32(current.Name);
    continue;
    }
    if (current.Name == "ImageKey")
    ...{
    imageKey = info.GetString(current.Name);
    continue;
    }
    }
    }
    if (imageKey != null)
    ...{
    ImageKey = imageKey;
    }
    else if (imageIndex != -1)
    ...{
    ImageIndex = imageIndex;
    }
    }

    #endregion

    ISerializable 成员#region ISerializable 成员

    [SecurityPermission(
    SecurityAction.LinkDemand,
    Flags = SecurityPermissionFlag.SerializationFormatter)]
    void ISerializable.GetObjectData(
    SerializationInfo info, StreamingContext context)
    ...{
    Serialize(info, context);
    }

    #endregion

    IDisposable 成员#region IDisposable 成员

    public void Dispose()
    ...{
    _imageComboBox = null;
    _imageIndexer = null;
    _tag = null;
    }

    #endregion

    ImageComboBoxItemImageIndexer Class
    #region ImageComboBoxItemImageIndexer Class
    internal class ImageComboBoxItemImageIndexer
    : ImageIndexer
    ...{
    private ImageComboBoxItem _owner;

    public ImageComboBoxItemImageIndexer(
    ImageComboBoxItem owner)
    ...{
    _owner = owner;
    }

    public override ImageList ImageList
    ...{
    get
    ...{
    if (_owner != null)
    ...{
    return _owner.ImageList;
    }
    return null;
    }
    set
    ...{
    }
    }
    }

    #endregion
    }

    第二步,实现ImageComboBoxItemCollection类。

    ImageComboBoxItemCollection类实现跟ComboBox.ObjectCollection类一样的功能,用来代替ComboBox控件中ComboBox.ObjectCollection类,定义一个新的Items来存储ImageComboBoxItem对象,来实现ImageComboBox控件设计时可以支持ImageComboBoxItem对象的设计。

    第三步,给ImageComboBox控件添加一些属性。

    ImageComboBox控件主要需要添加几个属性:图标集合(ImageList)、没有选择项时ComboBox中显示的默认图标(DefaultImage)、缩进值(Indent)、提示信息(EmptyTextTip)、提示信息的文本颜色(EmptyTextTipColor)。还需要覆盖一些属性,这里不一一列出了,看下面的ImageComboBox控件的类视图:

     

    图4 ImageComboBox类视图

    第四步,实现EditorNativeWimdow类。

    EditorNativeWimdow类的主要功能是实现当ImageComboBox控件的列表模式设为非DropDownList的时候,即DropDownStyle不是ComboBoxStyle.DropDownList的时候,实现在Editor中绘制图标。EditorNativeWimdow类的代码如下: 

    private class EditorNativeWimdow
    : NativeWindow, IDisposable
    ...{
    Fields#region Fields

    private ImageComboBox _owner;

    private const int EC_LEFTMARGIN = 0x1;
    private const int EC_RIGHTMARGIN = 0x2;
    private const int EC_USEFONTINFO = 0xFFFF;
    private const int EM_SETMARGINS = 0xD3;
    private const int EM_GETMARGINS = 0xD4;

    #endregion

    Constructors#region Constructors

    public EditorNativeWimdow(
    ImageComboBox owner)
    : base()
    ...{
    _owner = owner;
    Attach();
    }

    #endregion

    Private Methods#region Private Methods

    private void Attach()
    ...{
    if (!Handle.Equals(IntPtr.Zero))
    ...{
    ReleaseHandle();
    }
    AssignHandle(_owner.EditHandle);
    SetMargin();
    }

    protected override void WndProc(
    ref Message m)
    ...{
    base.WndProc(ref m);

    switch (m.Msg)
    ...{
    case (int)NativeMethods.WindowsMessage.WM_SETFONT:
    SetMargin();
    break;
    case (int)NativeMethods.WindowsMessage.WM_PAINT:
    RePaint();
    break;
    case (int)NativeMethods.WindowsMessage.WM_SETFOCUS:
    case (int)NativeMethods.WindowsMessage.WM_KILLFOCUS:
    RePaint();
    break;
    case (int)NativeMethods.WindowsMessage.WM_LBUTTONDOWN:
    case (int)NativeMethods.WindowsMessage.WM_RBUTTONDOWN:
    case (int)NativeMethods.WindowsMessage.WM_MBUTTONDOWN:
    RePaint();
    break;
    case (int)NativeMethods.WindowsMessage.WM_LBUTTONUP:
    case (int)NativeMethods.WindowsMessage.WM_RBUTTONUP:
    case (int)NativeMethods.WindowsMessage.WM_MBUTTONUP:
    RePaint();
    break;
    case (int)NativeMethods.WindowsMessage.WM_LBUTTONDBLCLK:
    case (int)NativeMethods.WindowsMessage.WM_RBUTTONDBLCLK:
    case (int)NativeMethods.WindowsMessage.WM_MBUTTONDBLCLK:
    RePaint();
    break;
    case (int)NativeMethods.WindowsMessage.WM_KEYDOWN:
    case (int)NativeMethods.WindowsMessage.WM_CHAR:
    case (int)NativeMethods.WindowsMessage.WM_KEYUP:
    RePaint();
    break;
    case (int)NativeMethods.WindowsMessage.WM_MOUSEMOVE:
    if (!m.WParam.Equals(IntPtr.Zero))
    ...{
    RePaint();
    }
    break;
    }
    }

    internal void SetMargin()
    ...{
    NearMargin(Handle, _owner.ItemHeight + 5);
    }

    private static bool IsRightToLeft(
    IntPtr handle)
    ...{
    int style = NativeMethods.GetWindowLong(
    handle, (int)NativeMethods.GWL.GWL_EXSTYLE);
    return (
    ((style & (int)NativeMethods.WS_EX.WS_EX_RIGHT)
    == (int)NativeMethods.WS_EX.WS_EX_RIGHT) ||
    ((style & (int)NativeMethods.WS_EX.WS_EX_RTLREADING)
    == (int)NativeMethods.WS_EX.WS_EX_RTLREADING) ||
    ((style & (int)NativeMethods.WS_EX.WS_EX_LEFTSCROLLBAR)
    == (int)NativeMethods.WS_EX.WS_EX_LEFTSCROLLBAR));
    }

    private static void FarMargin(
    IntPtr handle, int margin)
    ...{
    int message = IsRightToLeft(handle) ?
    EC_LEFTMARGIN : EC_RIGHTMARGIN;
    if (message == EC_LEFTMARGIN)
    ...{
    margin = margin & 0xFFFF;
    }
    else
    ...{
    margin = margin * 0x10000;
    }
    NativeMethods.SendMessage(
    handle,
    EM_SETMARGINS,
    message,
    margin);
    }

    internal static void NearMargin(
    IntPtr handle, int margin)
    ...{
    int message = IsRightToLeft(handle) ?
    EC_RIGHTMARGIN : EC_LEFTMARGIN;
    if (message == EC_LEFTMARGIN)
    ...{
    margin = margin & 0xFFFF;
    }
    else
    ...{
    margin = margin * 0x10000;
    }
    NativeMethods.SendMessage(
    handle,
    EM_SETMARGINS,
    message,
    margin);
    }

    private void RePaint()
    ...{
    ImageComboBoxItem item = _owner.SelectedItem;

    NativeMethods.RECT rcClient = new NativeMethods.RECT();
    NativeMethods.GetClientRect(Handle, ref rcClient);
    bool rightToLeft = IsRightToLeft(Handle);

    IntPtr handle = Handle;
    IntPtr hdc = NativeMethods.GetDC(handle);
    if (hdc == IntPtr.Zero)
    ...{
    return;
    }
    try
    ...{
    using (Graphics g = Graphics.FromHdc(hdc))
    ...{
    int itemSize = _owner.ItemHeight;
    Rectangle imageRect = new Rectangle(
    0,
    rcClient.Top + (rcClient.Bottom - itemSize) / 2,
    itemSize,
    itemSize);
    Rectangle textRect = new Rectangle(
    0,
    0,
    rcClient.Right - itemSize - 6,
    rcClient.Bottom);

    if (rightToLeft)
    ...{
    imageRect.X = rcClient.Right - itemSize - 2;
    textRect.X = 2;
    }
    else
    ...{
    imageRect.X = 2;
    textRect.X = imageRect.Right + 2;
    }

    if (_owner.Text.Length == 0)
    ...{
    DrawImage(
    g,
    imageRect,
    _owner.DefaultImage,
    _owner.DefaultImageList,
    0,
    _owner.Focused);

    if (_owner.Text.Length == 0 &&
    !string.IsNullOrEmpty(_owner.EmptyTextTip) &&
    !_owner.Focused)
    ...{
    TextFormatFlags format =
    TextFormatFlags.EndEllipsis |
    TextFormatFlags.VerticalCenter;

    if (_owner.RightToLeft == RightToLeft.Yes)
    ...{
    format |=
    (TextFormatFlags.RightToLeft |
    TextFormatFlags.Right);
    }

    TextRenderer.DrawText(
    g,
    _owner.EmptyTextTip,
    _owner.Font,
    textRect,
    _owner.EmptyTextTipColor,
    format);
    }
    return;
    }

    if (_owner.Text.Length > 0)
    ...{
    using (SolidBrush brush =
    new SolidBrush(_owner.BackColor))
    ...{
    g.FillRectangle(brush, imageRect);
    }
    }

    if (_owner.Items.Count == 0)
    ...{
    DrawImage(
    g,
    imageRect,
    _owner.DefaultImage,
    _owner.DefaultImageList,
    0,
    _owner.Focused);
    return;
    }

    if (item == null)
    ...{
    return;
    }

    DrawImage(
    g,
    imageRect,
    item.Image,
    _owner.ImageList,
    item.ImageIndexer.ActualIndex,
    _owner.Focused);
    }
    }
    finally
    ...{
    NativeMethods.ReleaseDC(handle, hdc);
    }
    }

    private void DrawImage(
    Graphics g,
    Rectangle imageRect,
    Image image,
    ImageList imageList,
    int imageIndex,
    bool focus)
    ...{
    using (SolidBrush brush =
    new SolidBrush(_owner.BackColor))
    ...{
    g.FillRectangle(brush, imageRect);
    }

    if (image == null)
    ...{
    return;
    }

    using (InterpolationModeGraphics graphics =
    new InterpolationModeGraphics(
    g, InterpolationMode.HighQualityBicubic))
    ...{
    if (focus)
    ...{
    IntPtr hIcon = NativeMethods.ImageList_GetIcon(
    imageList.Handle,
    imageIndex,
    (int)NativeMethods.ImageListDrawFlags.ILD_SELECTED);
    g.DrawIcon(Icon.FromHandle(hIcon), imageRect);
    NativeMethods.DestroyIcon(hIcon);
    }
    else
    ...{
    g.DrawImage(
    image,
    imageRect,
    0,
    0,
    image.Width,
    image.Height,
    GraphicsUnit.Pixel);
    }
    }
    }

    #endregion

    IDisposable 成员#region IDisposable 成员

    public void Dispose()
    ...{
    _owner = null;
    base.ReleaseHandle();
    }

    #endregion
    }

    第五步,重写OnCreateControl、OnHandleDestroyed方法。

    重写这两个方法主要是为了ImageComboBox控件的DropDownStyle为不同的值时,控制是否需要在Editor中绘制图标,这两个方法的代码如下: 

    protected override void OnCreateControl()
    ...{
    base.OnCreateControl();
    if (DropDownStyle != ComboBoxStyle.DropDownList &&
    !DesignMode)
    ...{
    if (_nativeWimdow == null)
    ...{
    _nativeWimdow = new EditorNativeWimdow(this);
    }
    }
    }

    protected override void OnHandleDestroyed(EventArgs e)
    ...{
    if (_nativeWimdow != null)
    ...{
    _nativeWimdow.Dispose();
    _nativeWimdow = null;
    }
    base.OnHandleDestroyed(e);
    }

    第六步,重写OnDropDown方法。

    重写这个方法是为了实现调节下拉列表框显示的大小,因为画了图标,以免项显示不完全。OnDropDown方法代码如下: 

    protected override void OnDropDown(
    EventArgs e)
    ...{
    base.OnDropDown(e);

    int ddWidth = 0;
    int textWidth = 0;
    int itemWidth = 0;
    int scrollBarWidth =
    Items.Count > MaxDropDownItems ?
    SystemInformation.VerticalScrollBarWidth :
    0;
    Graphics g = CreateGraphics();

    foreach (ImageComboBoxItem item in Items)
    ...{
    textWidth = g.MeasureString(
    item.Text, Font).ToSize().Width;
    itemWidth =
    textWidth +
    ItemHeight + 8 +
    _indent * item.Level +
    scrollBarWidth;

    if (itemWidth > ddWidth)
    ddWidth = itemWidth;
    }

    DropDownWidth = (ddWidth > Width) ?
    ddWidth : Width;
    g.Dispose();
    }
     

    第七步,重绘列表项,让其缩进和显示图标。

    重绘列表项,需要把ImageComboBox控件的DrawMode设为DrawMode.OwnerDrawFixed,然后通过重写OnDrawItem方法实现,具体代码如下: 

    protected override void OnDrawItem(DrawItemEventArgs e)
    ...{
    if (e.Index != -1)
    ...{
    ImageComboBoxItem item = Items[e.Index];
    Graphics g = e.Graphics;
    Rectangle bounds = e.Bounds;

    int indentOffset = Indent * item.Level;

    if ((e.State & DrawItemState.ComboBoxEdit) ==
    DrawItemState.ComboBoxEdit)
    ...{
    indentOffset = 0;
    }

    int imageWidth = bounds.Height;
    Rectangle imageRect;
    Rectangle textRect;
    TextFormatFlags format =
    TextFormatFlags.VerticalCenter |
    TextFormatFlags.SingleLine |
    TextFormatFlags.WordBreak;

    imageRect = new Rectangle(
    bounds.Left + indentOffset + 2,
    bounds.Top,
    imageWidth,
    imageWidth);
    textRect = new Rectangle(
    imageRect.Right + 3,
    bounds.Y,
    bounds.Width - imageRect.Width - indentOffset - 5,
    bounds.Height);

    Rectangle backRect = new Rectangle(
    textRect.X,
    textRect.Y + 1,
    textRect.Width,
    textRect.Height - 2);

    backRect.Width = TextRenderer.MeasureText(
    item.Text, e.Font, textRect.Size, format).Width;

    if (base.RightToLeft == RightToLeft.Yes)
    ...{
    imageRect.X = bounds.Right - imageRect.Right;
    textRect.X = bounds.Right - textRect.Right;
    backRect.X = textRect.Right - backRect.Width;
    }

    bool selected = ((e.State & DrawItemState.Selected) ==
    DrawItemState.Selected);

    Color backColor = selected ?
    SystemColors.Highlight : base.BackColor;

    using (Brush backBrush = new SolidBrush(backColor))
    ...{
    g.FillRectangle(backBrush, backRect);
    }

    if (selected)
    ...{
    ControlPaint.DrawFocusRectangle(
    g,
    backRect);
    }

    Image image = item.Image;
    if (image != null)
    ...{
    using (InterpolationModeGraphics graphics =
    new InterpolationModeGraphics(
    g, InterpolationMode.HighQualityBicubic))
    ...{
    if (selected)
    ...{
    IntPtr hIcon = NativeMethods.ImageList_GetIcon(
    ImageList.Handle,
    item.ImageIndexer.ActualIndex,
    (int)NativeMethods.ImageListDrawFlags.ILD_SELECTED);
    g.DrawIcon(Icon.FromHandle(hIcon), imageRect);
    NativeMethods.DestroyIcon(hIcon);
    }
    else
    ...{
    g.DrawImage(
    image,
    imageRect,
    0,
    0,
    image.Width,
    image.Height,
    GraphicsUnit.Pixel);
    }
    }
    }

    TextRenderer.DrawText(
    g,
    item.Text,
    e.Font,
    textRect,
    base.ForeColor,
    format);
    }
    }

    到此为止,ImageComboBox控件需要实现的功能就完成了。

    转载:http://www.csharpwin.com/csharpresource/7620r2070.shtml



    返回导读目录,阅读更多随笔



    分割线,以下为博客签名:

    软件臭虫情未了
    • 编码一分钟
    • 测试十年功


    随笔如有错误或不恰当之处、为希望不误导他人,望大侠们给予批评指正。

  • 相关阅读:
    C 语言中字符的输入输出
    C 语言 ctype.h 中系列字符处理函数
    C 语言中 for 循环的几种用法
    C 中优先级和关系运算符
    字符串和格式化输入/输出 [printf & scanf]
    C++中关于string类的一些API总结
    两大基本数据类型
    这些时候的总结
    PL/SQL 十进制数转任意进制
    复现题目[CISCN 2019 华东北赛区 Web2 WriteUp](https://www.zhaoj.in/read-6100.html)的一些东西
  • 原文地址:https://www.cnblogs.com/08shiyan/p/1992535.html
Copyright © 2020-2023  润新知