C#大量相同对象winform的性能问题 C# winform中有大量的对象(例如几万个label),这些对象除了颜色、位置不同,其他属性(包括事件)都相同,如何提高程序的速度?(共享内存?)谢谢! 解决方案 » 免费领取超大流量手机卡,每月29元包185G流量+100分钟通话, 中国电信官方发货 传统的winform 中,对于大量的事件,已经使用了将其统一管理到事件数据库(内存中的集合)的机制。在微软的新的界面组件的架构中,则使用了叫做 DependencyProperty 的东西解决属性问题。你可以搜索一下。 是几万个必须都要在一个form里面么,我曾经做过一个类似的,但是没有你这么多,我当时是用了好几个线程去初始化这些label,做的就好像web里面的ajax似的那种,一块一块的加载出来的。至于你说共享内存~~我觉得可以同时用。 我把它说是“事件数据库”可能不太合适,在传统winform中,在顶层component父类中定一个一个叫做Events属性来保存事件集合,然后.net内置的控件大多是使用它管理事件、减少声明事件对象的数量。不过它还是一个简单的List。不过到了DependencyProperty,则就复杂多了,功能也多了,像是一个微型的内存数据库。 lz的意思是说,一个Form上往往有成千上万的控件(大控件也使用了小控件来组合实现),每一个控件中大部分属性其实都只是默认值,而并不修改值。lz认为简单的设计方式很浪费空间。 几万个又怎了了datagridview里面放个几百行数据也不会怎么样,就算这些行里每行都有内嵌控件。 我曾经试图在一个winform中加入1万个Label,结果到了9千多个的时候就出现“创建窗口句柄时出错...”的提示,有什么好的办法能够将超过1万个对象加入到一个winform中? 给个例子。放了一百万个不同的对象,刚启动的时候会慢一些。演示了 - 局部刷新- 滚动- 鼠标经过- 鼠标选择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; } }} form上虽然对象多,但是你不可能一屏给显示完吧每次只处理显示出的一小部分 首先要明白是对象还是控件。几百万个控件甚至几万个控件是肯定做不到的。几百万个对象的话倒只是你的内存问题。控件要注册句柄,接收消息。和对象不同。楼上所说 datagridview里面放个几百行数据也不会怎么样,就算这些行里每行都有内嵌控件。这个是误解。datagridview肯定不是一个单元格或者一行就是一个控件。他只是模仿一些控件或者用一些控件的处理方式。或者等到有焦点时,现用一个控件放在上面。 谢谢大家的热心相助,受益匪浅,我正在看gomoku的代码,也许这就是我要的方法,再等我测试看看,谢谢了! 谢谢大家的热心相助,我现在所使用的方法和“gomoku”所说的类似,完全用画的方法来解决,只是觉得这样比较麻烦,诸如单选、多选、移动等所有的鼠标动作都要自己写,如果是在找不出更好的方法,可能也只能采用这样的方法了。实际上我的确是希望能够像“liuhongzhe”所说的,自己定义一个控件,这个控件占用资源很少,可以加入几万个到form上,速度也能很快,但是我试了如果继承winform下的控件,速度一样很慢,是不是可以采用“完全自定义控件”的方法?如何实现完全自动以控件在winform上的显示(比如这个控件的界面就是一个Rectangle,可以根据是否选中来改变边框的样式),希望能有高人指点,谢谢! 继承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));优化内存和那种局部刷新都不是最好的选择,还是要根据你的实际需求,先想最好的解决办法,然后再去研究。 可不可以使用tab控件或者别的,换个思路应该是最好的。 控件(Control)底层实现是一个Window(Control.Handle就是一个Windows句柄)。Window属于GDI资源。在Windows操作系统中,GDI资源是有限制的,所以不能无限制的创建控件(创建十几万个Window是不可能的)。用控件来做不是不可以,但是也要“重复利用”有限的控件。也就是利用一个好的数据结构,用百十个控件来显示当前的可视对象。 在大家的帮助下,经过了这么多天的测试,我觉得在一个form中加入几万个控件的方法似乎不太可行,即使是自定义控件,好像也不能加到几万个,所以目前我还是采用自己绘制图形的方法来实现类似功能,虽然程序写的麻烦一些,但是性能基本能够满足需要,大家的帮助,让我学到了不少东西,先结贴,有空时,我再研究完全自定义控件的方法。 窗体怎么传值 谁了解ColorMatrix啊 查询IP数据库的问题。。帮顶有分! 在c#中如何进行毫秒级的计算 要用什么样的函数!! 用.net2005开发的winform的系统,菜单控件用Alt或F10都可以使焦点移到菜单上,假如我只想用Alt,不想用F10来控制怎么办呢? 这样的报表的例子怎么用阿? 求directX书籍 c#或c++版 如何让IE或WebBrowser窗口内自动打开word文件? 在petshop中,petshop.snk文件是什么用处的? 怪了,我的SDK找不到ILDasm.EXE? 如何正则判断是否匹配? 又一个正则问题
lz的意思是说,一个Form上往往有成千上万的控件(大控件也使用了小控件来组合实现),每一个控件中大部分属性其实都只是默认值,而并不修改值。lz认为简单的设计方式很浪费空间。
演示了
- 局部刷新
- 滚动
- 鼠标经过
- 鼠标选择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;
}
}
}
几百万个对象的话倒只是你的内存问题。
控件要注册句柄,接收消息。和对象不同。楼上所说 datagridview里面放个几百行数据也不会怎么样,就算这些行里每行都有内嵌控件。
这个是误解。datagridview肯定不是一个单元格或者一行就是一个控件。他只是模仿一些控件或者用一些控件的处理方式。或者等到有焦点时,现用一个控件放在上面。
实际上我的确是希望能够像“liuhongzhe”所说的,自己定义一个控件,这个控件占用资源很少,可以加入几万个到form上,速度也能很快,但是我试了如果继承winform下的控件,速度一样很慢,是不是可以采用“完全自定义控件”的方法?如何实现完全自动以控件在winform上的显示(比如这个控件的界面就是一个Rectangle,可以根据是否选中来改变边框的样式),希望能有高人指点,谢谢!
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));优化内存和那种局部刷新都不是最好的选择,还是要根据你的实际需求,先想最好的解决办法,然后再去研究。
Window属于GDI资源。在Windows操作系统中,GDI资源是有限制的,所以不能无限制的创建控件(创建十几万个Window是不可能的)。用控件来做不是不可以,但是也要“重复利用”有限的控件。也就是利用一个好的数据结构,用百十个控件来显示当前的可视对象。