2008-11-03 3 views
4

Мое приложение имеет объект DataGridView и список типов MousePos. MousePos - это настраиваемый класс, который содержит координаты мыши X, Y (типа «Точка») и текущее количество этой позиции. У меня есть поток (System.Timers.Timer), который вызывает событие один раз в секунду, проверяет положение мыши, добавляет и/или обновляет подсчет позиции мыши в этом списке.Как безопасно заполнять данные и обновлять() DataGridView в многопоточном приложении?

Я бы хотел иметь подобную бегущую нить (опять же, я думаю, что System.Timers.Timer - хороший выбор), который снова поднимал бы событие раз в секунду, чтобы автоматически обновлять() DataGridView, чтобы пользователь мог видеть данные об обновлении экрана. (например, TaskManager делает.)

К сожалению, вызов метода DataGridView.Refresh() приводит к остановке выполнения VS2005 и отмечает, что я столкнулся с ситуацией с перекрестными потоками.

Если я правильно понять, у меня есть 3 темы теперь:

  • Первичный поток пользовательского интерфейса
  • MousePos Список нить (таймер)
  • DataGridView Refresh нить (таймер)

Чтобы узнать, могу ли я обновить() 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! 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#] в теме Verboten, да? – Pretzel 2008-11-03 19:05:13

ответ

5

ОБНОВЛЕНИЕ! - Я частично выяснил ответ на часть # 1 в книге "Pro .NET 2.0 Windows Forms и элементы управления клиентами в C#"

Я первоначально думал, что 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(); 
} 
+2

Более элегантным средством перезагрузки DataSource является использование BindingContext и обновление вместо этого. Это означает, что вам не нужно каждый раз переустанавливать его на нуль. Что касается автоматического обновления, я думаю, вам нужен источник данных, который реализует INotifyPropertyChanged. – Quibblesome 2008-11-03 18:24:08

5

Вы должны обновить сетку на главном потоке пользовательского интерфейса, как и все другие элементы управления. См. Control.Invoke или Control.BeginInvoke.

3

Похоже, у вас есть свой ответ прямо здесь! Как раз в cawse вам интересно, как выполнять перекрестные потоки вызовов обратно в ui: Все элементы управления имеют метод Invoke() (или BEginInvoke() - если вы хотите делать асинхронно), это используется для вызова любых метод управления в контексте основного потока пользовательского интерфейса. Так что, если вы собираетесь позвонить своему 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(); }); 
}