如何安全数据和刷新()一个DataGridView填充一个多线程的应用程序?多线程、应用程序、安全、数据

2023-09-03 10:33:54 作者:你的情话敷衍

我的应用程序都有一个DataGridView对象和类型mousePos结构的列表。 mousePos结构是持有鼠标X,(类型为点)Y坐标和该位置的运行计数的自定义类。我有一个线程(System.Timers.Timer的)是引发事件每秒一次,检查鼠标位置,添加和/或更新这个列表中的鼠标位置的计数。

我想有一个类似的正在运行的线程(再次,我觉得System.Timers.Timer的是一个很好的选择),这将再次引发事件一次,第二次自动刷新()在DataGridView,这样用户可以看到在屏幕上更新数据。 (如任务管理器一样。)

不幸的是,调用DataGridView.Refresh()方法的结果VS2005停止执行,并指出,我碰到的一个跨线程的情况。

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

在主UI线程 mousePos结构列表线程(定时器) 的DataGridView刷新线程(定时器)

要看看我是否能刷新()在主线程上的DataGridView,我添加了一个按钮,要求DataGridView.Refresh()的形式,但是这(奇怪)什么都没做。我发现这似乎表明,如果我设置DataGridView.DataSource = null,并且回到了我的名单,它将刷新数据网格的话题。事实上这个工作,但只能通按钮(它获取的主线程处理。)

所以,这个问题已经变成了两个舞伴:

是设置DataGridView.DataSource为空,回我的列表刷新数据网格一种可接受的方式? (这似乎效率不高,我...) 如何安全地做到这一点在多线程环境中?

这里的code我写到目前为止(C#/。NET 2.0)

 公共部分类Form1中:形态
{
    私有静态列表< mousePos结构> mousePositionList =新的名单,其中,mousePos结构>();
    私有静态System.Timers.Timer的mouseCheck =新System.Timers.Timer的(1000);
    私有静态System.Timers.Timer的refreshWindow =新System.Timers.Timer的(1000);

    公共Form1中()
    {
        的InitializeComponent();
        mousePositionList.Add(新mousePos结构()); // 回答!在绑定到数据源必须至少有1项
        dataGridView1.DataSource = mousePositionList;
        mouseCheck.Elapsed + =新System.Timers.ElapsedEventHandler(mouseCheck_Elapsed);
        mouseCheck.Start();
        refreshWindow.Elapsed + =新System.Timers.ElapsedEventHandler(refreshWindow_Elapsed);
        refreshWindow.Start();
    }

    公共无效mouseCheck_Elapsed(对象源,EventArgs的五)
    {
        点mPnt = Control.MousePosition;
        mousePos结构MPOS = mousePositionList.Find(ByPoint(mPnt));
        如果(MPOS == NULL){mousePositionList.Add(新mousePos结构(mPnt)); }
        其他{mPos.Count ++; }
    }

    公共无效refreshWindow_Elapsed(对象源,EventArgs的五)
    {
        //dataGridView1.DataSource = NULL; //老办法
        //dataGridView1.DataSource = mousePositionList; //老办法
        dataGridView1.Invalidate(); //< =解答!
    }

    私有静态predicate< mousePos结构> ByPoint(点PNT)
    {
        返回委托(mousePos结构MPOS){返回(mPos.Pnt == PNT); };
    }
}

公共类mousePos结构
{
    私人点位置=新的点();
    私人诠释计数= 1;

    公共点PNT {{返回位置; }}
    公众诠释X {{返回position.X; }集合{position.X =价值; }}
    公众诠释Ÿ{{返回position.Y; }集合{position.Y =价值; }}
    公众诠释计数{{返回计数; }集合{数=价值; }}

    公共mousePos结构(){}
    公共mousePos结构(点鼠标){位置=鼠标; }
}
 
不可能的图表 如何在柱形图背后添加平均线

解决方案

更新 - !我的部分的想出答案第1 在这本书临.NET 2.0的C#Windows窗体和客户控制

我原本以为刷新()没有做任何事情,我需要调用的Invalidate()的方法,来让Windows重画我控制在它是休闲。 (通常是马上,但如果你需要一个保证重绘它的现在的,然后跟进到Update()方法的直接调用。)

  dataGridView1.Invalidate();
 

不过,事实证明,在刷新()方式仅仅是一个别名:

  dataGridView1.Invalidate(真正的);
    dataGridView1.Update(); //< ==部队立即重绘
 

我发现这样做的唯一问题是,如果有在DataGridView没有数据,再多的无效将刷新控制。我不得不重新指定数据源。然后,在那之后的罚款。但仅限于行(或在我的列表项)的量: - 如果新项目被添加,所述的dataGridView会意识到,有更多的行,以显示

如此看来,在绑定的数据(表或表)的数据源的来源,在DataGridView计数的项目(行),然后设置这个内部,从来没有检查,看看是否有新行/项目或列/项目删除。这就是为什么重新绑定重复数据源是工作了。

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

在做一些挖后,我觉得我有我的答案第2号我的问题(又名安全多线程):

而不是使用的 System.Timers.Timer的的,我发现,我应该用 System.Windows.Forms.Timer 代替。

的事件发生时,使得用于在回调方法自动发生主线程上。没有跨线程问题!

声明如下:

 私有静态System.Windows.Forms.Timer refreshWindow2;
refreshWindow2 =新的Timer();
refreshWindow2.Interval = 1000;
refreshWindow2.Tick + =新的EventHandler(refreshWindow2_Tick);
refreshWindow2.Start();
 

和方法是这样的:

 私人无效refreshWindow2_Tick(对象发件人,EventArgs的)
{
    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:

Is setting DataGridView.DataSource to null and back to my List an acceptable way to refresh the datagrid? (It seems inefficient to me...) 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();
}