欢迎光临
我们一直在努力

玩转控件:重绘ComboBox —— 让ComboBox多列显示 (提供源码下载)

  前言

        高考结束了,马上又将会有这么一群孩子,迫不及待的扔下书包,去聚餐,通宵上网,旅行,KTV,闲逛,狂欢……认为自己终于解放了……殊不知,你们离开的,就是天堂。

-- 致 即将步入社会的孩纸们

 

  缘由

 

       最近在维护一个winform项目,公司购买的是DevExpress控件 (请问怎么联系DevExpress工作人员? 我想询问下,广告费是怎么给的。:p),经过公司大牛们对DevExpress控件疯狂的重写、封装、加密、混淆...等一系列的操作,制作了一套 安全+实用 、基于DevExpress控件又高于DevExpress控件的模板。此时,大家也许觉得我夸张了。但是哥很淡定的告诉大家:不信拉倒!

     一系列绚丽的控件中,目前最让我久久不能忘记吃早餐的就是comboboxEdit的显示多列的功能。所以我就想,能不能用普通的ComboBox控件实现这样的功能,带着这样的问题询问了没有被公司网络限制的度娘,结果度娘告诉我需要用一个TextBox结合一个ListView或者GridView来实现这样的功能。我嘞个去~ 这不是和Web里面DIV的显示和隐藏一样了么。 搞得跟个二五八萬似的! 深叹一口气,脑子一转想起了上篇《公用章水印工具》中用到的GDI+,于是有了下文:

 

  实施:新建组件类

       首先,新建一个winform窗体项目,新建一个组件类,并让其继承ComboBox控件类. 

    

public partial class MyComboBox : ComboBox

 

       然后,就是需要做的就是:重写涉及到下拉列表显示的事件了。重写之前需要注意到一点是:将ComboBox控件的DrawMode 设置为DrawMode.OwnerDrawVariable(手动绘制元素)

       无废话,直接贴出核心代码:

     

 /// <summary> /// 初始化数据源各列的名称 /// </summary> private void InitializeColumns() { PropertyDescriptorCollection propertyDescriptorCollection = DataManager.GetItemProperties(); columnWidths = new float[propertyDescriptorCollection.Count]; columnNames = new string[propertyDescriptorCollection.Count]; for (int i = 0; i < propertyDescriptorCollection.Count; i++) { string name = propertyDescriptorCollection[i].Name; columnNames[i] = name; } } /// <summary> /// 显示下拉框的时候出发 /// </summary> /// <param name="e"></param> protected override void OnDropDown(EventArgs e) { base.OnDropDown(e); this.DropDownWidth = (int)CalculateTotalWidth();//计算下拉框的总宽度   } private float CalculateTotalWidth() { columnPadding = 5; float totalWidth = 0; foreach (int width in columnWidths) { totalWidth += (width + columnPadding); } //总宽度加上垂直滚动条的宽度 return totalWidth + SystemInformation.VerticalScrollBarWidth; } /// <summary> /// 获取各列的宽度和项的总宽度 /// </summary> /// <param name="e"></param> protected override void OnMeasureItem(MeasureItemEventArgs e) { base.OnMeasureItem(e); if (DesignMode) { return; } InitializeColumns(); for (int i = 0; i < columnNames.Length; i++) { string item = Convert.ToString(FilterItemOnProperty(Items[e.Index], columnNames[i])); SizeF sizeF = e.Graphics.MeasureString(item, Font);//返回显示项字符串的大小 columnWidths[i] = Math.Max(columnWidths[i], sizeF.Width); } float totalWidth = CalculateTotalWidth();//计算combobox下拉框项的宽度 e.ItemWidth = (int)totalWidth;//设置下拉框项的宽度  } /// <summary> /// 绘制下拉框的内容 /// </summary> /// <param name="e"></param> protected override void OnDrawItem(DrawItemEventArgs e) { base.OnDrawItem(e); if (DesignMode) { return; } Rectangle boundsrect = e.Bounds;//获取绘制项边界的矩形 //e.DrawBackground();  e.Graphics.FillRectangle(Brushes.Bisque, e.Bounds); if ((e.State & DrawItemState.Focus) == 0) { //设置鼠标悬浮ComboBox的item的背景色  e.Graphics.FillRectangle(Brushes.White, e.Bounds); } int lastRight = 0; using (Pen linePen = new Pen(SystemColors.GrayText)) { using (SolidBrush brush = new SolidBrush(ForeColor)) { if (columnNames.Length == 0) { e.Graphics.DrawString(Convert.ToString(Items[e.Index]), Font, brush, boundsRect); } else { //循环各列 for (int i = 0; i < columnNames.Length; i++) { string item = Convert.ToString(FilterItemOnProperty(Items[e.Index], columnNames[i])); boundsRect.X = lastRight;//列的左边位置 boundsRect.Width = (int)columnWidths[i] + columnPadding;//列的宽度 lastRight = boundsRect.Right; //绘制项的内容  e.Graphics.DrawString(item, Font, brush, boundsRect); //绘制各项间的竖线 if (i < columnNames.Length - 1) { e.Graphics.DrawLine(linePen, boundsRect.Right, boundsRect.Top, boundsRect.Right, boundsRect.Bottom); } } } } } e.DrawFocusRectangle(); }

 

           上述代码没有什么亮点,稍微了解点GDI+的都能看懂,而且有很丰富的注释。就不一一解释了。

           显示多列的核心其实就是根据数据源传入的数据,循环每个属性(列) 并且用“|”分开罢了。具体操作见上图中重写的OnDrawItem事件。 

           其中让我耗时的操作是如下这段代码:

 //e.DrawBackground();  e.Graphics.FillRectangle(Brushes.Bisque, e.Bounds); if ((e.State & DrawItemState.Focus) == 0) { //设置鼠标悬浮ComboBox的item的背景色  e.Graphics.FillRectangle(Brushes.White, e.Bounds); } 

         鼠标悬浮到ComboBox的item的背景色, 其实直接可以调用e.DrawBackground()方法即可,默认的是深蓝色。因为我字体显示的是黑色,加上深蓝背景色后,列表项看起来很费神。

         所以苦思冥想,反复测试,最后才发现可以用DrawItemState.Focus来判断鼠标悬浮到哪个Item(鼠标焦点)的位置,然后用喜欢的颜色填充当前的小矩形,鼠标焦点离开时再恢复原来的背景色即可。(如果不想这么处理,也可以用第一种方法,换个字体颜色就行了)

 

  回到窗体,重新定义窗体设计器的代码InitializeComponent()

       

         首先,深呼吸,扎下马步,气运丹田,一鼓作气使出 拖控件大法 ,将ComboBox拖入到窗体中,这样VS会自动在窗体设计器中新增窗体所有控件的设计代码。

