前面介绍了两篇关于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