Заготовка Window Progress. Использование BackgroundWorker. Пример использования BeginInvoke при решении проблем с кросспотоковостью
В этой статье я расскажу о:
- создании шаблона Window Progress
- работе с компонентом BackgroundWorker
- использовании BeginInvoke и InvokeRequired
Я уже когда-то писал статью о создании формы, которая бы показывала информацию о ходе выполнения некоторых действий. Тот пример нельзя было назвать удачным, потому что там не использовалась многопоточность и это окно изредка тормозило при определенных условий. Поэтому я решил усовершенствовать то окно и сделать его более функциональным. Далее я буду использовать такое понятие, как шаблон окна, т.к. планировалось создать именно шаблон для использования его в разработке различных программ. Перед началом работы были поставлены следующие задачи:
- возможность работы отдельном потоке (со всеми вытекающими отсюда последствиями), чтобы выполнение действий не «замораживало» окно
- возможность отменить выполнение действий в любой момент
- удобный доступ к основным функциям шаблона окна
После часа работы у меня получило с следующий код:
namespace ProgressW
{
public delegate void CancelProgress();
public delegate void ProgressChange();
public delegate void Complete();
public partial class frmProgress : Form
{
/// <summary>
/// При отмене действия
/// </summary>
public event CancelProgress OnCancelWork;
/// <summary>
/// При изменении значения прогресс бара
/// </summary>
public event ProgressChange OnProgressChange;
/// <summary>
/// Событие возникающее по завершению
/// </summary>
public event Complete OnComplete;
#region Переменные
private bool _ifCancel = true;
string _onClosingQuestion = "Прекратить работу?";
ProgressBarStyle _style = ProgressBarStyle.Blocks;
bool ifCancelClose = true;
#endregion
/// <summary>
/// Возможна ли отмена
/// </summary>
public bool IfCancel
{
get { return _ifCancel; }
set { btnCancel.Enabled = _ifCancel = value; }
}
/// <summary>
/// Вопрос во время отмены
/// </summary>
public string OnClosingQuestion
{
get { return _onClosingQuestion; }
set { _onClosingQuestion = value; }
}
/// <summary>
/// Стиль прогресс бара
/// </summary>
public ProgressBarStyle Style
{
get { return _style; }
set { prgBar.Style = _style = value; }
}
#region Методы
#region Публичные
public frmProgress()
{
InitializeComponent();
}
/// <summary>
/// Установить значение прогресс бара
/// </summary>
/// <param name="value">Значение</param>
public void SetValue(int value)
{
if (value > -1 && value <= prgBar.Maximum)
prgBar.Value = value;
}
/// <summary>
/// Задать максимальное значение прогресс бара
/// </summary>
/// <param name="value"></param>
public void SetMaxValue(int value)
{
if (value < prgBar.Value)
prgBar.Maximum = prgBar.Value = value;
else
prgBar.Maximum = value;
}
/// <summary>
/// Увеличение значения прогресс бара
/// </summary>
/// <param name="value">Значение</param>
public void Increment(int value)
{
if (prgBar.Style == ProgressBarStyle.Marquee) return;
prgBar.Increment(value);
if (OnProgressChange != null)
OnProgressChange();
checkIfComplete();
}
/// <summary>
/// Увеличение значения прогресс бара
/// </summary>
public void Increment()
{
Increment(1);
}
/// <summary>
/// Задать заголовок
/// </summary>
/// <param name="text">Текст заголовка</param>
public void SetTitle(string text)
{
if (this.InvokeRequired)
BeginInvoke(new MethodInvoker(delegate
{
Text = text;
}));
else
Text = text;
}
/// <summary>
/// Задать текущую информацию
/// </summary>
/// <param name="text">Содержание</param>
public void SetInfo(string text)
{
if (this.InvokeRequired)
BeginInvoke(new MethodInvoker(delegate
{
lblInfo.Text = text;
}));
else
lblInfo.Text = text;
}
/// <summary>
/// Закрыть окно
/// </summary>
public new void Close()
{
ifCancelClose = false;
base.Close();
}
#endregion
#region Приватные
private void checkIfComplete()
{
if (prgBar.Value == prgBar.Maximum)
if (OnComplete != null)
OnComplete();
}
private void frmProgress_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = ifCancelClose;
if (ifCancelClose)
cancel();
}
private void btnCancel_Click(object sender, EventArgs e)
{
cancel();
}
private void cancel()
{
if (_ifCancel)
if (MessageBox.Show(this, _onClosingQuestion, Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
if (OnCancelWork != null)
OnCancelWork();
}
}
#endregion
#endregion
}
}
В самом начале описаны делегаты для использования событий. Одно из них OnCancelWork, которое уведомляет если нажата кнопка Отмена. Доступ к основным переменным описан через аксессоры:
IfCancel — задает возможность прервать работу.
OnClosingQuestion — задает вопрос при нажатии на кнопку Отмена.
Style — задает стиль прогресс бара.
Также описаны публичные методы для управления элементами на форме:
SetValue — задает текущее значение для прогресс бара.
SetMaxValue — задает максимальное значение для прогресс бара.
Перегружаемый Increment — увеличивает на заданное число текущее значение прогресс бара.
SetTitle и SetInfo — задают текст для заголовка окна и текущей информации на форме.
Переназначенный метод Close — корректно закрывает окно.
Это все возможности которые доступны на данный момент. Чтобы подключить шаблон к проекту достаточно нажать Shift+Alt+A и выбрать файла frmProgress.cs. В обозревателе проекта должна появиться новая форма. Вы также можете доработать шаблон «под себя». На форме есть PictureBox, в котором вы можете отображать выбранную картинку. На сегодняшний день это тестовая версия.
Использование шаблона
Теперь давайте рассмотрим пример использования шаблона. Я создал проект и добавил два компонента BackgroundWorker. Один для работы без возможности отмены, другой с этой возможностью. Компонент BackgroundWorker очень удобно использовать при работе с многопоточностью. Он позволяет выполнять заданный код в отдельном потоке и корректно его завершать. Для быстрой разработки программ — вещь просто незаменимая.
При настройке компонента вам необходимо задать свойства WorkerReportsProgress и WorkerSupportCancellation. Первое позволяется использовать событие, которое должно возникнуть при изменении состояния, а второе — возможность прервать работу BackgroundWorker. В настройках событий следует задать все три событие, которые там расположены.
Для выполнения действий в коде пишем:
private void btnRun_Click(object sender, EventArgs e)
{
frmPrg = new frmProgress();
frmPrg.SetTitle("Проверка работы");
frmPrg.Owner = this;
frmPrg.Show();
frmPrg.IfCancel = false;
bgWorker.RunWorkerAsync();
}
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 100; i++)
{
frmPrg.SetInfo(i.ToString());
Thread.Sleep(100);
bgWorker.ReportProgress(1);
}
}
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
frmPrg.Increment();
}
private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (frmPrg != null)
frmPrg.Close();
}
private void btnRunCancel_Click(object sender, EventArgs e)
{
frmPrg = new frmProgress();
frmPrg.SetTitle("Проверка работы");
frmPrg.Owner = this;
frmPrg.Show();
cancel = false;
frmPrg.OnCancelWork += new CancelProgress(frmPrg_OnCancelWork);
bgWorkerCancel.RunWorkerAsync();
}
protected void frmPrg_OnCancelWork()
{
cancel = true;
frmPrg.SetInfo("Ожидание отмены");
}
bool cancel = false;
private void bgWorkerCancel_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 100; i++)
{
if (cancel)
{
bgWorkerCancel.CancelAsync();
break;
}
frmPrg.SetInfo(i.ToString());
Thread.Sleep(100);
bgWorkerCancel.ReportProgress(1);
}
}
В первом методе мы описываем создание формы прогресса и задаем необходимые свойства. Обратите внимание, что задается свойство Owner, чтобы форма прогресса была поверх родительской. Ей не требуется при этом задавать свойство TopMost. Далее выполняем bgWorker.RunWorkerAsync(), который выполнит метод bgWorker_DoWork в отдельном потоке. Т.к. код выполняем в отдельном потоке, то нам необходимо реализовать кросспотоковость, т.к. объект frmPrg (форма прогресса) создан в потоке главной формы. Для доступа к элементу можно использовать следующую конструкцию:
if (this.InvokeRequired)
BeginInvoke(new MethodInvoker(delegate
{
Text = text;
}));
else
Text = text;
if (this.InvokeRequired) — сначала проверяет нужно ли использовать BeginInvoke. BeginInvoke асинхронный вариант вызова Invoke и позволяет получить доступ к элементу из другого потока.
Если требуется изменить состояние, то вызывается метод bgWorker_ProgressChanged с помощью bgWorkerCancel.ReportProgress(1). В самом bgWorker_ProgressChanged описывается, что нужно сделать в этот момент. В данном случает увеличивается значение прогресс бара.
После того, как метод bgWorker_DoWork закончил работы (неважно успешно или нет), всегда выполняется метод bgWorker_RunWorkerCompleted. В данном случае у нас описан код, который закрывает форму с прогресс баром.

Заключение
Считаю, что с задачей создания шаблона формы прогресса мы справились. Надеюсь, что это форма поможет вам быстрее и качественнее разрабатывать свои программы. На момент написания статьи была выложена бета версия, которая требует глубокого тестирования. Если у вас будут дополнения и пожелания к выполненной работе, пишите.
Шаблон формы в разделе проектов
Исходники обучающего проекта
Популярность: 25%
Еще по этой теме:
- C#. Простой пример использования логирования log4net
- Альтернативные потоки данных NTFS с примером использования на C#
- C#. Использование System.Console для создания игр в текстовом режиме. Часть 3
- Использование Reflection для получения доступа к приватным переменным
- Создаем форму с информацией о ходе выполнения действий
Если у вас возникли вопросы, вы можете оставить их в комментариях
купить бассейн . китайские телефоны киев . антицеллюлитный массаж
