2008-11-03 31 views
4

我的應用程序有一個DataGridView對象和一個MousePos類型的列表。 MousePos是一個自定義類,它包含鼠標X,Y座標(類型爲「Point」)和該位置的運行計數。我有一個線程(System.Timers.Timer),每秒產生一次事件,檢查鼠標位置,添加和/或更新此列表中鼠標位置的計數。如何在多線程應用程序中安全地填充數據並刷新()DataGridView?

我想有一個類似的運行線程(同樣,我認爲System.Timers.Timer是一個不錯的選擇),它會再次引發一次事件,自動刷新()DataGridView,以便用戶可以看到屏幕上的數據更新。 (如任務管理器一樣。)

不幸的是,調用VS2005的DataGridView.Refresh()方法將導致停止執行,並指出,我碰到一個跨線程情況。

如果我理解正確的話,我現在有3個線程:

  • 主UI線程
  • mousePos結構列表線程(計時器)
  • 的DataGridView刷新線程(定時器)

爲了查看我是否可以在主線程上刷新DataGridView,我添加了一個名爲DataGridView.Refresh()的表單的按鈕,但是這個(奇怪的是)沒有做任何事情。我發現一個話題似乎表明,如果我設置DataGridView.DataSource = null並返回到我的列表,它會刷新數據網格。事實上這個工作,但只能通按鈕(它獲取的主線程處理。)


所以這個問題已經變成了兩個舞伴:

  1. 是設置DataGridView.DataSource到null並返回到我的List一個可接受的方式刷新數據網格? (這似乎效率不高,我...)
  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; } 
} 
+0

我猜把[C#]在主題是禁止的,是吧? – Pretzel 2008-11-03 19:05:13

回答

5

更新! - 我部分想出答案一部分#1在書中「臨.NET 2.0的Windows窗體和控件的客戶在C#」

我原本以爲刷新()沒有做任何事情,並且我需要調用Invalidate()方法,告訴Windows在閒暇時重新繪製我的控件。 (通常是馬上,但如果你需要一個保證,以重繪它現在,然後到Update()方法的調用立即跟進。)

dataGridView1.Invalidate(); 

但是,事實證明,在刷新()方法只是一個別名:

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

我發現這樣做的唯一問題是,如果有一個在沒有數據dataGridView,沒有任何數量的無效會刷新控件。我不得不重新分配數據源。然後它在那之後很好地工作。但僅限於行數(或列表中的項目) - 如果添加了新項目,dataGridView將不會意識到還有更多行要顯示。

因此,當將數據源(List或Table)綁定到數據源時,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(); 
} 
+2

重新設置DataSource的更優雅的方法是使用BindingContext並進行刷新。這意味着您不必每次都將其重置爲空。在獲得自動更新方面,我認爲你需要一個實現INotifyPropertyChanged的數據源。 – Quibblesome 2008-11-03 18:24:08

5

你必須更新主UI線程上的網格,像所有其他控件。請參閱control.Invoke或Control.BeginInvoke。

3

看起來你有你的答案就在那裏! 就在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(); }); 
} 
相關問題