Как безопасно заполнить данными и Refresh () DataGridView в многопоточном приложении? - PullRequest
4 голосов
/ 03 ноября 2008

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

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

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

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

  • Основной поток пользовательского интерфейса
  • Тема списка 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 ]

5 голосов
/ 03 ноября 2008

ОБНОВЛЕНИЕ! - я частично выяснил ответ на вопрос part # 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();
}
5 голосов
/ 03 ноября 2008

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

3 голосов
/ 20 апреля 2009

Похоже, у вас есть ответ прямо здесь! Просто в cawse вам интересно, как выполнять перекрестные вызовы обратно к пользовательскому интерфейсу: Все элементы управления имеют метод Invoke () (или BEginInvoke () - в случае, если вы хотите делать что-то асинхронно), это используется для вызова любого метода в элементе управления в контексте основного потока пользовательского интерфейса. Поэтому, если вы собираетесь вызывать ваше представление данных из другого потока, вам нужно будет сделать следующее:

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(); });
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...