InitializeComponent()方法很熟悉吧,随便新建一个窗体,后台代码都会自动生成这个方法,根据控件的位置、大小等属性添加设计代码。

        但是,我们要做的是重写ComboBox,所以InitializeComponent()方法,我们需要做些改动。 我的做法是:双击窗体的Designer.cs文件,进去把InitializeComponent()方法直接剪切出来,放到窗体的.cs文件中,然后修改代码将ComboBox的实例化对象指向我们新建的组件类。具体操作如下代码:

      

 this.components = new System.ComponentModel.Container(); this.MyComboBox1 = new TTTT.MyComboBox(this.components); //实例化对象指定到我们绘制的组件类 this.SuspendLayout(); // // MyComboBox1 // this.MyComboBox1.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable; this.MyComboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.MyComboBox1.FormattingEnabled = true; this.MyComboBox1.Location = new System.Drawing.Point(6, 22); this.MyComboBox1.Name = "MyComboBox1"; this.MyComboBox1.Size = new System.Drawing.Size(144, 21); this.MyComboBox1.TabIndex = 0; this.MyComboBox1.SelectedIndexChanged += new System.EventHandler(this.MyComboBox1_SelectedIndexChanged);

        

  数据源绑定

       绑定数据源的方式有很多中,DataTable、List<T>、string[]等数组都可以,本文演示DataTable 作为数据源进行绑定,代码如下:

      

 DataTable dataTable = new DataTable("Student"); dataTable.Columns.Add("Number", typeof(String)); dataTable.Columns.Add("Name", typeof(String)); dataTable.Columns.Add("RealName", typeof(String)); dataTable.Columns.Add("UserName", typeof(String)); dataTable.Columns.Add("Address", typeof(String)); dataTable.Rows.Add(new String[] { "1", "James", "张三", "james.zhang", "长沙" }); dataTable.Rows.Add(new String[] { "2", "Mary", "李四", "mary.xu", "山东" }); dataTable.Rows.Add(new String[] { "3", "Jack", "王五", "jack.li", "台湾" }); dataTable.Rows.Add(new String[] { "4", "joy", "赵六", "joy.zhou", "济南" }); dataTable.Rows.Add(new String[] { "5", "jay", "钱七", "jay.ji", "美国" }); dataTable.Rows.Add(new String[] { "6", "stephen", "康忠鑫", "Stephen.Kang", "深圳" }); MyComboBox1.DataSource = dataTable;

 

       效果图如下:

    玩转控件:重绘ComboBox  —— 让ComboBox多列显示 (提供源码下载)

    (如果您对图片中的公章水印的效果感兴趣,请见我上篇博文:《公用章水印工具》)

 

  获取数据

      说完数据绑定,自然要说如何获取到自己想要的数据了,否则华而不实,只能看不能用。 代码如下:

 

 try { DataTable dt = MyComboBox1.DataSource as DataTable; if (dt != null) { textBox1.Text = dt.Rows[MyComboBox1.SelectedIndex]["Name"].ToString(); textBox2.Text = dt.Rows[MyComboBox1.SelectedIndex]["RealName"].ToString(); textBox3.Text = dt.Rows[MyComboBox1.SelectedIndex]["UserName"].ToString(); textBox4.Text = dt.Rows[MyComboBox1.SelectedIndex]["Address"].ToString(); } } catch (Exception ex) { MessageBox.Show(ex.Message); }

      添加ComboBox的SelectedIndexChanged事件,将如上代码添加进去即可。 最终效果图:

      玩转控件:重绘ComboBox  —— 让ComboBox多列显示 (提供源码下载)

 

  后语

      一篇文章写了1个半小时,边写还边组织语言,不知道说清楚没有,如果对大家有帮助,将请点击下面的推荐吧! 千万别手软~

      俗话说得好,您的支持,就是我写作的动力。 谢谢大家~  睡觉

      下载链接已更新,见博文尾端。

 

 更新说明(时间:201306071122)

 

       目前ComboBox中默认显示的是一整行的内容,按照日常的客户体验,需要显示用户定义的列,
       解决方法:在MyComboBox组件类中,将ComboBoxStyle设置为ComboBoxStyle.DropDown,在数据绑定的时候,设置ComboBox的DisplayMember和ValueMember属性即可。

 

