最近在维护一个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. sqlserver 2012 中的 sysobjects

    sysobjects 表  在数据库内创建的每个对象(约束.默认值.日志.规则.存储过程等)在表中占一行

  2. handy源码阅读(四):Channel类

    通道,封装了可以进行epoll的一个fd. struct Channel: private noncopyable { Channel(EventBase* base, int fd, int eve ...

  3. 如何解决DEDE织梦友情链接字数限制与链接个数限制的问题?

    如何解决DEDE织梦友情链接字数限制与链接个数限制的问题!织梦网站非常适合网站搭建以及网站优化,而友情链接是做优化必不可少的模块,我们经常搭建织梦网站发现织梦系统的友情链接模板有时候会限制字数不显示以 ...

  4. Flink Forward Asia 2019 - 总结和展望(附PPT下载链接)

    11 月 28 - 30 日,北京迎来了入冬以来的第一场雪,2019 Flink Forward Asia(FFA)也在初雪的召唤下顺利拉开帷幕.尽管天气寒冷,FFA 实际到会人次超过 2000,同比 ...

  5. 旧题再做【bzoj2287】【[pojchallenge]消失之物】分治背包

    (上不了p站我要死了) 今天听了 doggu神 讲了这道题的另一种做法,真是脑洞大开.眼界大开.虽然复杂度比黄学长的要大一点,但不总结一下简直对不起这神思路. 方法1:黄学长的做法(点这里) Desc ...

  6. Qt中图元对象的多重集成

    在继承自定义QGraphicsItem图元对象时,有时需要用到信号/槽机制,由于QGraphicsItem非QObject的子类 所以需要多重继承QObject,有一点需要特别注意:就是继承的顺序,一 ...

  7. bootstrap select下拉框模糊搜索和动态绑定数据解决方法

    此方法适合后台一次性返回所有数据好了废话不多说直接上代码: <!DOCTYPE html><html><head> <title>Bootstrap-s ...

  8. Cannot read property 'type' of undefined ....

    一直解决不了,弄了半天是 jquery 版本太低,换一个版本就ok.

  9. ContentProvider,ContentResolver和ContentObserver 使用

    1 ContentProvider内容提供者 四大组件之一,实现不同程序实现数据的共享.联系人应用就使用了ContentProvider,比如你在自己的应用可以读取和修改联系人的数据(获得相应权限). ...

  10. 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_05 IO字符流_5_flush方法和close方法的区别

    flush之后,还可以继续使用流写文件