您的位置:首页 > 其它

让ComboBox多列显示 (提供源码下载)

2013-06-07 09:55 519 查看

让ComboBox多列显示(提供源码下载)前言

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

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

缘由

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

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

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

publicpartialclassMyComboBox:ComboBox


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

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

///<summary>
///初始化数据源各列的名称
///</summary>
privatevoidInitializeColumns()
{
PropertyDescriptorCollectionpropertyDescriptorCollection=DataManager.GetItemProperties();
columnWidths=newfloat[propertyDescriptorCollection.Count];
columnNames=newstring[propertyDescriptorCollection.Count];

for(inti=0;i<propertyDescriptorCollection.Count;i++)
{
stringname=propertyDescriptorCollection[i].Name;
columnNames[i]=name;
}
}
///<summary>
///显示下拉框的时候出发
///</summary>
///<paramname="e"></param>
protectedoverridevoidOnDropDown(EventArgse)
{
base.OnDropDown(e);
this.DropDownWidth=(int)CalculateTotalWidth();//计算下拉框的总宽度
}

privatefloatCalculateTotalWidth()
{
columnPadding=5;
floattotalWidth=0;
foreach(intwidthincolumnWidths)
{
totalWidth+=(width+columnPadding);
}
//总宽度加上垂直滚动条的宽度
returntotalWidth+SystemInformation.VerticalScrollBarWidth;
}
///<summary>
///获取各列的宽度和项的总宽度
///</summary>
///<paramname="e"></param>
protectedoverridevoidOnMeasureItem(MeasureItemEventArgse)
{
base.OnMeasureItem(e);
if(DesignMode)
{
return;
}
InitializeColumns();
for(inti=0;i<columnNames.Length;i++)
{
stringitem=Convert.ToString(FilterItemOnProperty(Items[e.Index],columnNames[i]));
SizeFsizeF=e.Graphics.MeasureString(item,Font);//返回显示项字符串的大小
columnWidths[i]=Math.Max(columnWidths[i],sizeF.Width);
}
floattotalWidth=CalculateTotalWidth();//计算combobox下拉框项的宽度
e.ItemWidth=(int)totalWidth;//设置下拉框项的宽度
}
///<summary>
///绘制下拉框的内容
///</summary>
///<paramname="e"></param>
protectedoverridevoidOnDrawItem(DrawItemEventArgse)
{
base.OnDrawItem(e);

if(DesignMode)
{
return;
}
RectangleboundsRect=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);
}
intlastRight=0;
using(PenlinePen=newPen(SystemColors.GrayText))
{
using(SolidBrushbrush=newSolidBrush(ForeColor))
{
if(columnNames.Length==0)
{
e.Graphics.DrawString(Convert.ToString(Items[e.Index]),Font,brush,boundsRect);
}
else
{
//循环各列
for(inti=0;i<columnNames.Length;i++)
{
stringitem=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=newSystem.ComponentModel.Container();
this.MyComboBox1=newTTTT.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=newSystem.Drawing.Point(6,22);
this.MyComboBox1.Name="MyComboBox1";
this.MyComboBox1.Size=newSystem.Drawing.Size(144,21);
this.MyComboBox1.TabIndex=0;
this.MyComboBox1.SelectedIndexChanged+=newSystem.EventHandler(this.MyComboBox1_SelectedIndexChanged);


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

DataTabledataTable=newDataTable("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(newString[]{"1","James","张三","james.zhang","长沙"});
dataTable.Rows.Add(newString[]{"2","Mary","李四","mary.xu","山东"});
dataTable.Rows.Add(newString[]{"3","Jack","王五","jack.li","台湾"});
dataTable.Rows.Add(newString[]{"4","joy","赵六","joy.zhou","济南"});
dataTable.Rows.Add(newString[]{"5","jay","钱七","jay.ji","美国"});
dataTable.Rows.Add(newString[]{"6","stephen","康忠鑫","Stephen.Kang","深圳"});
MyComboBox1.DataSource=dataTable;


效果图如下:



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

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

try
{
DataTabledt=MyComboBox1.DataSourceasDataTable;
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(Exceptionex)
{
MessageBox.Show(ex.Message);
}


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



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

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

差点忘记上传源码了:下载

"EntityFramework数据插入性能追踪"读后总结

园友莱布尼茨写了一篇《EntityFramework数据插入性能追踪》的文章,我感觉不错,至少他提出了问题,写了出来,引起了大家的讨论,这就是一个氛围。读完文章+评论,于是我自己也写了个简单的程序试了试。

先晒一下代码:

两个简单的类:

///<summary>

[code]///消费者
///</summary>

publicclassConsumer

{

publicintCId{get;set;}

publicstringCName{get;set;}

publicList<Order>Orders{get;set;}

}

///<summary>

///订单

///</summary>

publicclassOrder

{

publicintOrderNo{get;set;}

publicDateTimeOrderDate{get;set;}

publicdecimalTotalMoney{get;set;}

publicintCId{get;set;}


publicConsumerConsumer{get;set;}

}

[/code]

映射配置:


publicclassConsumerConfiguration:EntityTypeConfiguration<Consumer>

[code]{
publicConsumerConfiguration()

{

HasKey(t=>t.CId).Property(t=>t.CId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

Property(t=>t.CName).IsRequired().HasMaxLength(50);

}

}


publicclassOrderConfiguration:EntityTypeConfiguration<Order>

{

publicOrderConfiguration()

{

HasKey(t=>t.OrderNo);

HasRequired(t=>t.Consumer).WithMany(t=>t.Orders).HasForeignKey(t=>t.CId);

}

}

[/code]

Context:


publicclassTestContext:DbContext

[code]{
publicDbSet<Consumer>Consumers{get;set;}

publicDbSet<Order>Orders{get;set;}


protectedoverridevoidOnModelCreating(DbModelBuildermodelBuilder)

{

modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();


modelBuilder.Configurations.Add(newConsumerConfiguration());

modelBuilder.Configurations.Add(newOrderConfiguration());


base.OnModelCreating(modelBuilder);

}

}

[/code]
测试代码:


staticvoidMain(string[]args)

[code]{
using(varctx=newTestContext())

{

ctx.Consumers.Add(newConsumer()

{

CName="张三"

});

ctx.SaveChanges();


Stopwatchsw=newStopwatch();

sw.Start();

Console.WriteLine("订单开始:\n");


for(intouter=0;outer<20000;outer++)

{

ctx.Orders.Add(newOrder()

{

OrderDate=DateTime.Now,

TotalMoney=100,

CId=1,

});

}

ctx.SaveChanges();

sw.Stop();

Console.WriteLine(sw.Elapsed.Minutes+"分"+sw.Elapsed.Seconds+"秒"+sw.Elapsed.Milliseconds+"毫秒");

}

}

[/code]

上面的代码是最平常的代码了,没有什么可解释的,将内容放到重点上。

运行以上代码的环境是VS2012+SQLSERVER2008R2,机器配置:4G,N年以前的CPU。

运行上面的代码非常的慢,正如莱布尼茨说的,在数据Add到上下文这个阶段比较耗时。出现这个问题的原因是:每次调用ctx.Orders.Add(order)之前,EF都会调用DetectChanges,在StackOverFlow上有解释,地址是:http://stackoverflow.com/questions/9439430/improving-performance-of-initializing-dbset-in-seed,另外在ProgrammingEntityFrameworkDbContext这本书的60也有DetectChange的介绍。

解决上面速度慢的问题的办法就是设置


ctx.Configuration.AutoDetectChangesEnabled=false;


下面来看看禁用以后的执行速度:





另外一个解决办法就是使用DbSet<T>.AddRange方法,这个方法是在6.0beta1中加入的。


List<Order>orderList=newList<Order>();

[code]for(intouter=0;outer<20000;outer++)
{

orderList.Add(newOrder()

{

OrderDate=DateTime.Now,

TotalMoney=100,

CId=1

});

}

ctx.Orders.AddRange(orderList);

ctx.SaveChanges();

[/code]




AddRange方法在System.Data.Entity泛型DbSet类中,下图是我通过Reflector截的图









从上面两幅图中可以看到,Add和AddRange都是添加到_internalSet中,但是如果AutoDetectChangesEnabled设置为true的话,添加任何实体之前都会调用DetectChanges,注意看Remarks中的解释。

测试源码下载地址:http://www.ef-community.com/forum.php?mod=viewthread&tid=437&extra=page%3D1

夜已深!

天亮了,就要高考了,祝福所有的考生!

Technorati标签:EF,EntityFramework,DetectChanges
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