4

私のアプリには、DataGridView オブジェクトと MousePos タイプの List があります。MousePos は、マウスの X、Y 座標 ("Point" 型) とこの位置の現在のカウントを保持するカスタム クラスです。毎秒 1 回イベントを発生させ、マウス位置をチェックし、このリストのマウス位置のカウントを追加および/または更新するスレッド (System.Timers.Timer) があります。

ユーザーがデータを表示できるように、DataGridView を自動的に Refresh() するために 1 秒に 1 回イベントを再び発生させる、同様の実行中のスレッドが必要です (これも System.Timers.Timer が適切な選択だと思います)。画面更新。(TaskManager のように。)

残念ながら、DataGridView.Refresh() メソッドを呼び出すと、VS2005 が実行を停止し、クロススレッドの状況に陥ったことが通知されます。

私が正しく理解していれば、現在3つのスレッドがあります:

  • プライマリ UI スレッド
  • MousePos リスト スレッド (タイマー)
  • DataGridView 更新スレッド (タイマー)

プライマリ スレッドで DataGridView を Refresh() できるかどうかを確認するために、DataGridView.Refresh() を呼び出すボタンをフォームに追加しましたが、これは (奇妙なことに) 何もしませんでした。DataGridView.DataSource = null を設定してリストに戻すと、データグリッドが更新されることを示すトピックを見つけました。実際、これは機能しましたが、ボタンを介してのみ (プライマリ スレッドで処理されます)。


したがって、この質問は 2 つの部分に変わりました。

  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; }
}
4

3 に答える 3

5

アップデート!--本書「Pro .NET 2.0 Windows Forms and Customer Controls in C#」のパート 1に対する答えを部分的に理解しました。

私は当初、Refresh()は何もしていないので、Invalidate()メソッドを呼び出して、Windows に自分のコントロールを余暇に再描画するように指示する必要があると考えていました。(これは通常すぐに行われますが、今すぐ再描画する保証が必要な場合は、Update() メソッドをすぐに呼び出してフォローアップしてください。)

    dataGridView1.Invalidate();

しかし、 Refresh()メソッドは次の単なるエイリアスであることが判明しました。

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

これで見つかった唯一の不具合は、dataGridView にデータがない場合、無効化してもコントロールが更新されないことでした。データソースを再割り当てする必要がありました。その後、うまくいきました。ただし、行 (またはリスト内の項目) の量のみ -- 新しい項目が追加された場合、dataGridView は、表示する行がさらにあることを認識しません。

そのため、データ ソース (リストまたはテーブル) をデータソースにバインドすると、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();
}
于 2008-11-03T18:17:08.527 に答える
5

他のすべてのコントロールと同様に、メイン UI スレッドでグリッドを更新する必要があります。control.Invoke または Control.BeginInvoke を参照してください。

于 2008-11-03T16:15:57.070 に答える