Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Form2 с прогрессбаром в отдельном потоке по нажатию на кнопку

69K
10 июня 2014 года
Palma91
21 / / 10.06.2014
Здравствуйте!Такой вопрос. Есть Form1 на которой по кнопке выполняется чтение xml файла и выгрузка данных в таблицу datagridview.
Как сделать в отдельном потоке показ Form2 пока грузятся данные с Form1?
Вот код чтения и загрузки данных по нажатию из Form1:
 
Код:
private void открытьToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string path = "file.xml";
            DataSet ds = new DataSet();
            ds.ReadXml(path);
            dataGridView1.DataSource = ds;
            dataGridView1.DataMember = "table1";
            MessageBox.Show("Данные успешно загружены", "Готово");
         }
Вот код показа Form2:
 
Код:
Form2 frm = new Form2();
            frm.Show();
Т.е. по нажатию открытьToolStripMenuItem_Click начинается чтение и выгрузка файла и в тоже время должна открыться Form2 (Типа подождите идет загрузка)
Вот как организовать все эти действия в отдельных потоках я не знаю...точнее голова уже кругом.Столько способов в инете.
Может кто подобное делал?Заранее спасибо.
20K
12 июня 2014 года
sem2711
124 / / 23.09.2009
Ну, выход из этой ситуации зависит от требований, предъявляемых к приложению. Если во время загрузки данных пользователь должен иметь возможность делать что-либо с основным окном, то диалог с прогресс-баром должен быть немодальным. В этом случае Microsoft рекомендует показывать прогресс-бар не в отдельном окне, а в статус-баре основного окна. Если пользователь не должен иметь доступ к основному окну на время загрузки данных, то модальный диалог - подходящее решение. Но в этом случае логику загрузки удобнее было бы (на мой взгляд) реализовать внутри этого диалога. Тогда вопрос с его закрытием разрешается сам собой. Для первого же случая я предлагаю немного измененный предыдущий пример. Здесь диалог создается и показывается из основного потока, поэтому с отображением прогресс-бара проблем не возникает.
Код:
private Form waitForm; // Окно с прогресс-баром.
        private delegate void UpdateDataGridViewCallback(DataSet dataSet); // Делегат для метода заполнения DataGridView.
        private delegate void CloseProgressCallback(); // Делегат для закрытия диалога прогресс-бара.
        public delegate void OnWorkIsDoneHandler(object sender, EventArgs e); // Делегат для события окна.
        public event OnWorkIsDoneHandler OnWorkIsDone; // Событие окна: завершение потока чтения.
        public Form1()
        {
            InitializeComponent();
            // Подписка на событие.
            OnWorkIsDone += Form1_OnWorkIsDone;
        }

        // Обработчик события окончания работы потока чтения.
        private void Form1_OnWorkIsDone(object sender, EventArgs e)
        {
            waitForm.Invoke(new CloseProgressCallback(CloseProgress));
            MessageBox.Show("Загрузка завершена");
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // Инициализация и показ формы с прогресс-баром.
            waitForm = new ProgressDlg();
            waitForm.Show();
            // Создание и запуск рабочего потока.
            System.Threading.Thread readThread = new System.Threading.Thread(new System.Threading.ThreadStart(ReadThread));
            readThread.Start();
        }

        private void ReadThread()
        {
            DataSet ds = new DataSet();
            ds.Tables.Add(new DataTable());
            System.Threading.Thread.Sleep(5000);
            dataGridView1.Invoke(new UpdateDataGridViewCallback(UpdateDataGridView), new object[] { ds });
            // Оповещаем основное окно о завершении работы.
            if (OnWorkIsDone != null)
            {
                OnWorkIsDone(this, EventArgs.Empty);
            }
        }

        // Заполнение DataGridView
        private void UpdateDataGridView(DataSet dataSet)
        {
            dataGridView1.DataSource = dataSet;
            dataGridView1.DataMember = "table1";
        }

        // Закрытие диалога прогресс-бара.
        private void CloseProgress()
        {
            if (waitForm != null)
            {
                waitForm.Close();
            }
        }
