前面介绍了两篇关于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
Constructors
Properties
Methods
ISerializable 成员
IDisposable 成员
ImageComboBoxItemImageIndexer Class
}
第二步,实现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
Constructors
Private Methods
IDisposable 成员
}
第五步,重写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控件需要实现的功能就完成了。