代码之家  ›  专栏  ›  技术社区  ›  Pretzel

如何在多线程应用程序中安全地使用数据和Refresh()填充DataGridView?

  •  4
  • Pretzel  · 技术社区  · 17 年前

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

    我希望有一个类似的运行线程(同样,我认为System.Timers.Timer是一个不错的选择),它每秒再次引发一次事件,自动刷新()DataGridView,以便用户可以看到屏幕上的数据更新。(就像TaskManager一样。)

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

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

    • 主UI线程
    • MousePos列表线程(计时器)
    • DataGridView刷新线程(计时器)

    为了查看是否可以在主线程上刷新()DataGridView,我在窗体中添加了一个名为DataGridView的按钮。Refresh(),但这(奇怪的是)什么也没做。我发现了一个主题,似乎表明如果我设置了DataGridView。DataSource=null并返回到我的列表,它将刷新数据网格。事实上,这是有效的,但只能通过按钮(在主线程上处理)


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

    1. 正在设置DataGridView。将DataSource设置为null并返回我的列表是刷新数据网格的一种可接受的方式吗?(我觉得效率不高……)
    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! 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; }
    }
    
    3 回复  |  直到 17 年前
        1
  •  5
  •   Grzenio    17 年前

    与所有其他控件一样,您必须更新主UI线程上的网格。参见控制。调用或控制。开始调用。

        2
  •  5
  •   Pretzel    17 年前

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

    我原本以为 刷新() 什么都没做,我需要打电话给 无效() 方法,告诉Windows在空闲时重新绘制我的控件。(通常是马上,但如果你需要重新粉刷的保证 现在 ,然后立即调用Update()方法。)

        dataGridView1.Invalidate();
    

    但是,事实证明 刷新() 方法只是以下内容的别名:

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

    我发现的唯一问题是,如果dataGridView中没有数据,那么任何无效操作都不会刷新控件。我不得不重新分配数据源。在那之后,它工作得很好。但仅限于行数(或我的列表中的项)——如果添加了新项,dataGridView将不会意识到还有更多行要显示。

    因此,当将数据源(列表或表)绑定到数据源时,dataGridView会对项目(行)进行计数,然后在内部设置,从不检查是否有新行/项目或删除的行/项目。这就是为什么重复绑定数据源以前是有效的。

    现在,我们来了解如何更新dataGridView中显示的行数,而无需重新绑定数据源。..有趣,有趣,有趣! :-)


    经过一番挖掘,我想我有了答案 第2部分 关于我的问题(又名安全多线程):

    而不是使用 系统。计时器。定时器 ,我发现我应该使用 系统。窗户。形式。定时器 相反。

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

    声明如下:

    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();
    }
    
        3
  •  3
  •   Fredrik Bonde    17 年前

    看来你的答案就在那里! 只是在cawse中,你很好奇如何对ui进行跨线程调用: 所有控件都有一个Invoke()方法(或BEginInvoke(,如果你想异步执行操作),这用于在主UI线程的上下文中调用控件上的任何方法。 因此,如果你要从另一个线程调用你的datagridview,你需要执行以下操作:

    public void refreshWindow_Elapsed(object source, EventArgs e)
    {
    
       // we use anonymous delgate here as it saves us declaring a named delegate in our class
       // however, as c# type inference sometimes need  a bit of 'help' we need to cast it 
       // to an instance of MethodInvoker
       dataGridView1.Invoke((MethodInvoker)delegate() { dataGridView1.Invalidate(); });
    }
    
    推荐文章