winform程序,大量Label的Text修改,UI更新跟不上如何解决? winformui更新多线程大量控件 解决方案 » 免费领取超大流量手机卡,每月29元包185G流量+100分钟通话, 中国电信官方发货 label是显示给用户看的50ms刷新一次有什么意义么?即使数据在50ms之内有变化,你确实实时的显示出来了,用户能看的过来么?改成500ms完全没有任何区别的. 或者你在赋值之前先判断一下if(lable1.text!=value){lable1.text=value;} 我也知道50ms更新一次,人根本看不清楚。但是用户表明这个是硬需求,可以看到树枝的线性波动情况现实情况是,需求没办法改变,就只能自己想办法解决用列表控件不行么?= =用300个label想当蛋疼。。如果是listview 或者 datagridview的话300条数据完全没有问题。 数据来源是单片机,单片机数据来源是采集器,采集器是50ms采集一次工作环境数据然后发送。我也不知道这50ms对于观察有什么意义,1秒钟20帧看电影啊但是换成数字能看得过来么。数据的话,95%每次都会有变化,所以全部更新和比较是否有变化再更新,没太大区别 说一下软件的界面情况:一个form均分为20块区域,每块上有15个label控件。每一块区域对应一台采集器采集的数据。程序就是要把这20台采集器的数据更新到界面上。采集器和程序之间通过RJ45连接,Form做socket的server,采集器是client。通讯、数据解包分析什么的,几乎不占时间,时间全部都耗在了更新UI上 可以试试多开几个线程吧,每个线程依次对主窗体线程部分控件进行刷新,同时只有1个线程启动,其他都是阻塞状态,这样应该能达到伪全部刷新的效果吧。PS:50ms要求肯定太BT了 就是一般的socket 一对多server的写法。监听线程获取到新的连接,就新开一个线程用while(true)去获取通讯数据,然后解包,更新UI。 仅有部分label的数据可以看到变化,绝大多数的都不动(实际上数值是有变化的),能变化的Label是随机的,不一定是哪些,有可能这几秒是某些,下几秒是另一些。但是数据通讯正常,数据并没有丢失,将数据连断开后,会发现所有的label都会被更新到最新的数据。如果降低这个50ms的要求,目前测试出来是400ms,那么可以到所有的label都顺利的同时发生变化。 。HTML不是问题,问题是:HTML如何做socket通讯? 试了一下,好像没有问题public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { CreateLabel(); } Hashtable ht = new Hashtable(); int Seed = 0; int dis = 5; bool auto = false; int row = 10; int col = 30; void CreateLabel() { for (int i = 0; i < col; i++) { for (int j = 0; j < row; j++) { Label lb = new Label(); lb.Name = "lb" + i.ToString() + j.ToString(); lb.Width = 30; lb.Height = 15; lb.Text = "lbl"; lb.Location = new Point(lb.Width * i + dis, lb.Height * j + dis); lb.Parent = panel1; lb.Show(); ht.Add(lb.Name, lb); } } } int[,] GetValues() { int[,] vals = new int[col, row]; for (int i = 0; i < col; i++) { for (int j = 0; j < row; j++) { long tick = DateTime.Now.Ticks; Random rd = new Random(Seed); vals[i, j] = rd.Next(1, 100); Seed++; while (Seed == int.MaxValue) { Seed = 0; } } } return vals; } void Refresh() { int[,] vals = new int[col, row]; vals = GetValues(); for (int i = 0; i < col; i++) { for (int j = 0; j < row; j++) { Update("lb" + i.ToString() + j.ToString(), vals[i, j]); } } } delegate void delUpdate(string name, int val); void Update(string name, int val) { Label lb = ht[name] as Label; if (lb.InvokeRequired) { delUpdate du = new delUpdate(Update); lb.Invoke(du, new object[] { name,val}); } else { lb.Text = val.ToString(); } } void AutoRefresh() { while (auto) { Refresh(); Thread.Sleep(50); } } private void btnManaul_Click(object sender, EventArgs e) { Refresh(); } private void btnStart_Click(object sender, EventArgs e) { Thread thd = new Thread(new ThreadStart(AutoRefresh)); auto = true; thd.Start(); } private void btnEnd_Click(object sender, EventArgs e) { auto = false; } } 明白了.目测是加锁加的有问题.导致某个线程在操作UI的时候,其他线程无法将数据更新到UI. 另外,你的这个多线程目测根本不需要加锁.多线程将数据写入同一个全局数组中然后主线程(可以是timer)就循环到数组中取数据显示就好了.或者要加锁也应该是每个线程使用不同的锁(因为每个线程其实是对数组中的不同元素在进行更新)不要都统一使用一个锁,会造成一个线程在写入数据的时候,其他线程无法写入对应的其他数据 昨天最后的尝试就是这样,多线程,将数据写入一个全局的数组,主线程新开一个Thread.Timer,100ms刷新一次这些数据到UI。这个Timer里第一次尝试使用了个一个for,来循环20块区域,后来修改为逐条语句写出来,实测均无改善。要想达到“流畅”的感觉,仍然需要400ms左右。 用您的代码测试了下,的确没有问题!奇怪的是,按我的方法测试了下,300个Label更新也没问题。后来琢磨了下,貌似问题出在UI上!测试的代码,ui很简单,纯净的背景,什么都没有,问题是,真实程序的UI不能说花哨,但是每一个区块(总共20个区块,布局、用色相同)都有不同的背景颜色对数据进行了区分,这时候,效率就下来!用你的代码,panel上加了张背景图上去,立刻挂了。 我勒个去啊!!!!!问题找到了,果然和UI有关系!17楼同学很热心的发了测试代码,我测试了下果然300个Label的更新不算事儿,于是想到测试界面没有实际界面那么复杂,于是加了张背景图,顺手把label的backgroundcolor指定为:Color.Transparent,于是,程序果然挂了!!!立马把Transparent注释掉,又正常了!看来Transparent相当耗费系统资源。准备找美工改Ui去,放弃渐变色等背景,全部改为纯色块,这样label就可以指定color而不用Transparent了。表示下无语。。感谢17楼同学提供测试代码,看来碰到问题还是要简化后测试,很快就定位到援引了。写成需时间长了,人变懒了感谢其余同学的帮助。结帖 找到问题了。Color.Transparent我勒个去。。严重影响性能啊 找到问题了。Color.Transparent我勒个去。。严重影响性能啊呵呵,解决了就好. 不应该,我做过一个工艺图的展示实时更新的程序,也遇到过类似的问题,好像重写OnPaint方法,使用多线程局部刷新数据,背景都是金属色的背景图,大小也有几十个,更新数据的Label也有近百个吧,都没有问题的 可否借鉴下How to do? 那么多lable为什么不能弄成关联性的 然后用一个控件自己绘制 一次刷新一批 每次刷新一次底图 然后绘制上数据就行了撒. 50ms刷新1024*800的屏幕应该可以的 每15个label为一组,是一个相同的数据源。不知道您说得自己绘制怎么做?另外,分辨率是1920*1080 意思用17楼代码已经可以了吗?如果还不行,试试下面三个函数呢 this.SuspendLayout(); this.ResumeLayout(false); this.PerformLayout();这三个函数或许对你有帮助。申明:我没有测试过 不要用label作输出直接在onpaint里面自己把所有数据画出来 你所需要做的,就是用两个数组一个保存数据、另一个保存输出坐标在OnPaint里面,循环坐标输出数据C#具体操作我不知道但是有个API函数(SetBkMode)可以在输出字符前,把整个绘图场景的输出字符背景设透明只需设置一次就行了50ms,足够画背景+300次字符输出了 CPaintDC dc(this); CRect rcClt; GetClientRect(&rcClt); int cx = rcClt.Width(); int cy = rcClt.Height();// 使用双缓冲绘图,避免画面闪烁 CBitmap bmp; bmp.CreateCompatibleBitmap(&dc, cx, cy); CDC memDC; memDC.CreateCompatibleDC(&dc); memDC.SelectObject(&bmp); CFont font; font.CreateFont(-12, 0, 0, 0, FW_NORMAL, 0, 0, 0, 0, 0, 0, 0, 0, L"宋体"); memDC.SelectObject(&font); int nFontWidth = cx / 10; memDC.SetTextColor(RGB(0, 0, 255)); // 设置文字颜色 memDC.SetBkMode(TRANSPARENT); // 设置输出文字背景透明 DWORD dwTimeStart = ::GetTickCount(); for (int i = 0; i < 1000; i++) { memDC.FillSolidRect(&rcClt, RGB(255, 0, 0)); CRect rcText(-nFontWidth, 0, 0, 12); for (int j = 0; j < 300; j++) { if ((j % 15) == 0) { rcText.top = 0; rcText.bottom = 12; rcText.OffsetRect(nFontWidth, 0); } TCHAR szText[32]; ::wsprintf(szText, L"Test%d", j + 1); memDC.DrawText(szText, -1, &rcText, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); rcText.OffsetRect(0, 12); } } TRACE1("Used time: %d\n", ::GetTickCount() - dwTimeStart); dc.BitBlt(0, 0, cx, cy, &memDC, 0, 0, SRCCOPY);这是刚写的MFC代码循环1000次只用了4571毫秒除去每次计算坐标的代码,还可以再节省一点代码里我没有用背景图,只是简单的画了个背景色但是按照我以前使用的经验,画一副1080P的图片用时也不会很久 每15个label为一组,是一个相同的数据源。不知道您说得自己绘制怎么做?另外,分辨率是1920*1080一次把一组的数据自己按坐标绘制上去. 毕竟你每次都要全部刷新这样节省了大量的绘制消息和背景的刷新 额。vs2010里 this.后面提示不了你说的这三个,是不是要引用什么? 额。vs2010里 this.后面提示不了你说的这三个,是不是要引用什么?我可以点出来呢你随便建一个winform,在design.cs里面都有的 Label显示消息在少量的时候适合,我最近也在做一些上位机的东西,面对众多label串口都已经关闭了页面还在刷新。由于我们只是要显示一句话或者一个值,而Label本身封装了很多属性在里面,而且具体是什么也不甚了解。那么简单原则,自己写一个Control来替代label即可。我是这样解决的public sealed partial class SuperLabel : Control { public SuperLabel() { this.DoubleBuffered = true; } protected override void OnPaintBackground(PaintEventArgs e) { if (this.DesignMode) { base.OnPaintBackground(e); } } protected override void OnTextChanged(EventArgs e) { base.OnTextChanged(e); Refresh(); } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); StringFormat format = new StringFormat {LineAlignment = StringAlignment.Center}; e.Graphics.FillRectangle(new SolidBrush(BackColor), this.ClientRectangle); e.Graphics.DrawString(this.Text, this.Font,new SolidBrush(ForeColor), this.ClientRectangle, format); } } 救急啊!!!!!c#调用oracle存储过程 Sql 空白如何 替换? 大家有没有DataGridView的一些资料呀 winform方面的 我怎么样知道我的鼠标在这个圆的区域 关于C#(Web)中的ListBox的问题 求助!!帮帮我啊 Xml问题 主窗体listview控件选中 在线等待关于这样的网页 怎么动态改变IIS中默认WEB站点的IP地址? 为什么用iframe去加载水晶报表 点击过水晶报表页面之后再点击其它页面 iframe的main主页面会另起一页? 字符串解析时内存溢出,请仙人指点!
50ms刷新一次有什么意义么?
即使数据在50ms之内有变化,你确实实时的显示出来了,用户能看的过来么?
改成500ms完全没有任何区别的.
if(lable1.text!=value)
{
lable1.text=value;
}
用列表控件不行么?= =用300个label想当蛋疼。。如果是listview 或者 datagridview的话300条数据完全没有问题。
一个form均分为20块区域,每块上有15个label控件。每一块区域对应一台采集器采集的数据。程序就是要把这20台采集器的数据更新到界面上。采集器和程序之间通过RJ45连接,Form做socket的server,采集器是client。通讯、数据解包分析什么的,几乎不占时间,时间全部都耗在了更新UI上
PS:50ms要求肯定太BT了
就是一般的socket 一对多server的写法。
监听线程获取到新的连接,就新开一个线程用while(true)去获取通讯数据,然后解包,更新UI。
仅有部分label的数据可以看到变化,绝大多数的都不动(实际上数值是有变化的),能变化的Label是随机的,不一定是哪些,有可能这几秒是某些,下几秒是另一些。但是数据通讯正常,数据并没有丢失,将数据连断开后,会发现所有的label都会被更新到最新的数据。如果降低这个50ms的要求,目前测试出来是400ms,那么可以到所有的label都顺利的同时发生变化。
。HTML不是问题,问题是:HTML如何做socket通讯?
{
public Form1()
{
InitializeComponent();
} private void Form1_Load(object sender, EventArgs e)
{
CreateLabel();
} Hashtable ht = new Hashtable();
int Seed = 0;
int dis = 5;
bool auto = false;
int row = 10;
int col = 30; void CreateLabel()
{
for (int i = 0; i < col; i++)
{
for (int j = 0; j < row; j++)
{
Label lb = new Label();
lb.Name = "lb" + i.ToString() + j.ToString();
lb.Width = 30;
lb.Height = 15;
lb.Text = "lbl";
lb.Location = new Point(lb.Width * i + dis, lb.Height * j + dis);
lb.Parent = panel1;
lb.Show(); ht.Add(lb.Name, lb);
}
}
}
int[,] GetValues()
{
int[,] vals = new int[col, row]; for (int i = 0; i < col; i++)
{
for (int j = 0; j < row; j++)
{
long tick = DateTime.Now.Ticks;
Random rd = new Random(Seed);
vals[i, j] = rd.Next(1, 100); Seed++;
while (Seed == int.MaxValue)
{
Seed = 0;
}
}
} return vals;
} void Refresh()
{
int[,] vals = new int[col, row];
vals = GetValues(); for (int i = 0; i < col; i++)
{
for (int j = 0; j < row; j++)
{
Update("lb" + i.ToString() + j.ToString(), vals[i, j]);
}
}
} delegate void delUpdate(string name, int val);
void Update(string name, int val)
{
Label lb = ht[name] as Label;
if (lb.InvokeRequired)
{
delUpdate du = new delUpdate(Update);
lb.Invoke(du, new object[] { name,val});
}
else
{
lb.Text = val.ToString();
}
}
void AutoRefresh()
{
while (auto)
{
Refresh();
Thread.Sleep(50);
}
}
private void btnManaul_Click(object sender, EventArgs e)
{
Refresh();
} private void btnStart_Click(object sender, EventArgs e)
{
Thread thd = new Thread(new ThreadStart(AutoRefresh));
auto = true;
thd.Start();
} private void btnEnd_Click(object sender, EventArgs e)
{
auto = false;
}
}
目测是加锁加的有问题.
导致某个线程在操作UI的时候,其他线程无法将数据更新到UI.
多线程将数据写入同一个全局数组中
然后主线程(可以是timer)就循环到数组中取数据显示就好了.或者要加锁也应该是每个线程使用不同的锁(因为每个线程其实是对数组中的不同元素在进行更新)
不要都统一使用一个锁,会造成一个线程在写入数据的时候,其他线程无法写入对应的其他数据
后来琢磨了下,貌似问题出在UI上!测试的代码,ui很简单,纯净的背景,什么都没有,问题是,真实程序的UI不能说花哨,但是每一个区块(总共20个区块,布局、用色相同)都有不同的背景颜色对数据进行了区分,这时候,效率就下来!用你的代码,panel上加了张背景图上去,立刻挂了。
感谢17楼同学提供测试代码,看来碰到问题还是要简化后测试,很快就定位到援引了。写成需时间长了,人变懒了感谢其余同学的帮助。结帖
找到问题了。Color.Transparent我勒个去。。严重影响性能啊
找到问题了。Color.Transparent我勒个去。。严重影响性能啊
呵呵,解决了就好.
可否借鉴下How to do?
每15个label为一组,是一个相同的数据源。不知道您说得自己绘制怎么做?另外,分辨率是1920*1080
如果还不行,试试下面三个函数呢
this.SuspendLayout();
this.ResumeLayout(false);
this.PerformLayout();
这三个函数或许对你有帮助。
申明:我没有测试过
直接在onpaint里面自己把所有数据画出来
一个保存数据、另一个保存输出坐标
在OnPaint里面,循环坐标输出数据C#具体操作我不知道
但是有个API函数(SetBkMode)可以在输出字符前,把整个绘图场景的输出字符背景设透明
只需设置一次就行了50ms,足够画背景+300次字符输出了
GetClientRect(&rcClt); int cx = rcClt.Width();
int cy = rcClt.Height();// 使用双缓冲绘图,避免画面闪烁
CBitmap bmp;
bmp.CreateCompatibleBitmap(&dc, cx, cy);
CDC memDC;
memDC.CreateCompatibleDC(&dc);
memDC.SelectObject(&bmp); CFont font;
font.CreateFont(-12, 0, 0, 0, FW_NORMAL, 0, 0, 0, 0, 0, 0, 0, 0, L"宋体");
memDC.SelectObject(&font); int nFontWidth = cx / 10; memDC.SetTextColor(RGB(0, 0, 255)); // 设置文字颜色
memDC.SetBkMode(TRANSPARENT); // 设置输出文字背景透明 DWORD dwTimeStart = ::GetTickCount(); for (int i = 0; i < 1000; i++)
{
memDC.FillSolidRect(&rcClt, RGB(255, 0, 0)); CRect rcText(-nFontWidth, 0, 0, 12);
for (int j = 0; j < 300; j++)
{ if ((j % 15) == 0)
{
rcText.top = 0;
rcText.bottom = 12;
rcText.OffsetRect(nFontWidth, 0);
} TCHAR szText[32];
::wsprintf(szText, L"Test%d", j + 1); memDC.DrawText(szText, -1, &rcText, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
rcText.OffsetRect(0, 12);
}
} TRACE1("Used time: %d\n", ::GetTickCount() - dwTimeStart); dc.BitBlt(0, 0, cx, cy, &memDC, 0, 0, SRCCOPY);这是刚写的MFC代码
循环1000次只用了4571毫秒
除去每次计算坐标的代码,还可以再节省一点
代码里我没有用背景图,只是简单的画了个背景色
但是按照我以前使用的经验,画一副1080P的图片用时也不会很久
每15个label为一组,是一个相同的数据源。不知道您说得自己绘制怎么做?另外,分辨率是1920*1080一次把一组的数据自己按坐标绘制上去. 毕竟你每次都要全部刷新这样节省了大量的绘制消息和背景的刷新
额。vs2010里 this.后面提示不了你说的这三个,是不是要引用什么?
额。vs2010里 this.后面提示不了你说的这三个,是不是要引用什么?我可以点出来呢你随便建一个winform,在design.cs里面都有的
由于我们只是要显示一句话或者一个值,而Label本身封装了很多属性在里面,而且具体是什么也不甚了解。那么简单原则,自己写一个Control来替代label即可。
我是这样解决的public sealed partial class SuperLabel : Control
{
public SuperLabel()
{
this.DoubleBuffered = true;
} protected override void OnPaintBackground(PaintEventArgs e)
{
if (this.DesignMode)
{
base.OnPaintBackground(e);
}
} protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
Refresh();
} protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
StringFormat format = new StringFormat {LineAlignment = StringAlignment.Center};
e.Graphics.FillRectangle(new SolidBrush(BackColor), this.ClientRectangle);
e.Graphics.DrawString(this.Text, this.Font,new SolidBrush(ForeColor), this.ClientRectangle, format);
}
}