P.S. По поводу причин такого поведения. Ответ, как я понимаю, кроется в приоритете потока основного окна приложения. Не все контролы к этому чувствительны, но вот прогресс-бар - один из таких. Если запускается модальный диалог, то поток основного окна блокируется (то есть очередь его сообщений не обрабатывается) и сообщения модального диалога обрабатываются в основном потоке.
326
10 июня 2014 года
sadovoya
757 / / 19.11.2005
Наоборот -- читай в потоке, не замораживай гуй. Т.е по щелчку запускаешь долгий нудный поток, а программа спокойно в это время форму 2 показывает. Как-то так :)
69K
11 июня 2014 года
Palma91
21 / / 10.06.2014
Цитата: sadovoya
Наоборот -- читай в потоке, не замораживай гуй. Т.е по щелчку запускаешь долгий нудный поток, а программа спокойно в это время форму 2 показывает. Как-то так :)



Сделал так,но выдает ошибку:


Код:
private void ThreadRead() // Создал поток для чтения xml и выгрузке в datagridview
        {
            string path = "test.xml";
            DataSet ds = new DataSet();
            ds.ReadXml(path);
            dataGridView1.DataSource = ds;
            dataGridView1.DataMember = "table1";
            MessageBox.Show("Данные успешно загружены", "Готово");
        }
 
       private void ThreadForm() // Создал поток для показа Form2 (Типа подождите идет загрузка)
       {
           Form2 frm = new Form2();
           frm.Show();
       }
 
        private void открытьToolStripMenuItem_Click(object sender, EventArgs e) // Запуск потоков
        {
            Thread t1 = new Thread(new ThreadStart(this.ThreadRead));
            t1.Start();
            Thread t2 = new Thread(new ThreadStart(this.ThreadForm));
            t2.Start();                          
        }
Сообщение ошибки:
Необработанное исключение типа "System.InvalidOperationException" в System.Windows.Forms.dll
Дополнительные сведения: Недопустимая операция в нескольких потоках: попытка доступа к элементу управления 'dataGridView1' не из того потока, в котором он был создан.

Как решить проблему?
20K
11 июня 2014 года
sem2711
124 / / 23.09.2009
Цитата: Palma91

Сообщение ошибки:
Необработанное исключение типа "System.InvalidOperationException" в System.Windows.Forms.dll
Дополнительные сведения: Недопустимая операция в нескольких потоках: попытка доступа к элементу управления 'dataGridView1' не из того потока, в котором он был создан.

Как решить проблему?


Проблема решается использованием делегата для метода, который должен обновлять состояние dataGridView1, и вызовом этого делегата через метод Invoke():

Код:
private delegate void UpdateDataGridViewCallback(DataSet dataSet);

private void UpdateDataGridView(DataSet dataSet)
{
      dataGridView1.DataSource = dataSet;
      dataGridView1.DataMember = "table1";
}

private void ThreadRead() // Создал поток для чтения xml и выгрузке в datagridview
{
      string path = "test.xml";
      DataSet ds = new DataSet();
      ds.ReadXml(path);
      dataGridView1.Invoke(new UpdateDataGridViewCallback(UpdateDataGridView), new object[] { ds });
      MessageBox.Show("Данные успешно загружены", "Готово");
}
Вот такой паттерн можно использовать во всех подобных ситуациях.
69K
12 июня 2014 года
Palma91
21 / / 10.06.2014
Цитата: sem2711
Цитата: Palma91

Сообщение ошибки:
Необработанное исключение типа "System.InvalidOperationException" в System.Windows.Forms.dll
Дополнительные сведения: Недопустимая операция в нескольких потоках: попытка доступа к элементу управления 'dataGridView1' не из того потока, в котором он был создан.

Как решить проблему?


Проблема решается использованием делегата для метода, который должен обновлять состояние dataGridView1, и вызовом этого делегата через метод Invoke():

Код:
private delegate void UpdateDataGridViewCallback(DataSet dataSet);

