最近需要用到一个支持多选的ComboBox控件,但.Net控件族里没有类似ComboBox且支持多选的控件,所以写了一个自定义控件(参考了博客园“大气象”写的控件 <a href="http://www.cnblogs.com/greatverve/archive/2011/07/19/ComboBox-Multi-Pic.html">http://www.cnblogs.com/greatverve/archive/2011/07/19/ComboBox-Multi-Pic.html</a>及),在原作者控件的基础上完善了一些功能,比如鼠标点击下拉列表外部时隐藏下拉列表,记忆选项及鼠标经过下拉列表时highlight选项等。代码如下:
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Reflection;
namespace CustomComboBox
{
public class MultiSelectableComboBox : System.Windows.Forms.ComboBox
{
#region Private fields
private int clickedCounts = -1;
private ListBox selfLst = new ListBox();
MouseAndKeyHook mouseHooker = new MouseAndKeyHook();
private static readonly char Separator = ',';
private static readonly int DropDownButtonWidth = 21;
private static readonly string Empty = "";
#endregion
public MultiSelectableComboBox()
{
//Set this property to support redraw the control
this.DropDownHeight = 1;
this.DrawMode = DrawMode.OwnerDrawFixed;
selfLst.SelectionMode = SelectionMode.MultiExtended;
selfLst.KeyUp += new KeyEventHandler(lst_KeyUp);
selfLst.MouseUp += new MouseEventHandler(lst_MouseUp);
selfLst.KeyDown += new KeyEventHandler(lst_KeyDown);
selfLst.MouseMove += new MouseEventHandler(lst_MouseMove);
selfLst.VisibleChanged += new EventHandler(selfLst_VisibleChanged);
mouseHooker.OnMouseActivity += new MouseEventHandler(mouseHooker_OnMouseActivity);
}
private void mouseHooker_OnMouseActivity(object sender, MouseEventArgs e)
{
Point mousePoint = selfLst.PointToClient(new Point(e.X, e.Y));
bool withinListBoxWidth = (mousePoint.X >= 0 && mousePoint.X < selfLst.Width);
bool withinListBoxHight = (mousePoint.Y >= 0 && mousePoint.Y < selfLst.Height);
bool withinListBox = withinListBoxWidth && withinListBoxHight;
int dropDownButtonleft = this.Width - DropDownButtonWidth;
mousePoint = this.PointToClient(new Point(e.X, e.Y));
bool withinDropDownButtonWidth = (mousePoint.X > dropDownButtonleft && mousePoint.X < this.Width);
bool withinDropDownButtonHight = (mousePoint.Y >= 0 && mousePoint.Y < this.Height);
bool withinDropDownButton = withinDropDownButtonWidth && withinDropDownButtonHight;
if (e.Clicks > 0 && !withinListBox && !withinDropDownButton)
{
selfLst.Hide();
clickedCounts = 1;
mouseHooker.Stop();
}
}
#region Properties
[Description("Get those selected items"), Category("Data")]
public ListBox.SelectedObjectCollection SelectedItems
{
get
{
return selfLst.SelectedItems;
}
}
#endregion
#region Override methods
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
bool Pressed = (e.Control && ((e.KeyData & Keys.A) == Keys.A));
if (Pressed)
{
for (int i = 0; i < selfLst.Items.Count; i++)
{
selfLst.SetSelected(i, true);
}
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
this.DroppedDown = false;
selfLst.Focus();
}
protected override void OnDropDown(EventArgs e)
{
base.OnDropDown(e);
clickedCounts++;
if (selfLst.Parent == null)
{
selfLst.Parent = this.Parent;
}
if (clickedCounts % 2 == 0)
{
clickedCounts = 0;
selfLst.Items.Clear();
// Set the style and location info for listBox.
selfLst.ItemHeight = this.ItemHeight;
selfLst.BorderStyle = BorderStyle.FixedSingle;
selfLst.Size = new Size(this.DropDownWidth, this.ItemHeight * (this.MaxDropDownItems - 1) - (int)this.ItemHeight / 2);
selfLst.Location = new Point(this.Left, this.Top + this.ItemHeight + 6);
// Bring this lsitBox to front
this.Parent.Controls.SetChildIndex(selfLst, 0);
selfLst.BeginUpdate();
if (this.Items.Count != 0)
{
for (int i = 0; i < this.Items.Count; i++)
{
selfLst.Items.Add(this.Items[i]);
}
}
selfLst.EndUpdate();
selfLst.Show();
if (!this.Parent.Controls.Contains(selfLst))
{
this.Parent.Controls.Add(selfLst);
}
mouseHooker.Start();
}
else
{
selfLst.Hide();
clickedCounts = 1;
mouseHooker.Stop();
}
}
protected override void OnDataSourceChanged(EventArgs e)
{
base.OnDataSourceChanged(e);
selfLst.BeginUpdate();
// If the comboBox's dataSource is not null then clear all items of ListBox, and set
// the listBox's dataSource with the comboBox's dataSource.
if (this.DataSource != null)
{
selfLst.Items.Clear();
selfLst.DataSource = this.DataSource;
selfLst.DisplayMember = this.DisplayMember;
selfLst.ValueMember = this.ValueMember;
}
selfLst.EndUpdate();
selfLst.Refresh();
}
protected override void OnTextUpdate(EventArgs e)
{
base.OnTextUpdate(e);
clickedCounts = -1;
this.OnDropDown(e);
}
protected override void OnSelectedIndexChanged(EventArgs e)
{
base.OnSelectedIndexChanged(e);
selfLst.SelectedIndex = this.SelectedIndex;
}
#endregion
#region Self listBox's events
private void lst_KeyUp(object sender, KeyEventArgs e)
{
this.OnKeyUp(e);
}
private void lst_MouseUp(object sender, MouseEventArgs e)
{
try
{
this.Text = Empty;
for (int i = 0; i < selfLst.SelectedItems.Count; i++)
{
if (i == 0)
this.Text = selfLst.SelectedItems[i].ToString();
else
{
this.Text = this.Text + Separator.ToString() + selfLst.SelectedItems[i].ToString();
}
}
}
catch
{
this.Text = Empty;
}
bool isControlPressed = (Control.ModifierKeys == Keys.Control);
bool isShiftPressed = (Control.ModifierKeys == Keys.Shift);
if (isControlPressed || isShiftPressed)
{
selfLst.Show();
clickedCounts = 0;
}
else
{
selfLst.Hide();
clickedCounts = 1;
}
}
private void lst_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyData)
{
case Keys.Down:
if (selfLst.SelectedItems.Count != 0)
{
this.Text = selfLst.SelectedItem.ToString();
}
else
this.Text = this.Items[0].ToString();
break;
case Keys.Up:
if (selfLst.SelectedItems.Count != 0)
{
this.Text = selfLst.SelectedItem.ToString();
}
else
this.Text = this.Items[0].ToString();
break;
}
}
private void lst_MouseMove(object sender, MouseEventArgs e)
{
int hoverIndex = -1;
bool hasMultiSelectedItem = (selfLst.SelectedItems.Count > 1);
bool isControlPressed = (Control.ModifierKeys == Keys.Control);
bool isShiftPressed = (Control.ModifierKeys == Keys.Shift);
hoverIndex = selfLst.IndexFromPoint(e.X, e.Y);
if (hoverIndex != -1)
{
// If the Ctrl or Shift key dosen't pressed or the listBox dosen't have multi selected items
if (!(isControlPressed || isShiftPressed || hasMultiSelectedItem))
{
selfLst.ClearSelected();
selfLst.SetSelected(hoverIndex, true);
}
}
}
private void selfLst_VisibleChanged(object sender, EventArgs e)
{
if (selfLst.Visible && !string.IsNullOrEmpty(this.Text))
{
string[] selectedItems = { };
selectedItems = this.Text.Split(Separator);
selfLst.ClearSelected();
for (int i = 0; i < selectedItems.Length; i++)
{
if (selfLst.Items.Contains(selectedItems[i]))
{
int targetItemIndex = selfLst.Items.IndexOf(selectedItems[i]);
if (targetItemIndex != -1)
{
selfLst.SetSelected(targetItemIndex, true);
}
}
}
}
}
#endregion
}
#region Mouse and Key Hook
public class MouseAndKeyHook : object
{
public event MouseEventHandler OnMouseActivity;
public event KeyEventHandler KeyDown;
public event KeyPressEventHandler KeyPress;
public event KeyEventHandler KeyUp;
public delegate IntPtr HookProc(int nCode, Int32 wParam, IntPtr lParam);
static IntPtr hMouseHook = IntPtr.Zero;
static IntPtr hKeyboardHook = IntPtr.Zero;
public const int WH_MOUSE = 7;
public const int WH_MOUSE_LL = 14;
public const int WH_KEYBOARD_LL = 13;
HookProc MouseHookProcedure;
HookProc KeyboardHookProcedure;
[StructLayout(LayoutKind.Sequential)]
public class POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
public class MouseHookStruct
{
public POINT pt;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public class KeyboardHookStruct
{
public int vkCode;
public int scanCode;
public int flags;
public IntPtr dwExtraInfo;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(IntPtr idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, Int32 wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
public void Start()
{
if (hMouseHook == IntPtr.Zero)
{
MouseHookProcedure = new HookProc(MouseHookProc);
hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseHookProcedure,
IntPtr.Zero, AppDomain.GetCurrentThreadId());
if (hMouseHook == IntPtr.Zero)
{
Stop();
}
}
//if (hKeyboardHook == IntPtr.Zero)
//{
// KeyboardHookProcedure = new HookProc(KeyboardHookProc);
// hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL,
// KeyboardHookProcedure,
// Marshal.GetHINSTANCE(
// Assembly.GetExecutingAssembly().GetModules()[0]),
// 0);
// if (hKeyboardHook == IntPtr.Zero)
// {
// Stop();
// throw new Exception("SetWindowsHookEx ist failed.");
// }
//}
}
public void Stop()
{
bool retMouse = true;
bool retKeyboard = true;
if (hMouseHook != IntPtr.Zero)
{
retMouse = UnhookWindowsHookEx(hMouseHook);
hMouseHook = IntPtr.Zero;
}
if (hKeyboardHook != IntPtr.Zero)
{
retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
hKeyboardHook = IntPtr.Zero;
}
if (!(retMouse && retKeyboard)) throw new Exception("UnhookWindowsHookEx failed.");
}
private const int WM_MOUSEMOVE = 0x200;
private const int WM_LBUTTONDOWN = 0x201;
private const int WM_RBUTTONDOWN = 0x204;
private const int WM_MBUTTONDOWN = 0x207;
private const int WM_LBUTTONUP = 0x202;
private const int WM_RBUTTONUP = 0x205;
private const int WM_MBUTTONUP = 0x208;
private const int WM_LBUTTONDBLCLK = 0x203;
private const int WM_RBUTTONDBLCLK = 0x206;
private const int WM_MBUTTONDBLCLK = 0x209;
private IntPtr MouseHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
if ((nCode >= 0) && (OnMouseActivity != null))
{
MouseButtons button = MouseButtons.None;
switch (wParam)
{
case WM_LBUTTONDOWN:
button = MouseButtons.Left;
break;
case WM_RBUTTONDOWN:
button = MouseButtons.Right;
break;
case WM_MBUTTONDOWN:
button = MouseButtons.Middle;
break;
}
int clickCount = 0;
if (button != MouseButtons.None)
{
if (wParam == WM_LBUTTONDBLCLK || wParam == WM_RBUTTONDBLCLK)
{
clickCount = 2;
}
else
{
clickCount = 1;
}
}
MouseHookStruct MyMouseHookStruct = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
MouseEventArgs e = new MouseEventArgs(
button,
clickCount,
MyMouseHookStruct.pt.x,
MyMouseHookStruct.pt.y,
0);
OnMouseActivity(this, e);
}
return CallNextHookEx(hMouseHook, nCode, wParam, lParam);
}
[DllImport("user32")]
public static extern int ToAscii(int uVirtKey,
int uScanCode,
byte[] lpbKeyState,
byte[] lpwTransKey,
int fuState);
[DllImport("user32")]
public static extern int GetKeyboardState(byte[] pbKeyState);
private const int WM_KEYDOWN = 0x100;
private const int WM_KEYUP = 0x101;
private const int WM_SYSKEYDOWN = 0x104;
private const int WM_SYSKEYUP = 0x105;
private IntPtr KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
if ((nCode >= 0) && (KeyDown != null || KeyUp != null || KeyPress != null))
{
KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
if (KeyDown != null && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN))
{
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
KeyEventArgs e = new KeyEventArgs(keyData);
KeyDown(this, e);
}
if (KeyPress != null && wParam == WM_KEYDOWN)
{
byte[] keyState = new byte[256];
GetKeyboardState(keyState);
byte[] inBuffer = new byte[2];
if (ToAscii(MyKeyboardHookStruct.vkCode,
MyKeyboardHookStruct.scanCode,
keyState,
inBuffer,
MyKeyboardHookStruct.flags) == 1)
{
KeyPressEventArgs e = new KeyPressEventArgs((char)inBuffer[0]);
KeyPress(this, e);
}
}
if (KeyUp != null && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP))
{
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
KeyEventArgs e = new KeyEventArgs(keyData);
KeyUp(this, e);
}
}
return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
/// <summary>Gets a reference to the Process instance for the running ehshell.exe</summary>
private Process GetEhShellProcess()
{
// Get the current terminal services session ID
int currentSessionId;
using (Process currentProcess = Process.GetCurrentProcess()) currentSessionId = currentProcess.SessionId;
// Get all ehome processes on the machine, and find the one in the current session
Process[] procs = Process.GetProcessesByName("ehshell");
Process ehshell = null;
for (int i = 0; i < procs.Length; i++)
{
if (ehshell == null && procs[i].SessionId == currentSessionId) ehshell = procs[i];
else procs[i].Dispose();
}
return ehshell;
}
}
#endregion
}
注意在该自定义控件中包含两个类,“MultiSelectableComboBox ”,“MouseAndKeyHook”, 后者通过使用Hook来实现鼠标和键盘操作的监控,该控件由ComboBox和ListBox两个控件组成,如果你需要更多的功能,完全可以在此基础上添加,希望对你有帮助