如何在多线程应用程序中安全地填充数据和Refresh()DataGridView? [英] How do I safely populate with data and Refresh() a DataGridView in a multi-threaded application?

查看:113
本文介绍了如何在多线程应用程序中安全地填充数据和Refresh()DataGridView?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的应用程序具有DataGridView对象和MousePos类型的列表。 MousePos是一个自定义类,可以保存鼠标X,Y坐标(类型Point)和该位置的运行计数。我有一个线程(System.Timers.Timer),每秒钟会引发一次事件,检查鼠标位置,添加和/或更新此列表上鼠标位置的计数。


$ b $我想有一个类似的运行线程(再次,我认为System.Timers.Timer是一个不错的选择),这将再次引发一个事件一秒钟自动刷新()DataGridView,以便用户可以看到屏幕上的数据更新。 (如TaskManager所做)。



不幸的是,调用DataGridView.Refresh()方法会导致VS2005停止执行,并注意到我遇到了跨线程的情况。



如果我理解正确,我现在有3个线程:




  • 主UI线程

  • MousePos列表线程(Timer)

  • DataGridView刷新线程(Timer)



要查看是否可以在主线程上刷新()DataGridView,我将一个按钮添加到称为DataGridView.Refresh()的窗体中,但这个(奇怪的是)没有做任何事情。我发现一个主题似乎表明如果我设置DataGridView.DataSource = null并返回到我的列表,它将刷新datagrid。确实这样做有效,但只能通过按钮(在主线程上处理)。






所以这个问题变成一个两个部分:


  1. 将DataGridView.DataSource设置为null并返回到我的列表可以接受的方式来刷新datagrid? (对我来说似乎效率不高)

  2. 如何在多线程环境中安全地执行此操作?






这是我迄今为止写的代码(C#/ .Net 2.0)

  public partial class Form1:Form 
{
private static List&MousePos> mousePositionList = new List< MousePos>();
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!绑定到DataSource之前必须至少有1个条目
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)
{
点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; //旧方式
//dataGridView1.DataSource = mousePositionList; //旧方式
dataGridView1.Invalidate(); //< = ANSWER !!
}

私有静态谓词< MousePos> ByPoint(Point pnt)
{
返回代理(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; }
}


解决方案

更新! - 部分在Pro .NET 2.0 Windows窗体和C#中的客户控件一书中找出了第1部分的答案



我最初以为, Refresh()没有做任何事情,我需要调用 Invalidate()方法,告诉Windows,我的控制在它的休闲(通常是马上),但是如果您需要保证重新绘制它现在,然后立即调用Update()方法)。

  dataGridView1.Invalidate(); 

但是,事实证明, Refresh()方法只是一个别名为:

  dataGridView1.Invalidate(true); 
dataGridView1.Update(); //< ==强制立即重绘

我发现这个唯一的故障是,如果有dataGridView中没有数据,没有数量的无效将刷新控件。我不得不重新分配数据源。那之后它工作得很好。但是仅限于行数量(或列表中的项目) - 如果添加了新项目,则dataGridView将不会意识到有更多行要显示。



所以看起来当将数据源(List或Table)绑定到Datasource时,dataGridView计算项目(行),然后在内部进行设置,从不检查是否删除了新的行/项目或行/项目。这就是为什么反复重新绑定数据源的原因在于。



现在来弄清楚如何更新在dataGridView中显示的行数,而不必重新绑定数据源...有趣,有趣,有趣! : - )






在做了一些挖掘之后,我想我有答案 part#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();
}


My app has a DataGridView object and a List of type MousePos. MousePos is a custom class that holds mouse X,Y coordinates (of type "Point") and a running count of this position. I have a thread (System.Timers.Timer) that raises an event once every second, checks the mouse position, adds and/or updates the count of the mouse position on this List.

I would like to have a similar running thread (again, I think System.Timers.Timer is a good choice) which would again raise an event once a second to automatically Refresh() the DataGridView so that the user can see the data on the screen update. (like TaskManager does.)

Unfortunately, calling the DataGridView.Refresh() method results in VS2005 stopping execution and noting that I've run into a cross-threading situation.

If I'm understanding correctly, I have 3 threads now:

  • Primary UI thread
  • MousePos List thread (Timer)
  • DataGridView Refresh thread (Timer)

To see if I could Refresh() the DataGridView on the primary thread, I added a button to the form which called DataGridView.Refresh(), but this (strangely) didn't do anything. I found a topic which seemed to indicate that if I set DataGridView.DataSource = null and back to my List, that it would refresh the datagrid. And indeed this worked, but only thru the button (which gets handled on the primary thread.)


So this question has turned into a two-parter:

  1. Is setting DataGridView.DataSource to null and back to my List an acceptable way to refresh the datagrid? (It seems inefficient to me...)
  2. How do I safely do this in a multi-threaded environment?


Here's the code I've written so far (C#/.Net 2.0)

public partial class Form1 : Form
{
    private static List<MousePos> mousePositionList = new List<MousePos>();
    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<MousePos> 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; }
}

解决方案

UPDATE! -- I partially figured out the answer to part #1 in the book "Pro .NET 2.0 Windows Forms and Customer Controls in C#"

I had originally thought that Refresh() wasn't doing anything and that I needed to call the Invalidate() method, to tell Windows to repaint my control at it's leisure. (which is usually right away, but if you need a guarantee to repaint it now, then follow up with an immediate call to the Update() method.)

    dataGridView1.Invalidate();

But, it turns out that the Refresh() method is merely an alias for:

    dataGridView1.Invalidate(true);
    dataGridView1.Update();             // <== forces immediate redraw

The only glitch I found with this was that if there was no data in the dataGridView, no amount of invalidating would refresh the control. I had to reassign the datasource. Then it worked fine after that. But only for the amount of rows (or items in my list) -- If new items were added, the dataGridView would be unaware that there were more rows to display.

So it seems that when binding a source of data (List or Table) to the Datasource, the dataGridView counts the items (rows) and then sets this internally and never checks to see if there are new rows/items or rows/items deleted. This is why re-binding the datasource repeatedly was working before.

Now to figure out how to update the number of rows to display in dataGridView without having to re-bind the datasource... fun, fun, fun! :-)


After doing some digging, I think I have my answer to part #2 of my question (aka. safe Multi-threading):

Rather than using System.Timers.Timer, I found that I should be using System.Windows.Forms.Timer instead.

The event occurs such that the method that is used in the Callback automatically happens on the primary thread. No cross-threading issues!

The declaration looks like this:

private static System.Windows.Forms.Timer refreshWindow2;
refreshWindow2 = new Timer();
refreshWindow2.Interval = 1000;
refreshWindow2.Tick += new EventHandler(refreshWindow2_Tick);
refreshWindow2.Start();

And the method is like this:

private void refreshWindow2_Tick(object sender, EventArgs e)
{
    dataGridView1.Invalidate();
}

这篇关于如何在多线程应用程序中安全地填充数据和Refresh()DataGridView?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