private void UpdateDataGridView(DataSet dataSet)
{
      dataGridView1.DataSource = dataSet;
      dataGridView1.DataMember = "table1";
}

private void ThreadRead() // Создал поток для чтения xml и выгрузке в datagridview
{
      string path = "test.xml";
      DataSet ds = new DataSet();
      ds.ReadXml(path);
      dataGridView1.Invoke(new UpdateDataGridViewCallback(UpdateDataGridView), new object[] { ds });
      MessageBox.Show("Данные успешно загружены", "Готово");
}
Вот такой паттерн можно использовать во всех подобных ситуациях.



Спасибо...все работает.Но тут осталась нерешенной маленькая деталь.Form2 которую я вызываю при выгрузке данных в datagridview,быстро появляется и исчезает,а потом идет заполнение таблицы.Как будто таблица и прогрессбар (он же Form2) работают не одновременно,а отдельно.
Как сделать показ Form2 пока грузится таблица,а как только таблица полностью загрузилась,окно закрывается?
Пробовал использовать не

 
Код:
frm.Show();
 
Код:
frm.ShowDialog();
,но при таком раскладе Form2 показывается пока не остановишь компилятор (ну т.е. сама не закрывается).

Сейчас все выглядит так:
Код:
private delegate void UpdateDataGridViewCallback(DataSet dataSet);

        private void UpdateDataGridView(DataSet dataSet)
        {
            dataGridView1.DataSource = dataSet;
            dataGridView1.DataMember = "table1";
        }

        private void ThreadRead() // Создал поток для чтения xml и выгрузке в datagridview
        {
            string path = "test.xml";
            DataSet ds = new DataSet();
            ds.ReadXml(path);
            dataGridView1.Invoke(new UpdateDataGridViewCallback(UpdateDataGridView), new object[] { ds });
            MessageBox.Show("Данные успешно загружены", "Готово");
        }

        private void ThreadForm() // Создал поток для показа Form2 (Типа подождите идет загрузка)
        {
            Form2 frm = new Form2();
            frm.Show();
           
        }

        private void открытьToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Thread t2 = new Thread(new ThreadStart(this.ThreadForm));
            t2.Start();
            Thread t1 = new Thread(new ThreadStart(this.ThreadRead));
            t1.Start();
           
        }
20K
12 июня 2014 года
sem2711
124 / / 23.09.2009
Решение этой проблемы в том, чтобы формировать, показывать и закрывать дополнительное окно в том же потоке, что и чтение данных (ReadThread). В моем примере, кроме того, создается событие для оповещения основного окна об окончании работы.
Код:
private Form waitForm; // Окно с прогресс-баром.
        private delegate void UpdateDataGridViewCallback(DataSet dataSet); // Делегат для метода заполнения DataGridView.
        public delegate void OnWorkIsDoneHandler(object sender, EventArgs e); // Делегат для события окна.
        public event OnWorkIsDoneHandler OnWorkIsDone; // Событие окна: завершение потока чтения.
        public Form1()
        {
            InitializeComponent();
            // Подписка на событие.
            OnWorkIsDone += Form1_OnWorkIsDone;
        }

        // Обработчик события окончания работы потока чтения.
        private void Form1_OnWorkIsDone(object sender, EventArgs e)
        {
            MessageBox.Show("Загрузка завершена");
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            System.Threading.Thread readThread = new System.Threading.Thread(new System.Threading.ThreadStart(ReadThread));
            readThread.Start();
        }

        private void ReadThread()
        {
            // Инициализация и показ окна.
            waitForm = new Form();
            InitializeWaitForm();
            waitForm.Show();
            DataSet ds = new DataSet();
            ds.Tables.Add(new DataTable());
            System.Threading.Thread.Sleep(3000); // Имитация работы.
            dataGridView1.Invoke(new UpdateDataGridViewCallback(UpdateDataGridView), new object[] { ds });
            // Закрываем окно с прогресс-баром и оповещаем основное окно о завершении работы.
            waitForm.Close();
            if (OnWorkIsDone != null)
            {
                OnWorkIsDone(this, EventArgs.Empty);
            }
        }

        // Настройка окна с прогресс-баром.
        private void InitializeWaitForm()
        {
            waitForm.Width = 350;
            waitForm.Height = 200;
            waitForm.Location = new Point(400, 250);
        }

        // Заполнение DataGridView
        private void UpdateDataGridView(DataSet dataSet)
        {
            dataGridView1.DataSource = dataSet;
            dataGridView1.DataMember = "table1";
        }
