最近喜欢上了自己创建控件,感觉方便使用
其实自定义控件,除了之前说的自己建立个独立的控件库的方法外,另一个办法就是通过继承已有的控件来扩展功能
今天就是用这个办法,在combox的控件中扩展个check功能,这样就可以有选择的选择多条Item了。
其实主要的步骤就是:1.定义属性和各个属性的读写 2.定义方法和各种函数实现功能 3.关联各种方法和事件触发
首先,要确定框架
using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Drawing; using System.Diagnostics; using System.Windows.Forms; namespace PowerComboBox //带有复选框的combox { public class xComboBox : ComboBox { public xComboBox() { this.DrawMode =System.Windows.Forms.DrawMode.OwnerDrawFixed; //这是关键;没有这一步自定义的控件无法显示出来 } } }
下来我们就要看看需要定义什么样的属性了
private string mDividerFormat = ""; private Color mGroupColor = System.Drawing.SystemColors.WindowText; //分组的背景颜色 private System.Windows.Forms.CheckState[] mItemsChecks;//用来记录选中的状态,那些选中,那些没选中。只要读这里就知道。 private System.Windows.Forms.CheckState[] mItemsChecks_Temp; private bool mCheckBoxes; //是否使用复选框 private System.Windows.Forms.CheckState mChecked = CheckState.Unchecked; //默认的选中状态 private Color mGridColor = Color.FromArgb(240, 248, 255);//条目的背景颜色
各种变量
private char mItemSeparator1 = ',';//用来分隔多条选中信息的 private char mItemSeparator2 = '&';//同上 private Int32 mHoverIndex; private double mHoverIndex_Dec; private Int32 mLastSelectedIndex = -1; private Timer mTimer;//定时器,用来控制在Item选中状态变化过程中,发生的事 private Int32 mFirerTimer; private Int32 mKillEvents1; private Int32 mKillEvents2; private Int32 mKillEvents3; private Int32 mLastMessage;
再来看看各个变量的读写设置
[System.ComponentModel.Description("Use this property to set divider flag. Recommend you use three hyphens ---."), System.ComponentModel.Category("Power Properties")] //这种写法就是要将属性在属性页中显示的样子(请参照我之前写的Property Grid的内容) public string DividerFormat { get { return mDividerFormat; } set { mDividerFormat = value; } } [System.ComponentModel.Description("Use this property to set the ForeColor of the grouping text."), System.ComponentModel.Category("Power Properties")] public Color GroupColor { get { return mGroupColor; } set { mGroupColor = value; } } [System.ComponentModel.Description("Use this property to set the BackColor of the grid."), System.ComponentModel.Category("Power Properties")] public Color GridColor { get { return mGridColor; } set { mGridColor = value; } } //ORIGINAL LINE: Public Property ItemsChecks(ByVal xIndex As Int32) As System.Windows.Forms.CheckState //INSTANT C# NOTE: C# does not support parameterized properties - the following property has been divided into two methods: [System.ComponentModel.Description("Use this property to get/set corresponding checkboc values."), System.ComponentModel.Category("Power Properties")] public System.Windows.Forms.CheckState get_ItemsChecks(Int32 xIndex) { try { return mItemsChecks[xIndex]; } catch (Exception ex) { return CheckState.Unchecked; } } public void set_ItemsChecks(Int32 xIndex, System.Windows.Forms.CheckState value) //将条目的序号和选中状态配对mItemsChecks[xIndex] = value { if (xIndex < 0 | xIndex > this.Items.Count) { return; } if (mKillEvents3 != 0) { mItemsChecks[xIndex] = value; //mItemsChecks[xIndex] = value;将条目序号和状态对应 if (ItemChecked != null) ItemChecked(SelectedIndex); } else { if (this.CheckBoxes)//如果已经选定check功能 { if (this.DroppedDown)// { if (this.SelectedIndex == xIndex) { this.SelectedIndex = -1; } this.SelectedIndex = xIndex; PrepareTimer(); if (ItemChecked != null) ItemChecked(SelectedIndex); //raises event immeadiatly } else { if (mItemsChecks == null) { Array.Resize(ref mItemsChecks_Temp, this.Items.Count); mItemsChecks_Temp[xIndex] = value; CommitCheckList(); } else { mItemsChecks[xIndex] = value; } } } } } [System.ComponentModel.Description("Use this property to enable checkboxes."), System.ComponentModel.Category("Power Properties")] public bool CheckBoxes { get { return mCheckBoxes; } set { mCheckBoxes = value; } } [System.ComponentModel.Description("Use this property to set CheckBox's default value."), System.ComponentModel.Category("Power Properties")] public System.Windows.Forms.CheckState Checked { get { return mChecked; } set { mChecked = value; } } [System.ComponentModel.Description("Use this property to set item separator1 character."), System.ComponentModel.Category("Power Properties")] public char ItemSeparator1 { get { return mItemSeparator1; } set { mItemSeparator1 = value; } } [System.ComponentModel.Description("Use this property to set item separator2 character."), System.ComponentModel.Category("Power Properties")] public char ItemSeparator2 { get { return mItemSeparator2; } set { mItemSeparator2 = value; } }
下来就是事件方法了
其实大体的流程就是在选择了check功能后,下拉菜单在打开时重新绘制个带check的Items。然后在选中某个Item项时,combox的Text中就重新显示说有checked的项目
protected override void OnSelectedIndexChanged(System.EventArgs e)//当选项的check状态被选中时 { Int32 i = 0; if (mKillEvents1 != 0 || SelectedIndex == -1) { return; } if (this.DividerFormat.Length > 0 && IsItemAGroup(SelectedIndex)) { if (! this.CheckBoxes) { this.SelectedIndex = mLastSelectedIndex; return; } else { mKillEvents3 += 1; i = SelectedIndex; do { i += 1; if (i > (this.Items.Count - 1)) { break; } if (IsItemAGroup(i)) { break; } this.SelectedIndex = i; } while (true); mKillEvents3 -= 1; base.OnSelectedIndexChanged(e); return; } } //2 - standard event stuff mLastSelectedIndex = this.SelectedIndex; base.OnSelectedIndexChanged(e); //3 - toggle checkbox/force redraw if (this.CheckBoxes && SelectedIndex > -1) { mKillEvents3 += 1; if (this.get_ItemsChecks(this.SelectedIndex) == CheckState.Checked) { this.set_ItemsChecks(this.SelectedIndex, CheckState.Unchecked); } else { this.set_ItemsChecks(this.SelectedIndex, CheckState.Checked); } mKillEvents3 -= 1; PrepareTimer(); } } protected override void OnDrawItem(DrawItemEventArgs e)//重新绘制Item,根据用户是否选择了check定义item项的样子 { Int32 zX1 = 0; Pen zPen = null; float zWidth = 0F; string zText = null; Font zFont = null; Color zFore = new Color(); System.Windows.Forms.VisualStyles.CheckBoxState zState = 0; //1 - Exit if (e.Index < 0) { base.OnDrawItem(e); return; } //2 - Grouping if (this.Items[e.Index].ToString().Contains(this.mDividerFormat) & mDividerFormat.Length > 0) { e.DrawBackground(); if (DataSource == null) //TN Engineers { zText = this.Items[e.Index].ToString(); } else { zText = ((DataRowView)(this.Items[e.Index]))[this.DisplayMember].ToString(); } zText = this.Items[e.Index].ToString(); zText = " " + zText.Replace(this.mDividerFormat, "") + " "; zFont = new Font(Font, FontStyle.Bold); if (e.BackColor == System.Drawing.SystemColors.Highlight) { zFore = Color.Gainsboro; } else { zFore = this.GroupColor; } zPen = new Pen(zFore); zWidth = e.Graphics.MeasureString(zText, zFont).Width; zX1 = Convert.ToInt32(e.Bounds.Width - zWidth) / 2; e.Graphics.DrawRectangle(zPen, new Rectangle(e.Bounds.X, e.Bounds.Y + e.Bounds.Height / 2, zX1, 1)); e.Graphics.DrawRectangle(zPen, new Rectangle(e.Bounds.Width - zX1, e.Bounds.Y + e.Bounds.Height / 2, e.Bounds.Width, 1)); e.Graphics.DrawString(zText, zFont, new SolidBrush(zFore), zX1, e.Bounds.Top); } else { if (mKillEvents2 == 0) { if (true == System.Convert.ToBoolean(e.State & DrawItemState.Selected)) { e.DrawBackground(); } else if (true == (e.Index % 2 == 0)) { e.Graphics.FillRectangle(new SolidBrush(Color.White), e.Bounds); } else { e.Graphics.FillRectangle(new SolidBrush(mGridColor), e.Bounds); } } if (this.DataSource == null) { e.Graphics.DrawString(this.Items[e.Index].ToString(), Font, new SolidBrush(e.ForeColor), e.Bounds.Left, e.Bounds.Top); } else { e.Graphics.DrawString(((DataRowView)(this.Items[e.Index]))[this.DisplayMember].ToString(), Font, new SolidBrush(e.ForeColor), e.Bounds.Left, e.Bounds.Top); } //5 - CheckBox if (mCheckBoxes) { if (System.Convert.ToBoolean(e.State & DrawItemState.Selected)) { if (this.get_ItemsChecks(e.Index) == CheckState.Checked) { zState = System.Windows.Forms.VisualStyles.CheckBoxState.CheckedHot; } else { zState = System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedHot; } } else { if (this.get_ItemsChecks(e.Index) == CheckState.Checked) { zState = System.Windows.Forms.VisualStyles.CheckBoxState.CheckedNormal; } else { zState = System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal; } } zX1 = this.FontHeight; zPen = new Pen(Color.Black, 1F); zPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot; System.Windows.Forms.CheckBoxRenderer.DrawCheckBox(e.Graphics, new System.Drawing.Point(e.Bounds.X + e.Bounds.Width - 15, e.Bounds.Y + 1 + ((e.Bounds.Height - 13) / 2)), e.Bounds, "", this.Font, false, zState); } } //6 - Base event base.OnDrawItem(e); } protected override void WndProc(ref System.Windows.Forms.Message m)//消息处理 { const Int32 WM_SETCURSOR = 32; const Int32 WM_COMMAND = 273; const int WM_CTLCOLORLISTBOX = 308; const Int32 CB_ADDSTRING = 323; const Int32 CB_GETCURSEL = 327; const Int32 WM_LBUTTONUP = 514; const Int32 OCM_COMMAND = 8465; if (true == (m.Msg == WM_CTLCOLORLISTBOX || m.Msg == WM_SETCURSOR)) { GetHoverIndexFromCursor(); if (mHoverIndex_Dec >= 0) { if (mHoverIndex > -1 & mHoverIndex < this.Items.Count & this.DroppedDown) { if (ItemHover != null) ItemHover(mHoverIndex); } } } else if (true == (m.Msg == CB_ADDSTRING)) { base.WndProc(ref m); StretchCheckList(); return; } else if (true == (m.Msg == WM_COMMAND && m.WParam == new System.IntPtr(66536))) { if (mLastMessage == 8235) //FIX for ignoring SIC event on keyboard controlling i.e. F4 + down arrow { mHoverIndex = this.SelectedIndex; if (ItemHover != null) ItemHover(mHoverIndex); mLastMessage = m.Msg; return; } //1 - normal behaviour when no checkboxes if (! this.mCheckBoxes) { base.WndProc(ref m); return; } if (! this.DroppedDown) { return; } SendMessage(this.Handle, OCM_COMMAND, 591814, new IntPtr(1705748)); //1 SendMessage(this.Handle, OCM_COMMAND, 67526, new IntPtr(1705748)); //2 SendMessage(this.Handle, CB_GETCURSEL, 0, new IntPtr(0)); //3 SendMessage(this.Handle, WM_LBUTTONUP, 0, new IntPtr(721012)); //4 return; } mLastMessage = m.Msg; base.WndProc(ref m); } protected override void OnKeyDown(System.Windows.Forms.KeyEventArgs e) //键盘事件 { if (this.DroppedDown && e.KeyCode == Keys.Space) { mKillEvents3 += 1; e.SuppressKeyPress = true; if (this.CheckBoxes) { if (this.get_ItemsChecks(this.SelectedIndex) == CheckState.Checked) //如果选择的条目是被选中的状态 { this.set_ItemsChecks(this.SelectedIndex, CheckState.Unchecked);//则是变反 } else { this.set_ItemsChecks(this.SelectedIndex, CheckState.Checked); } mKillEvents3 -= 1; } else //如果没有选用check的形式条目,那么就不用处理 { this.DroppedDown = false; } PrepareTimer(); //时间空间处理刷新text } base.OnKeyDown(e); }
下面是方法中用到的各个处理方法
private void PrepareTimer() //通过时间控件来刷新combox的text显示的内容 { mTimer = null; mTimer = new Timer(); mTimer.Interval = 64; mTimer.Enabled = true; mTimer.Tick += mTimer_Tick; mFirerTimer = 1; } private void mTimer_Tick(object sender, System.EventArgs e) { if (mFirerTimer > 0) { mTimer.Enabled = false; mTimer.Dispose(); ForceRedraw(); this.Text = FormatCheckString(); //将选中的显示出来 mFirerTimer = 0;//判断是否启动timer控件的 } } public void StretchCheckList() { Int32 i = 0; Array.Resize(ref mItemsChecks, this.Items.Count); //1. suck in temp if (mItemsChecks_Temp != null) { mItemsChecks = mItemsChecks_Temp; mItemsChecks_Temp = null; //2. formats list with pre-defined format } else { if (mChecked != CheckState.Checked) { return; } for (i = 0; i <= mItemsChecks.GetUpperBound(0); i++) { if (! (IsItemAGroup(i))) { mItemsChecks[i] = CheckState.Checked; } } } this.Text = FormatCheckString(); } public void CommitCheckList() { mItemsChecks = mItemsChecks_Temp; this.Text = FormatCheckString(); } //private private void GetHoverIndexFromCursor() { Int32 yPos = 0; yPos = this.PointToClient(System.Windows.Forms.Cursor.Position).Y; if (this.DropDownStyle == ComboBoxStyle.Simple) { yPos -= (this.ItemHeight + 10); } else { yPos -= (this.Size.Height + 1); } mHoverIndex_Dec = yPos / (double)this.ItemHeight; mHoverIndex = System.Convert.ToInt32(Math.Floor(mHoverIndex_Dec)); } private void ForceRedraw() { if (this.Items.Count > 0) { mKillEvents1 += 1; mKillEvents2 += 1; this.SelectedIndex -= 1; mKillEvents2 -= 1; this.SelectedIndex += 1; mKillEvents1 -= 1; } } private string FormatCheckString() //形成text { //NOTE returns "a,b & c" for internal use Int32 i = 0; System.Text.StringBuilder sb = new System.Text.StringBuilder(""); string s = null; string zFirst = null; string zLast = null; Int32 zLastComa = 0; string zSpace = " "; if (! this.CheckBoxes) //首先要确定是check的item { return this.Text; } for (i = 0; i < this.Items.Count; i++) { if (IsItemAGroup(i)) //什么意思啊 { continue; } if (this.get_ItemsChecks(i) == CheckState.Checked)//确定选项是被选中的 { if (DataSource == null) //TN Engineers { sb.Append(this.Items[i].ToString()); } else { sb.Append(((DataRowView)(this.Items[i]))[this.DisplayMember].ToString()); } sb.Append(mItemSeparator1); //没加一项就加一个分隔符& } } s = sb.ToString(); if (s.Length == 0) { return ""; } s = s.Substring(0, s.Length - 1); zLastComa = s.LastIndexOf(mItemSeparator1); if (zLastComa != -1) { zLast = s.Substring(zLastComa); zFirst = s.Substring(0, zLastComa); s = zFirst + zSpace + zLast.Replace(Convert.ToString(mItemSeparator1), Convert.ToString(mItemSeparator2) + zSpace); return s; } else { return s + " "; } } private bool IsItemAGroup(Int32 xIndex) { if (this.mDividerFormat.Length > 0 && this.Items[xIndex].ToString().Contains(this.mDividerFormat)) { return true; } //INSTANT C# NOTE: Inserted the following 'return' since all code paths must return a value in C#: return false; } //public public string CHECKED2CSV() { //NOTE returns "a,b,c" for external user Int32 i = 0; System.Text.StringBuilder sb = new System.Text.StringBuilder(""); string s = null; for (i = 0; i < this.Items.Count; i++) { if (IsItemAGroup(i)) { continue; } if (this.get_ItemsChecks(i) == CheckState.Checked) { if (DataSource == null) //TN Engineers { sb.Append(this.Items[i].ToString() + ","); } else { sb.Append(((DataRowView)(this.Items[i]))[this.ValueMember].ToString() + ","); } } } s = sb.ToString(); if (s.Length > 0) { s = s.Substring(0, s.Length - 1); } return s; } public string CHECKED2IDCSV() { return CHECKED2IDCSV(true); } //INSTANT C# NOTE: Overloaded method(s) are created above to convert the following method having optional parameters: //ORIGINAL LINE: Public Function CHECKED2IDCSV(Optional ByVal xZeroBound As Boolean = true) As String public string CHECKED2IDCSV(bool xZeroBound) { Int32 i = 0; Int32 c = 0; Int32 a = 0; System.Text.StringBuilder sb = new System.Text.StringBuilder(""); string s = null; if (xZeroBound) { a = 0; } else { a = 1; } for (i = 0; i < this.Items.Count; i++) { if (IsItemAGroup(i)) { continue; } if (this.get_ItemsChecks(i) == CheckState.Checked) { if (this.mDividerFormat.Length > 0) { sb.Append((c + a).ToString() + ","); } else { sb.Append((i + a).ToString() + ","); } } c += 1; } s = sb.ToString(); if (s.Length > 0) { s = s.Substring(0, s.Length - 1); } return s; } public string CHECKED2BITSHIFT() { Int32 i = 0; Int32 c = 1; Int32 a = 0; for (i = 0; i < this.Items.Count; i++) { if (IsItemAGroup(i)) { continue; } if (this.get_ItemsChecks(i) != CheckState.Checked) { a += c; } c = (c * 2); } return a.ToString(); } public void CheckAll(CheckState xChecked) { Int32 i = 0; for (i = 0; i < this.Items.Count; i++) { this.set_ItemsChecks(i, xChecked); } this.Text = FormatCheckString(); }
其实知道了流程,完全就可以自定义控件来使用。方便好用