C# winfrom 自定义一个多选下拉控件MultiCombobox
先看效果图:下拉框可自由拖动大小,内部checkbox会自动换行。
主要代码片段
自定义控件MultiComboboxCtrl
1 public partial class MultiComboBoxCtrl : UserControl 2 { 3 MyCheckboxListCtrl checkBoxListCtrl; 4 public event Action<string> SelectdItemChanged; 5 /// <summary> 6 /// 复选ComboBox 7 /// </summary> 8 /// <param name="dataSource">可选择数据源,list,dictionary;dictionary<存储值-显示值></param> 9 /// <param name="cBSize">选择窗口控件大小</param> 10 /// <param name="sizeMode">选择窗口控件大小模式</param> 11 public MultiComboBoxCtrl(object dataSource, Size cBSize, SizeMode sizeMode = SizeMode.UseComboSize) 12 { 13 InitializeComponent(); 14 checkBoxListCtrl = new MyCheckboxListCtrl(); 15 checkBoxListCtrl.BorderStyle = BorderStyle.None; 16 Dictionary<string, string> datasource = dataSource as Dictionary<string, string>; 17 if (datasource == null) 18 { 19 datasource = new Dictionary<string, string>(); 20 if (dataSource is List<string>) 21 { 22 List<string> list = dataSource as List<string>; 23 24 foreach (string str in list) 25 { 26 datasource.Add(str, str); 27 } 28 } 29 else if (dataSource is string[]) 30 { 31 string[] list = dataSource as string[]; 32 foreach (string str in list) 33 { 34 datasource.Add(str, str); 35 } 36 } 37 } 38 39 checkBoxListCtrl.DataSource = datasource; 40 checkBoxListCtrl.Width = cBSize.Width; 41 checkBoxListCtrl.Height = cBSize.Height; 42 this.customComboBox1.DropDownControl = checkBoxListCtrl; 43 this.customComboBox1.DropDownSizeMode = sizeMode; 44 this.customComboBox1.DropDownStyle = ComboBoxStyle.DropDownList; 45 this.customComboBox1.AllowResizeDropDown = true; 46 checkBoxListCtrl.ItemSelectdChanged += MyCheckBoxListCtrl_ItemSelectdChanged; 47 } 48 49 /// <summary> 50 /// 显示文本 51 /// </summary> 52 public override string Text 53 { 54 get 55 { 56 return this.customComboBox1.Text; 57 } 58 set 59 { 60 SetComboxText(value); 61 UpdateCheckBoxListCheckedItem(value); 62 } 63 } 64 65 /// <summary> 66 /// 是否只读方式 67 /// </summary> 68 public ComboBoxStyle DropDownStyle 69 { 70 get 71 { 72 return customComboBox1.DropDownStyle; 73 } 74 set 75 { 76 customComboBox1.DropDownStyle = value; 77 } 78 } 79 /// <summary> 80 /// 是否只读方式 81 /// </summary> 82 public bool ReadOnly 83 { 84 get; set; 85 } 86 87 private void MultiComboBox_Load(object sender, EventArgs e) 88 { 89 if (ReadOnly) 90 { 91 customComboBox1.Enabled = false; 92 } 93 } 94 95 /// <summary> 96 /// 更新CheckBoxList当前选中项 97 /// </summary> 98 /// <param name="allitemStr"></param> 99 private void UpdateCheckBoxListCheckedItem(string allitemStr) 100 { 101 if (checkBoxListCtrl != null) 102 { 103 List<string> items = new List<string>(); 104 if (allitemStr != null) 105 items = allitemStr.Split('/').ToList(); 106 checkBoxListCtrl.CheckItems(items); 107 } 108 } 109 110 /// <summary> 111 /// checkboxlist选中项变化事件 112 /// </summary> 113 /// <param name="value"></param> 114 /// <param name="beCheck"></param> 115 private void MyCheckBoxListCtrl_ItemSelectdChanged(string value, bool beCheck) 116 { 117 string text = this.customComboBox1.Text; 118 if (beCheck) 119 { 120 if (text.Split('/').Count(t => t.Equals(value)) <= 0) 121 text += string.Format("/{0}", value); 122 } 123 else 124 { 125 List<string> curValues = text.Split('/').ToList(); 126 if (curValues.Contains(value)) 127 curValues.Remove(value); 128 text = string.Join("/", curValues.ToArray()); 129 } 130 string[] priorityItems = new string[] { "ZJ", "JY1", "JY2", "JY3" }; 131 List<string> Values = text.Split('/').ToList(); 132 List<string> priorityValues = new List<string>(); 133 foreach (var item in priorityItems) 134 { 135 if (Values.Contains(item)) 136 { 137 Values.Remove(item); 138 priorityValues.Add(item); 139 } 140 } 141 priorityValues.AddRange(Values); 142 text = string.Join("/", priorityValues.ToArray()); 143 text = text.Replace("//", "/"); 144 SetComboxText(text.Trim('/')); 145 if (SelectdItemChanged != null) 146 { 147 SelectdItemChanged(this.customComboBox1.Text); 148 } 149 } 150 151 /// <summary> 152 /// checkboxlist选中项变化事件 153 /// </summary> 154 /// <param name="value"></param> 155 /// <param name="beCheck"></param> 156 private void CheckBoxListCtrl_ItemSelectdChanged(string value, ItemCheckEventArgs arg2) 157 { 158 string text = this.customComboBox1.Text; 159 bool beCheck = arg2.NewValue == CheckState.Checked; 160 if (beCheck) 161 text += string.Format("/{0}", value); 162 else 163 { 164 List<string> curValues = text.Split('/').ToList(); 165 if (curValues.Contains(value)) 166 curValues.Remove(value); 167 text = string.Join("/", curValues.ToArray()); 168 } 169 string[] priorityItems = new string[] { "ZJ", "JY1", "JY2", "JY3" }; 170 List<string> Values = text.Split('/').ToList(); 171 List<string> priorityValues = new List<string>(); 172 foreach (var item in priorityItems) 173 { 174 if (Values.Contains(item)) 175 { 176 Values.Remove(item); 177 priorityValues.Add(item); 178 } 179 } 180 priorityValues.AddRange(Values); 181 text = string.Join("/", priorityValues.ToArray()); 182 // this.customComboBox1.TextChanged -= CustomComboBox1_TextChanged; 183 text = text.Replace("//", "/"); 184 SetComboxText(text.Trim('/')); 185 //this.customComboBox1.TextChanged += CustomComboBox1_TextChanged; 186 if (SelectdItemChanged != null) 187 { 188 SelectdItemChanged(this.customComboBox1.Text); 189 } 190 } 191 192 /// <summary> 193 /// 设置Combobox显示值 194 /// </summary> 195 /// <param name="text"></param> 196 private void SetComboxText(string text) 197 { 198 if (DropDownStyle == ComboBoxStyle.DropDownList) 199 { 200 customComboBox1.Items.Clear(); 201 customComboBox1.Items.Add(text.TrimStart('/')); 202 } 203 this.customComboBox1.Text = text.TrimStart('/'); 204 } 205 206 private void CustomComboBox1_TextChanged(object sender, EventArgs e) 207 { 208 UpdateCheckBoxListCheckedItem(this.customComboBox1.Text); 209 if (SelectdItemChanged != null) 210 { 211 SelectdItemChanged(this.customComboBox1.Text); 212 } 213 } 214 }
自定义组件CustomCombobox
namespace MultiComboBox { /// <summary> /// 弹出控件大小模式 /// </summary> public enum SizeMode { UseComboSize, UseControlSize, UseDropDownSize, } /// <summary> /// <c>CustomComboBox</c> is an extension of <c>ComboBox</c> which provides drop-down customization. /// </summary> [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] [Designer(typeof(CustomComboBoxDesigner))] public class CustomComboBox : ComboBox, IPopupControlHost { #region Construction and destruction public CustomComboBox() : base() { m_sizeCombo = new Size(base.DropDownWidth, base.DropDownHeight); m_popupCtrl.Closing += new ToolStripDropDownClosingEventHandler(m_dropDown_Closing); } void m_dropDown_Closing(object sender, ToolStripDropDownClosingEventArgs e) { m_lastHideTime = DateTime.Now; } public CustomComboBox(Control dropControl) : this() { DropDownControl = dropControl; } #endregion #region ComboBox overrides protected override void Dispose(bool disposing) { if (disposing) { if (m_timerAutoFocus != null) { m_timerAutoFocus.Dispose(); m_timerAutoFocus = null; } } base.Dispose(disposing); } #endregion #region Event handlers private void timerAutoFocus_Tick(object sender, EventArgs e) { if (m_popupCtrl.Visible && !DropDownControl.Focused) { DropDownControl.Focus(); m_timerAutoFocus.Enabled = false; } if (base.DroppedDown) base.DroppedDown = false; } private void m_dropDown_LostFocus(object sender, EventArgs e) { m_lastHideTime = DateTime.Now; } #endregion #region Events public new event EventHandler DropDown; public new event EventHandler DropDownClosed; public new event OldNewEventHandler<object> SelectedValueChanged; public void RaiseDropDownEvent() { EventHandler eventHandler = this.DropDown; if (eventHandler != null) this.DropDown(this, EventArgs.Empty); } public void RaiseDropDownClosedEvent() { EventHandler eventHandler = this.DropDownClosed; if (eventHandler != null) this.DropDownClosed(this, EventArgs.Empty); } public void RaiseSelectedValueChangedEvent(object oldValue, object newValue) { OldNewEventHandler<object> eventHandler = this.SelectedValueChanged; if (eventHandler != null) this.SelectedValueChanged(this, new OldNewEventArgs<object>(oldValue, newValue)); } #endregion #region IPopupControlHost Members /// <summary> /// Displays drop-down area of combo box, if not already shown. /// </summary> public virtual void ShowDropDown() { if (m_popupCtrl != null && !IsDroppedDown) { // Raise drop-down event. RaiseDropDownEvent(); // Restore original control size. AutoSizeDropDown(); Point location = PointToScreen(new Point(0, Height)); // Actually show popup. PopupResizeMode resizeMode = (this.m_bIsResizable ? PopupResizeMode.BottomRight : PopupResizeMode.None); m_popupCtrl.Show(this.DropDownControl, location.X, location.Y, Width, Height, resizeMode); m_bDroppedDown = true; m_popupCtrl.PopupControlHost = this; // Initialize automatic focus timer? if (m_timerAutoFocus == null) { m_timerAutoFocus = new Timer(); m_timerAutoFocus.Interval = 10; m_timerAutoFocus.Tick += new EventHandler(timerAutoFocus_Tick); } // Enable the timer! m_timerAutoFocus.Enabled = true; m_sShowTime = DateTime.Now; InitCheckBoxListCtrl(); } } private void InitCheckBoxListCtrl() { string text = this.Text; if (!string.IsNullOrEmpty(text)) { List<string> curValues = text.Split('/').ToList(); (this.DropDownControl as MyCheckboxListCtrl).CheckItems(curValues); } } /// <summary> /// Hides drop-down area of combo box, if shown. /// </summary> public virtual void HideDropDown() { if (m_popupCtrl != null && IsDroppedDown) { // Hide drop-down control. m_popupCtrl.Hide(); m_bDroppedDown = false; // Disable automatic focus timer. if (m_timerAutoFocus != null && m_timerAutoFocus.Enabled) m_timerAutoFocus.Enabled = false; // Raise drop-down closed event. RaiseDropDownClosedEvent(); } } #endregion #region Methods /// <summary> /// Automatically resize drop-down from properties. /// </summary> protected void AutoSizeDropDown() { if (DropDownControl != null) { switch (DropDownSizeMode) { case SizeMode.UseComboSize: DropDownControl.Size = new Size(Width, m_sizeCombo.Height); break; case SizeMode.UseControlSize: DropDownControl.Size = new Size(m_sizeOriginal.Width, m_sizeOriginal.Height); break; case SizeMode.UseDropDownSize: DropDownControl.Size = m_sizeCombo; break; } } } /// <summary> /// Assigns control to custom drop-down area of combo box. /// </summary> /// <param name="control">Control to be used as drop-down. Please note that this control must not be contained elsewhere.</param> protected virtual void AssignControl(Control control) { // If specified control is different then... if (control != DropDownControl) { // Preserve original container size. m_sizeOriginal = control.Size; // Reference the user-specified drop down control. m_dropDownCtrl = control; } } #endregion #region Win32 message handlers public const uint WM_COMMAND = 0x0111; public const uint WM_USER = 0x0400; public const uint WM_REFLECT = WM_USER + 0x1C00; public const uint WM_LBUTTONDOWN = 0x0201; public const uint CBN_DROPDOWN = 7; public const uint CBN_CLOSEUP = 8; public static uint HIWORD(int n) { return (uint)(n >> 16) & 0xffff; } public override bool PreProcessMessage(ref Message m) { if (m.Msg == (WM_REFLECT + WM_COMMAND)) { if (HIWORD((int)m.WParam) == CBN_DROPDOWN) return false; } return base.PreProcessMessage(ref m); } private static DateTime m_sShowTime = DateTime.Now; private void AutoDropDown() { if (m_popupCtrl != null && m_popupCtrl.Visible) HideDropDown(); else if ((DateTime.Now - m_lastHideTime).Milliseconds > 50) ShowDropDown(); } protected override void WndProc(ref Message m) { if (m.Msg == WM_LBUTTONDOWN) { AutoDropDown(); return; } if (m.Msg == (WM_REFLECT + WM_COMMAND)) { switch (HIWORD((int)m.WParam)) { case CBN_DROPDOWN: AutoDropDown(); return; case CBN_CLOSEUP: if ((DateTime.Now - m_sShowTime).Seconds > 1) HideDropDown(); return; } } base.WndProc(ref m); } #endregion #region Enumerations #endregion #region Properties /// <summary> /// Actual drop-down control itself. /// </summary> [Browsable(false)] public Control DropDownControl { get { return m_dropDownCtrl; } set { AssignControl(value); } } /// <summary> /// Indicates if drop-down is currently shown. /// </summary> [Browsable(false)] public bool IsDroppedDown { get { return this.m_bDroppedDown /*&& m_popupCtrl.Visible*/; } } /// <summary> /// Indicates if drop-down is resizable. /// </summary> [Category("Custom Drop-Down"), Description("Indicates if drop-down is resizable.")] public bool AllowResizeDropDown { get { return this.m_bIsResizable; } set { this.m_bIsResizable = value; } } /// <summary> /// Indicates current sizing mode. /// </summary> [Category("Custom Drop-Down"), Description("Indicates current sizing mode."), DefaultValue(SizeMode.UseComboSize)] public SizeMode DropDownSizeMode { get { return this.m_sizeMode; } set { if (value != this.m_sizeMode) { this.m_sizeMode = value; AutoSizeDropDown(); } } } [Category("Custom Drop-Down")] public Size DropSize { get { return m_sizeCombo; } set { m_sizeCombo = value; if (DropDownSizeMode == SizeMode.UseDropDownSize) AutoSizeDropDown(); } } [Category("Custom Drop-Down"), Browsable(false)] public Size ControlSize { get { return m_sizeOriginal; } set { m_sizeOriginal = value; if (DropDownSizeMode == SizeMode.UseControlSize) AutoSizeDropDown(); } } #endregion #region Hide some unwanted properties [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new ComboBoxStyle DropDownStyle { get { return base.DropDownStyle; } set { base.DropDownStyle = value; } } [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new ObjectCollection Items { get { return base.Items; } } [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new int ItemHeight { get { return base.ItemHeight; } set { } } [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new int MaxDropDownItems { get { return base.MaxDropDownItems; } set { } } [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new string DisplayMember { get { return base.DisplayMember; } set { } } [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new string ValueMember { get { return base.ValueMember; } set { } } [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new int DropDownWidth { get { return base.DropDownWidth; } set { } } [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new int DropDownHeight { get { return base.DropDownHeight; } set { } } [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new bool IntegralHeight { get { return base.IntegralHeight; } set { } } [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new bool Sorted { get { return base.Sorted; } set { } } #endregion #region Attributes /// <summary> /// Popup control. /// </summary> private PopupControl m_popupCtrl = new PopupControl(); /// <summary> /// Actual drop-down control itself. /// </summary> Control m_dropDownCtrl; /// <summary> /// Indicates if drop-down is currently shown. /// </summary> bool m_bDroppedDown = false; /// <summary> /// Indicates current sizing mode. /// </summary> SizeMode m_sizeMode = SizeMode.UseComboSize; /// <summary> /// Time drop-down was last hidden. /// </summary> DateTime m_lastHideTime = DateTime.Now; /// <summary> /// Automatic focus timer helps make sure drop-down control is focused for user /// input upon drop-down. /// </summary> Timer m_timerAutoFocus; /// <summary> /// Original size of control dimensions when first assigned. /// </summary> Size m_sizeOriginal = new Size(1, 1); /// <summary> /// Original size of combo box dropdown when first assigned. /// </summary> Size m_sizeCombo; /// <summary> /// Indicates if drop-down is resizable. /// </summary> bool m_bIsResizable = true; #endregion } internal class CustomComboBoxDesigner : ParentControlDesigner { #region ParentControlDesigner Overrides protected override void PreFilterProperties(IDictionary properties) { base.PreFilterProperties(properties); properties.Remove("DropDownStyle"); properties.Remove("Items"); properties.Remove("ItemHeight"); properties.Remove("MaxDropDownItems"); properties.Remove("DisplayMember"); properties.Remove("ValueMember"); properties.Remove("DropDownWidth"); properties.Remove("DropDownHeight"); properties.Remove("IntegralHeight"); properties.Remove("Sorted"); } #endregion }
自定义用户控件MyCheckboxListCtrl
public partial class MyCheckboxListCtrl : UserControl { /// <summary> /// CheckBoxList中指定项选中状态变化事件《指定项的值,选中状态》 /// </summary> public event Action<string, bool> ItemSelectdChanged; private Dictionary<string, string> m_dicValues = new Dictionary<string, string>(); public Dictionary<string, string> DataSource { get { return m_dicValues; } set { if (m_dicValues != value) m_dicValues = value; } } private List<string> m_selectedCheckboxs = new List<string>(); public List<string> CheckedItems { get { return m_selectedCheckboxs; } } public int Count { get { return this.Controls.Count; } } public MyCheckboxListCtrl() { InitializeComponent(); this.HScroll = true; GetControl(); } /// <summary> /// 设置默认选中项 /// </summary> /// <param name="items"></param> public void CheckItems(List<string> items) { foreach (string key in items) { if (DataSource.ContainsKey(key)) { foreach (Control ctrl in this.Controls) { if (ctrl is CheckBox) { if (key.Equals((ctrl as CheckBox).Text)) { (ctrl as CheckBox).Checked = true; break; } } } } } } /// <summary> /// 计算checkbox数量及位置 /// </summary> private void GetControl() { this.Controls.Clear(); int height = 8; int index = 1; int pointX = 14; int pointY = height; int sizeW = 120; int sizeH = this.Font.Height + 2; foreach (string v in m_dicValues.Keys) { CheckBox checkBox1 = new CheckBox(); checkBox1.AutoSize = false; //判断下一个checkbox的位置 if (pointY > this.Height - this.Font.Height - height) { pointX = pointX + sizeW + 15; pointY = height; checkBox1.Location = new System.Drawing.Point(pointX, pointY); checkBox1.Size = new System.Drawing.Size(sizeW, sizeH); } else { checkBox1.Location = new System.Drawing.Point(pointX, pointY); checkBox1.Size = new System.Drawing.Size(sizeW, sizeH); } checkBox1.Name = "checkBox" + index; checkBox1.TabIndex = 30 + index; checkBox1.Text = m_dicValues[v]; checkBox1.UseVisualStyleBackColor = true; checkBox1.CheckedChanged += CheckBox1_CheckedChanged; this.Controls.Add(checkBox1); index++; pointY = pointY + this.Font.Height + height; } } private void CheckBox1_CheckedChanged(object sender, EventArgs e) { CheckBox ckb = sender as CheckBox; if (ckb.Checked && !m_selectedCheckboxs.Contains(ckb.Text)) m_selectedCheckboxs.Add(ckb.Text); else if (!ckb.Checked && m_selectedCheckboxs.Contains(ckb.Text)) m_selectedCheckboxs.Remove(ckb.Text); if (ItemSelectdChanged != null) { if (ckb.Checked && !m_selectedCheckboxs.Contains(ckb.Text)) m_selectedCheckboxs.Add(ckb.Text); else if (!ckb.Checked && m_selectedCheckboxs.Contains(ckb.Text)) m_selectedCheckboxs.Remove(ckb.Text); string itemValue = ckb.Text; //bool check = e.NewValue == CheckState.Checked; string selValue = ""; foreach (KeyValuePair<string, string> pair in DataSource) { if (pair.Value == itemValue) { selValue = pair.Key; break; } } ItemSelectdChanged(selValue, ckb.Checked); } } private void UserControl1_SizeChanged(object sender, EventArgs e) { GetControl(); } }
代码比较多,有需要的可私信,发你demo
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?