69K
12 июня 2014 года
Palma91
21 / / 10.06.2014
Большое спасибо!Ваш пример работает как надо т.е. выходит Form2 вперед,а сама таблица где-то там грузится,а в конце Form2 закрывается. Но прогрессбар не отображается в Form2,хотя я его кинул на форму.
Вот скрин:

А если сделать
 
Код:
frm.ShowDialog();
то прогрессбар бегает
Почему так?
69K
12 июня 2014 года
Palma91
21 / / 10.06.2014
Ура!Ваш код выше решил задачу))))А если нужно блокировать действия пользователя?Т.е. пока форма с прогрессбаром не закроется,ничего сделать с приложением нельзя.
326
12 июня 2014 года
sadovoya
757 / / 19.11.2005
Полная блокировка приложения недопустима. Закрыть программу или прервать загрузку данных пользователь должен иметь возможность. Просто контролы, которые имеют смысл использовать лишь после загрузки данных делайте неактивными на период загрузки, а потом активируйте. Либо пусть они остаются активными, но при нажатии на них выдают диалог "Ожидается окончание загрузки. Подождать или выйти из программы?"

Либо как вам говорили выше - модальный диалог.
Цитата:
Если пользователь не должен иметь доступ к основному окну на время загрузки данных, то модальный диалог - подходящее решение. Но в этом случае логику загрузки удобнее было бы (на мой взгляд) реализовать внутри этого диалога. Тогда вопрос с его закрытием разрешается сам собой.


Всегда поверх основной формы, пока не закроешь. Но и в нем нужна кнопка прервать/выйти. Прервать, если только есть смысл продолжать работу без загрузки данных. Выйти, если смысла продолжать работу нет без этой загрузки.

По началу я думал, есть смысл продолжать, причем параллельно и основную программу и загрузку...

69K
12 июня 2014 года
Palma91
21 / / 10.06.2014
Цитата:
Просто контролы, которые имеют смысл использовать лишь после загрузки данных делайте неактивными на период загрузки, а потом активируйте.


Ну да...я имею ввиду,как сделать контрол открытьToolStripMenuItem_Click неактивным на период загрузки данных и показа формы прогрессбара?
Просто во время загрузки у меня активен контрол меню.

 
Код:
private void открытьToolStripMenuItem_Click(object sender, EventArgs e)
326
12 июня 2014 года
sadovoya
757 / / 19.11.2005
Неуже ли нет ничего типа
...MenuItem.setEnabled(false);
или
...MenuItem.Enabled = false;
Спрашиваю, потому что в шарпе не силен. Но думаю, что есть или аналогичное.
20K
12 июня 2014 года
sem2711
124 / / 23.09.2009
Цитата:
контрол открытьToolStripMenuItem_Click


Это не контрол. Это обработчик события щелчка по пункту меню, т.е. метод, который вызывается по событию. Контрол у Вас, по всей видимости, называется открытьToolStripMenuItem. У него должно быть свойство Enabled. Если передать этому свойству true (это по умолчанию), то пункт меню работает как ожидается. Если false - пункт меню становится "серым" и некликабельным. Остается добавить 2 строчки кода: с блокировкой пункта меню (перед запуском рабочего потока) и разблокировкой (в методе закрытия окна прогресс-бара).

69K
13 июня 2014 года
Palma91
21 / / 10.06.2014
Всем большое спасибо за оказанную помощь!Не думал,что кто-то вообще ответит на мои вопросы.
88K
25 июня 2014 года
Minily Wiry
5 / / 25.06.2014
пожалуйста
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог