最近在维护一个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;

效果图如下:

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

  获取数据

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

            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事件,将如上代码添加进去即可。 最终效果图:

  后语

一篇文章写了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;
}
http://www.cnblogs.com/axing/archive/2013/06/07/3123123.html

重绘ComboBox —— 让ComboBox多列显示的更多相关文章

  1. 玩转控件:重绘DEVEXPRESS中DateEdit控件 —— 让DateEdit支持只选择年月 (提供源码下载)

      前言 上一篇博文<玩转控件:重绘ComboBox —— 让ComboBox多列显示>中,根据大家的回馈,ComboBox已经支持筛选了,更新见博文最后最后最后面.   奇葩 这两天遇到 ...

  2. ListBox重绘

    .NET Framework 类库  ListBox.ItemHeight 属性 当 DrawMode 属性设置为 DrawMode.OwnerDrawFixed 时,所有项具有相同的高度.当 Dra ...

  3. 使用重绘项美化WinForm中的控件

    如果你觉得项目中的ComboBox.ListBox或其它的Winforms控件不能满足你的显示要求,包括窗体在内很多控件都支持重绘修改显示样式.下面的示例完成对ComBox数据项的重绘,希望能起到抛砖 ...

  4. 关于DOM的操作以及性能优化问题-重绘重排

     写在前面: 大家都知道DOM的操作很昂贵. 然后贵在什么地方呢? 一.访问DOM元素 二.修改DOM引起的重绘重排 一.访问DOM 像书上的比喻:把DOM和JavaScript(这里指ECMScri ...

  5. 关于repaint(重绘)和reflow( 回流)

    repaint就是重绘,reflow就是回流.repaint主要是针对某一个DOM元素进行的重绘,reflow则是回流,针对整个页面的重排 严重性: 在性能优先的前提下,性能消耗 reflow大于re ...

  6. MFC 滑动条的重绘

    MFC自带的滑动条的样子是这样的. 比较难看,所以需要重绘下,重绘后的样子是这样的. 代码如下: CustomSliderCtr.h #pragma once // CCustomSliderCtr ...

  7. WinForm中重绘TabControl选项卡标题

    最近开发WinForm频繁使用了TabControl控件,这个控件的选项卡没有BackgroundImage这个属性,那么如何为其各个选项卡添加背景图片呢?(这里说的是每个TabPage的头部,也就是 ...

  8. 回流(reflow)与重绘(repaint)

    最近项目排期不紧,于是看了一下之前看了好久也没看明白的chrome调试工具的timeline.但是很遗憾,虽然大概懂了每一项是做什么的,但是用起来并不能得心应手.所以今天的重点不是timeline,而 ...

  9. iOS 视图:重绘与UIScrollView(内容根据iOS编程编写)

    我们继续之前的 Hypnosister 应用,当用户开始触摸的时候,圆形的颜色会改变. 首先,在 JXHypnosisView 头文件中声明一个属性,用来表示圆形的颜色. #import " ...

  10. x01.Weiqi.7: 调整重绘

    GitHub 谁方便谁拍,谁重要拍谁.在这个砖头满天飞的时代,一个好的生态显得尤为重要.  红颜小头发,要的很简单. 也许成绝唱,只因鱼断肠. 姚贝福娃的离去,除感叹人生无常外,活着做点有意义的事情, ...

随机推荐

  1. 【微信小程序】setData的使用以及注意事项

    Page.prototype.setData(Object data, Function callback) setData 函数用于将数据从逻辑层发送到视图层(异步),同时改变对应的 this.da ...

  2. CSS定位机制之浮动定位float

    一.浮动定位实现的效果 二.使用float实现浮动定位 三.使用clear属性清除浮动定位 四.浮动定位的应用(布局) 一.浮动定位实现的效果   (一).块元素(div)在文档流中默认垂直排列,如果 ...

  3. Oulipo【Hash】

    Oulipo Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 45515   Accepted: 18181 Descript ...

  4. 续上文,Unity3D面试ABC

    http://www.unitymanual.com/blog-3573-685.html 最先执行的方法是: 1.(激活时的初始化代码)Awake,2.Start.3.Update[FixUpdat ...

  5. influxDB 1.3 中文文档

    influxDB是一个旨在处理高并发写入和查询负载的时序数据库,它是TICK框架的第二部分,influxdb用于任何包含大量时序数据应用的后台存储,包括Devops监控.应用指标数据.物联网传感器数据 ...

  6. mui.ajax中文乱码

    估计这是个bug: //mui 的 ajax 中文乱码 var url = 'http://api.juheapi.com/japi/toh?key=1f26c107d8864bdfb98202bc3 ...

  7. Cocoapods 版本

    查看当前安装的版本 gem list 卸载版本 gem uninstall cocoapods 安装 gem install cocoapods gem install cocoapods -v 1. ...

  8. Oracle JET(一)Oracle JET介绍

    Oracle JET (Oracle Javascript Extension Toolkit)是一款 Oracle 的 JavaScript 拓展工具包.简单来说 Oracle JET 是一个一堆好 ...

  9. 高通camera基本代码架构【转】

    本文转载自:http://blog.sina.com.cn/s/blog_c0de2be70102vyn1.html 1  camera基本代码架构 高通平台对于camera的代码组织,大体上还是遵循 ...

  10. optistruct如何将多个约束置于一个约束集合中

    建立load_col,卡片设置SPCADD.