MyComboBox1.DisplayMember = "Name"; MyComboBox1.ValueMember = "Number"; 

 

 

        但是,这样有个不好处就是,ComboBoxStyle.DropDown样式下,   无法获取鼠标选择行的焦点,所以DrawItem中

 

 e.Graphics.FillRectangle(Brushes.Bisque, e.Bounds); if ((e.State & DrawItemState.Focus) == 0) { //this code keeps the last item drawn from having a Bisque background.   e.Graphics.FillRectangle(Brushes.White, e.Bounds); } 

         这段代码将无效,屏蔽此处代码,使用e.DrawBackground();即可恢复默认的深蓝色背景。 如果觉得字体和背景色看起来费神,可自定义字体颜色来匹配。

         或者说如果有解决这个问题的方案,希望大家不吝赐教。谢谢

 

 

 更新说明(时间:201307091553)

        更新说明:

              1、已经解决了06月07日字体颜色看起来费神的bug,只需要将0607的代码换成如下代码即可。

   

 e.Graphics.FillRectangle(Brushes.White, e.Bounds); if (e.State == DrawItemState.Selected) { e.Graphics.FillRectangle(Brushes.Bisque, e.Bounds); }

               2、新增支持筛选的功能,只需要重写Keypress事件即可。具体代码如下:

 /// <summary> /// 响应按键 /// </summary> /// <param name="e"></param> protected override void OnKeyPress(KeyPressEventArgs e) { int idx = -1; string toFind; if (!Char.IsControl(e.KeyChar)) { toFind = Text.Substring(0, SelectionStart) + e.KeyChar; idx = FindStringExact(toFind); if (idx == -1) { idx = FindString(toFind); } else { // 精确匹配到字符后,隐藏下拉 DroppedDown = false; } if (idx != -1) { DroppedDown = true;//如果匹配到特殊的字符串,打开下拉 SelectedIndex = idx;//焦点指向对应行 SelectionStart = toFind.Length; SelectionLength = Text.Length - SelectionStart; } else { // 如果没有找到录入的字符串,取消按键响应 e.KeyChar = (char)0; } } if ((e.KeyChar == (char)(Keys.Back)) && (Convert.ToBoolean(SelectionStart))) { toFind = Text.Substring(0, SelectionStart - 1); idx = FindString(toFind); if (idx != -1) { SelectedIndex = idx; SelectionStart = toFind.Length; SelectionLength = Text.Length - SelectionStart; } } e.Handled = true; }

 

           源码已经更新下载链接:  下载源码

 

  • 海报
海报图正在生成中...
赞(0) 打赏
声明:
1、本博客不从事任何主机及服务器租赁业务,不参与任何交易,也绝非中介。博客内容仅记录博主个人感兴趣的服务器测评结果及一些服务器相关的优惠活动,信息均摘自网络或来自服务商主动提供;所以对本博客提及的内容不作直接、间接、法定、约定的保证,博客内容也不具备任何参考价值及引导作用,访问者需自行甄别。
2、访问本博客请务必遵守有关互联网的相关法律、规定与规则;不能利用本博客所提及的内容从事任何违法、违规操作;否则造成的一切后果由访问者自行承担。
3、未成年人及不能独立承担法律责任的个人及群体请勿访问本博客。
4、一旦您访问本博客,即表示您已经知晓并接受了以上声明通告。
文章名称:《玩转控件:重绘ComboBox —— 让ComboBox多列显示 (提供源码下载)》
文章链接:https://www.456zj.com/20473.html
本站资源仅供个人学习交流,请于下载后24小时内删除,不允许用于商业用途,否则法律问题自行承担。

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址