C# winform中有大量的对象(例如几万个label),这些对象除了颜色、位置不同,其他属性(包括事件)都相同,如何提高程序的速度?(共享内存?)谢谢!

解决方案 »

  1.   

    传统的winform 中,对于大量的事件,已经使用了将其统一管理到事件数据库(内存中的集合)的机制。在微软的新的界面组件的架构中,则使用了叫做 DependencyProperty 的东西解决属性问题。你可以搜索一下。
      

  2.   

    是几万个必须都要在一个form里面么,我曾经做过一个类似的,但是没有你这么多,我当时是用了好几个线程去初始化这些label,做的就好像web里面的ajax似的那种,一块一块的加载出来的。至于你说共享内存~~我觉得可以同时用。
      

  3.   

    我把它说是“事件数据库”可能不太合适,在传统winform中,在顶层component父类中定一个一个叫做Events属性来保存事件集合,然后.net内置的控件大多是使用它管理事件、减少声明事件对象的数量。不过它还是一个简单的List。不过到了DependencyProperty,则就复杂多了,功能也多了,像是一个微型的内存数据库。
      

  4.   


    lz的意思是说,一个Form上往往有成千上万的控件(大控件也使用了小控件来组合实现),每一个控件中大部分属性其实都只是默认值,而并不修改值。lz认为简单的设计方式很浪费空间。
      

  5.   

    几万个又怎了了datagridview里面放个几百行数据也不会怎么样,就算这些行里每行都有内嵌控件。
      

  6.   

    我曾经试图在一个winform中加入1万个Label,结果到了9千多个的时候就出现“创建窗口句柄时出错...”的提示,有什么好的办法能够将超过1万个对象加入到一个winform中?
      

  7.   

    给个例子。放了一百万个不同的对象,刚启动的时候会慢一些。
    演示了 
    - 局部刷新
    - 滚动
    - 鼠标经过
    - 鼠标选择public partial class Form1 : Form
    {
        VScrollBar vScrollBar = new VScrollBar();
        List<MyElement> elements = new List<MyElement>();
        MyElement SelectedElement = null;
        public Form1()
        {
            // 准备一百万个对象。分配不同的位置,颜色和位置
            KnownColor[] colors = Enum.GetValues(typeof(KnownColor)) as KnownColor[];
            for (int i = 0; i < 1000*1000; i++)
            {
                MyElement element = new MyElement()
                {
                    Bound = new Rectangle( (i % 5) * 80, (i / 5) * 80, 50,50),
                    Color = Color.FromKnownColor( colors[ i % colors.Length ] ),
                    Name = ((char)('A' + i % 26)).ToString() + i,
                };
                elements.Add(element);
            }        // 添加垂直滚动条,并在滚动时要求更新
            this.DoubleBuffered = true;
            vScrollBar.Dock = System.Windows.Forms.DockStyle.Right;
            vScrollBar.Maximum = elements[elements.Count - 1].Location.Y + 80 - this.ClientRectangle.Height;
            vScrollBar.ValueChanged += delegate { this.Invalidate(); };
            this.Controls.Add(vScrollBar);
        }    private int GetWhereAbout(Point location)
        {
            // 加速寻找,拿出与当前窗口大概相关的对象起始下标。正规算法可以用四分树等等
            int where = location.Y / 80 * 5;
            return Math.Max(0, where - 10);
        }    protected override void OnPaintBackground(PaintEventArgs e){}    protected override void OnPaint(PaintEventArgs e)
        {
            // 更新需要重画的地方
            e.Graphics.TranslateTransform(0, -vScrollBar.Value);
            Rectangle clip = Rectangle.Ceiling(e.Graphics.ClipBounds);
            int whereAbout = GetWhereAbout(clip.Location);
            for (int i = whereAbout; i < elements.Count && i < whereAbout + 100; i++)
            {
                if (clip.IntersectsWith(elements[i].Bound))
                {
                    elements[i].DrawTo(e.Graphics);
                }
            }
        }    protected override void OnMouseMove(MouseEventArgs e)
        {
            // 更新鼠标经过的对象
            Point cursor = new Point(e.X, e.Y + this.vScrollBar.Value);
            int whereAbout = GetWhereAbout(cursor);
            for (int i = whereAbout; i < elements.Count && i < whereAbout + 100; i++)
            {
                if (elements[i].HoverTest(cursor))
                {
                    this.Invalidate();
                    return;
                }
            }
        }    protected override void OnMouseDown(MouseEventArgs e)
        {
            // 更新选定的对象
            Point cursor = new Point(e.X, e.Y + this.vScrollBar.Value);
            if( this.SelectedElement != null)
            {
                this.SelectedElement.Selected = false;
                this.SelectedElement = null;
            }        int whereAbout = GetWhereAbout(cursor);
            for (int i = whereAbout; i < elements.Count && i < whereAbout + 100; i++)
            {
                if (elements[i].Bound.Contains(cursor))
                {
                    this.SelectedElement = elements[i];
                    this.SelectedElement.Selected = true;
                    this.Invalidate();
                    return;
                }
            }
        }
    }public class MyElement
    {
        public string Name { get; set; }
        public Rectangle Bound { get; set; }
        public Point Location { get { return this.Bound.Location; } }
        public Color Color { get; set; }
        public bool Selected { get; set; }
        public bool Hovered { get; set; }    public bool HoverTest(Point hit)
        {
            bool oldValue = this.Hovered;
            this.Hovered = this.Bound.Contains(hit);
            return this.Hovered != oldValue;
        }    public void DrawTo(Graphics g)
        {
            using (Brush brush = new SolidBrush(this.Hovered ? ControlPaint.LightLight(this.Color) : this.Color))
            {
                g.FillRectangle(brush, this.Bound);
            }
            {
                g.DrawString(this.Name, SystemFonts.MenuFont, Brushes.Black, this.Bound, MyElement.StringFormat);
            }
            if (this.Selected)
            {
                g.DrawRectangle(Pens.Red, this.Bound);
            }
        }    private static StringFormat stringFormat = null;
        private static StringFormat StringFormat
        {
            get
            {
                if (stringFormat == null)
                {
                    stringFormat = new StringFormat()
                    {
                         Alignment = StringAlignment.Center,
                         LineAlignment = StringAlignment.Center,
                         Trimming = StringTrimming.EllipsisWord,
                    };
                }
                return stringFormat;
            }
        }
    }
      

  8.   

    form上虽然对象多,但是你不可能一屏给显示完吧每次只处理显示出的一小部分
      

  9.   

    首先要明白是对象还是控件。几百万个控件甚至几万个控件是肯定做不到的。
    几百万个对象的话倒只是你的内存问题。
    控件要注册句柄,接收消息。和对象不同。楼上所说 datagridview里面放个几百行数据也不会怎么样,就算这些行里每行都有内嵌控件
    这个是误解。datagridview肯定不是一个单元格或者一行就是一个控件。他只是模仿一些控件或者用一些控件的处理方式。或者等到有焦点时,现用一个控件放在上面。
      

  10.   

    谢谢大家的热心相助,受益匪浅,我正在看gomoku的代码,也许这就是我要的方法,再等我测试看看,谢谢了!
      

  11.   

    谢谢大家的热心相助,我现在所使用的方法和“gomoku”所说的类似,完全用画的方法来解决,只是觉得这样比较麻烦,诸如单选、多选、移动等所有的鼠标动作都要自己写,如果是在找不出更好的方法,可能也只能采用这样的方法了。
    实际上我的确是希望能够像“liuhongzhe”所说的,自己定义一个控件,这个控件占用资源很少,可以加入几万个到form上,速度也能很快,但是我试了如果继承winform下的控件,速度一样很慢,是不是可以采用“完全自定义控件”的方法?如何实现完全自动以控件在winform上的显示(比如这个控件的界面就是一个Rectangle,可以根据是否选中来改变边框的样式),希望能有高人指点,谢谢!
      

  12.   

    继承Control,重写OnPaint方法。用e.Graphics取得Graphics对象,然后用GDI+随便画。
    g.DrawRectangle(new Pen(Color.Red), 0, 0, Width - 2, Height - 2);
    g.DrawString(Text, Font, new SolidBrush(Color.Blue), new RectangleF(0, 0, Width - 2, Height - 2));优化内存和那种局部刷新都不是最好的选择,还是要根据你的实际需求,先想最好的解决办法,然后再去研究。
      

  13.   

    可不可以使用tab控件或者别的,换个思路应该是最好的。
      

  14.   

    控件(Control)底层实现是一个Window(Control.Handle就是一个Windows句柄)。
    Window属于GDI资源。在Windows操作系统中,GDI资源是有限制的,所以不能无限制的创建控件(创建十几万个Window是不可能的)。用控件来做不是不可以,但是也要“重复利用”有限的控件。也就是利用一个好的数据结构,用百十个控件来显示当前的可视对象。
      

  15.   

    在大家的帮助下,经过了这么多天的测试,我觉得在一个form中加入几万个控件的方法似乎不太可行,即使是自定义控件,好像也不能加到几万个,所以目前我还是采用自己绘制图形的方法来实现类似功能,虽然程序写的麻烦一些,但是性能基本能够满足需要,大家的帮助,让我学到了不少东西,先结贴,有空时,我再研究完全自定义控件的方法。