Fork me on GitHub
让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;
复制代码

 

       效果图如下:

    

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

 

  获取数据

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

 

复制代码
            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个半小时,边写还边组织语言,不知道说清楚没有,如果对大家有帮助,将请点击下面的推荐吧! 千万别手软~

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

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

 

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

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

先晒一下代码:

两个简单的类:

   1:      /// <summary>
   2:      /// 消费者
   3:      /// </summary>
   4:      public class Consumer
   5:      {
   6:          public int CId { get; set; }
   7:          public string CName { get; set; }
   8:          public List<Order> Orders { get; set; }
   9:      }
  10:   
  11:      /// <summary>
  12:      /// 订单
  13:      /// </summary>
  14:      public class Order
  15:      {
  16:          public int OrderNo { get; set; }
  17:          public DateTime OrderDate { get; set; }
  18:          public decimal TotalMoney { get; set; }
  19:          public int CId { get; set; }
  20:   
  21:          public Consumer Consumer { get; set; }
  22:      }

 

 

映射配置:

   1:      public class ConsumerConfiguration : EntityTypeConfiguration<Consumer>
   2:      {
   3:          public ConsumerConfiguration()
   4:          {
   5:              HasKey(t => t.CId).Property(t => t.CId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
   6:              Property(t => t.CName).IsRequired().HasMaxLength(50);
   7:          }
   8:      }
   9:   
  10:      public class OrderConfiguration : EntityTypeConfiguration<Order>
  11:      {
  12:          public OrderConfiguration()
  13:          {
  14:              HasKey(t => t.OrderNo);
  15:              HasRequired(t => t.Consumer).WithMany(t => t.Orders).HasForeignKey(t => t.CId);
  16:          }
  17:      }

 

Context:

   1:      public class TestContext : DbContext
   2:      {
   3:          public DbSet<Consumer> Consumers { get; set; }
   4:          public DbSet<Order> Orders { get; set; }
   5:   
   6:          protected override void OnModelCreating(DbModelBuilder modelBuilder)
   7:          {
   8:              modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
   9:   
  10:              modelBuilder.Configurations.Add(new ConsumerConfiguration());
  11:              modelBuilder.Configurations.Add(new OrderConfiguration());
  12:   
  13:              base.OnModelCreating(modelBuilder);
  14:          }
  15:      }

测试代码:

   1:          static void Main(string[] args)
   2:          {
   3:              using (var ctx = new TestContext())
   4:              {
   5:                  ctx.Consumers.Add(new Consumer()
   6:                  {
   7:                      CName = "张三"
   8:                  });
   9:                  ctx.SaveChanges();
  10:   
  11:                  Stopwatch sw = new Stopwatch();
  12:                  sw.Start();
  13:                  Console.WriteLine("订单开始:\n");
  14:   
  15:                  for (int outer = 0; outer < 20000; outer++)
  16:                  {
  17:                      ctx.Orders.Add(new Order()
  18:                            {
  19:                                OrderDate = DateTime.Now,
  20:                                TotalMoney = 100,
  21:                                CId = 1,
  22:                            });
  23:                  }
  24:                  ctx.SaveChanges();
  25:                  sw.Stop();
  26:                  Console.WriteLine(sw.Elapsed.Minutes + "分" + sw.Elapsed.Seconds + "秒" + sw.Elapsed.Milliseconds + "毫秒");
  27:              }
  28:          }

 

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

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

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

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

   1:  ctx.Configuration.AutoDetectChangesEnabled = false;

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

QQ截图20130607002820

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

   1:                  List<Order> orderList = new List<Order>();
   2:                  for (int outer = 0; outer < 20000; outer++)
   3:                  {
   4:                      orderList.Add(new Order()
   5:                             {
   6:                                 OrderDate = DateTime.Now,
   7:                                 TotalMoney = 100,
   8:                                 CId = 1
   9:                             });
  10:                  }
  11:                  ctx.Orders.AddRange(orderList);
  12:                  ctx.SaveChanges();

QQ截图20130607004003

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

QQ截图20130607004407

QQ截图20130607004652

从上面两幅图中可以看到,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
posted on 2013-06-07 09:55  HackerVirus  阅读(357)  评论(0编辑  收藏  举报