我的应用程序有一个DataGridView对象和一个MousePos类型的List.MousePos是一个自定义类,它包含鼠标X,Y坐标("Point"类型)和此位置的运行计数.我有一个线程(System.Timers.Timer)每秒引发一次事件,检查鼠标位置,添加和/或更新此列表上鼠标位置的计数.
我想有一个类似的运行线程(再次,我认为System.Timers.Timer是一个不错的选择),这将再次引发一次事件一次自动Refresh()DataGridView,以便用户可以看到数据屏幕更新.(就像TaskManager一样.)
不幸的是,调用DataGridView.Refresh()方法会导致VS2005停止执行,并注意到我遇到了跨线程的情况.
如果我理解正确,我现在有3个主题:
主UI线程
MousePos List线程(Timer)
DataGridView刷新线程(计时器)
为了看看我是否可以在主线程上刷新()DataGridView,我在表单中添加了一个名为DataGridView.Refresh()的按钮,但是这个(奇怪地)没有做任何事情.我发现了一个似乎表明如果我设置DataGridView.DataSource = null并返回到我的List的主题,它会刷新数据网格.确实这有效,但只能通过按钮(在主线程上处理).
所以这个问题变成了两个问题:
将DataGridView.DataSource设置为null并返回到我的List是一种可接受的刷新数据网格的方法吗?(对我来说似乎效率低下......)
如何在多线程环境中安全地执行此操作?
这是我到目前为止编写的代码(C#/.Net 2.0)
public partial class Form1 : Form { private static ListmousePositionList = new List (); private static System.Timers.Timer mouseCheck = new System.Timers.Timer(1000); private static System.Timers.Timer refreshWindow = new System.Timers.Timer(1000); public Form1() { InitializeComponent(); mousePositionList.Add(new MousePos()); // ANSWER! Must have at least 1 entry before binding to DataSource dataGridView1.DataSource = mousePositionList; mouseCheck.Elapsed += new System.Timers.ElapsedEventHandler(mouseCheck_Elapsed); mouseCheck.Start(); refreshWindow.Elapsed += new System.Timers.ElapsedEventHandler(refreshWindow_Elapsed); refreshWindow.Start(); } public void mouseCheck_Elapsed(object source, EventArgs e) { Point mPnt = Control.MousePosition; MousePos mPos = mousePositionList.Find(ByPoint(mPnt)); if (mPos == null) { mousePositionList.Add(new MousePos(mPnt)); } else { mPos.Count++; } } public void refreshWindow_Elapsed(object source, EventArgs e) { //dataGridView1.DataSource = null; // Old way //dataGridView1.DataSource = mousePositionList; // Old way dataGridView1.Invalidate(); // <= ANSWER!! } private static Predicate ByPoint(Point pnt) { return delegate(MousePos mPos) { return (mPos.Pnt == pnt); }; } } public class MousePos { private Point position = new Point(); private int count = 1; public Point Pnt { get { return position; } } public int X { get { return position.X; } set { position.X = value; } } public int Y { get { return position.Y; } set { position.Y = value; } } public int Count { get { return count; } set { count = value; } } public MousePos() { } public MousePos(Point mouse) { position = mouse; } }
Grzenio.. 5
您必须像所有其他控件一样更新主UI线程上的网格.请参阅control.Invoke或Control.BeginInvoke.
您必须像所有其他控件一样更新主UI线程上的网格.请参阅control.Invoke或Control.BeginInvoke.
UPDATE!- 我部分找到了"Pro .NET 2.0 Windows窗体和C#中的客户控件"一书中第1部分的答案
我原先以为Refresh()没有做任何事情,我需要调用Invalidate()方法,告诉Windows重新调整我的控件.(通常是马上,但是如果您需要保证现在重新绘制它,那么请立即调用Update()方法.)
dataGridView1.Invalidate();
但是,事实证明,Refresh()方法仅仅是一个别名:
dataGridView1.Invalidate(true); dataGridView1.Update(); // <== forces immediate redraw
我发现的唯一一个问题是,如果dataGridView中没有数据,则无效的数量会刷新控件.我不得不重新分配数据源.然后它在那之后运作良好.但仅限于行数(或列表中的项目) - 如果添加了新项目,dataGridView将不会意识到还有更多行要显示.
因此,似乎在将数据源(List或Table)绑定到Datasource时,dataGridView会对项(行)进行计数,然后在内部进行设置,并且永远不会检查是否删除了新的行/项或行/项.这就是之前重复绑定数据源的原因.
现在要弄清楚如何在不必重新绑定数据源的情况下更新要在dataGridView中显示的行数...有趣,有趣,有趣!:-)
在做了一些挖掘之后,我想我的答案是我的问题的第2部分(又名安全的多线程):
而不是使用System.Timers.Timer,我发现我应该使用System.Windows.Forms.Timer.
发生这样的事件,使得回调中使用的方法自动发生在主线程上.没有跨线程问题!
声明如下:
private static System.Windows.Forms.Timer refreshWindow2; refreshWindow2 = new Timer(); refreshWindow2.Interval = 1000; refreshWindow2.Tick += new EventHandler(refreshWindow2_Tick); refreshWindow2.Start();
方法是这样的:
private void refreshWindow2_Tick(object sender, EventArgs e) { dataGridView1.Invalidate(); }