前面介绍了两篇关于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控件需要实现的功能就完成了。
声明:
本文版权归作者和CS 程序员之窗所有,欢迎转载,转载必须保留以下版权信息,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
作者:Starts_2000
出处:CS 程序员之窗 http://www.csharpwin.com。
你可以免费使用或修改提供的源代码,但请保留源代码中的版权信息,详情请查看:
CS程序员之窗开源协议 http://www.csharpwin.com/csol.html。