• Программинг

Нужны источники бесперебойного питания?

Источники бесперебойного питания от дизельстор

Заготовка Window Progress. Использование BackgroundWorker. Пример использования BeginInvoke при решении проблем с кросспотоковостью

В этой статье я расскажу о:

  • создании шаблона Window Progress
  • работе с компонентом BackgroundWorker
  • использовании BeginInvoke и InvokeRequired


Я уже когда-то писал статью о создании формы, которая бы показывала информацию о ходе выполнения некоторых действий. Тот пример нельзя было назвать удачным, потому что там не использовалась многопоточность и это окно изредка тормозило при определенных условий. Поэтому я решил усовершенствовать то окно и сделать его более функциональным. Далее я буду использовать такое понятие, как шаблон окна, т.к. планировалось создать именно шаблон для использования его в разработке различных программ. Перед началом работы были поставлены следующие задачи:

  • возможность работы отдельном потоке (со всеми вытекающими отсюда последствиями), чтобы выполнение действий не «замораживало» окно
  • возможность отменить выполнение действий в любой момент
  • удобный доступ к основным функциям шаблона окна

После часа работы у меня получило с следующий код:

1 namespace ProgressW
2 {
3     public delegate void CancelProgress();
4     public delegate void ProgressChange();
5     public delegate void Complete();
6  
7     public partial class frmProgress : Form
8     {
9         /// <summary>
10         /// При отмене действия
11         /// </summary>
12         public event CancelProgress OnCancelWork;
13         /// <summary>
14         /// При изменении значения прогресс бара
15         /// </summary>
16         public event ProgressChange OnProgressChange;
17         /// <summary>
18         /// Событие возникающее по завершению
19         /// </summary>
20         public event Complete OnComplete;
21  
22         #region Переменные
23         private bool _ifCancel = true;
24         string _onClosingQuestion = "Прекратить работу?";
25         ProgressBarStyle _style = ProgressBarStyle.Blocks;
26         bool ifCancelClose = true;
27         #endregion
28  
29         /// <summary>
30         /// Возможна ли отмена
31         /// </summary>
32         public bool IfCancel
33         {
34             get { return _ifCancel; }
35             set { btnCancel.Enabled = _ifCancel = value; }
36         }
37  
38         /// <summary>
39         /// Вопрос во время отмены
40         /// </summary>
41         public string OnClosingQuestion
42         {
43             get { return _onClosingQuestion; }
44             set { _onClosingQuestion = value; }
45         }
46  
47         /// <summary>
48         /// Стиль прогресс бара
49         /// </summary>
50         public ProgressBarStyle Style
51         {
52             get { return _style; }
53             set { prgBar.Style = _style = value; }
54         }
55  
56         #region Методы
57  
58         #region Публичные
59         public frmProgress()
60         {
61             InitializeComponent();
62         }
63  
64         /// <summary>
65         /// Установить значение прогресс бара
66         /// </summary>
67         /// <param name="value">Значение</param>
68         public void SetValue(int value)
69         {
70             if (value > -1 && value <= prgBar.Maximum)
71                 prgBar.Value = value;
72         }
73  
74         /// <summary>
75         /// Задать максимальное значение прогресс бара
76         /// </summary>
77         /// <param name="value"></param>
78         public void SetMaxValue(int value)
79         {
80             if (value < prgBar.Value)
81                 prgBar.Maximum = prgBar.Value = value;
82             else
83                 prgBar.Maximum = value;
84         }
85  
86         /// <summary>
87         /// Увеличение значения прогресс бара
88         /// </summary>
89         /// <param name="value">Значение</param>
90         public void Increment(int value)
91         {
92             if (prgBar.Style == ProgressBarStyle.Marquee) return;
93  
94             prgBar.Increment(value);
95             if (OnProgressChange != null)
96                 OnProgressChange();
97             checkIfComplete();
98         }
99  
100         /// <summary>
101         /// Увеличение значения прогресс бара
102         /// </summary>
103         public void Increment()
104         {
105             Increment(1);
106         }
107  
108         /// <summary>
109         /// Задать заголовок
110         /// </summary>
111         /// <param name="text">Текст заголовка</param>
112         public void SetTitle(string text)
113         {
114             if (this.InvokeRequired)
115                 BeginInvoke(new MethodInvoker(delegate
116                    {
117                        Text = text;
118                    }));
119             else
120                 Text = text;
121         }
122  
123         /// <summary>
124         /// Задать текущую информацию
125         /// </summary>
126         /// <param name="text">Содержание</param>
127         public void SetInfo(string text)
128         {
129             if (this.InvokeRequired)
130                 BeginInvoke(new MethodInvoker(delegate
131                 {
132                     lblInfo.Text = text;
133                 }));
134             else
135                 lblInfo.Text = text;
136         }
137  
138         /// <summary>
139         /// Закрыть окно
140         /// </summary>
141         public new void Close()
142         {
143             ifCancelClose = false;
144             base.Close();
145         }
146         #endregion
147  
148         #region Приватные
149         private void checkIfComplete()
150         {
151             if (prgBar.Value == prgBar.Maximum)
152                 if (OnComplete != null)
153                     OnComplete();
154         }
155  
156         private void frmProgress_FormClosing(object sender, FormClosingEventArgs e)
157         {
158             e.Cancel = ifCancelClose;
159             if (ifCancelClose)
160                 cancel();
161         }
162  
163         private void btnCancel_Click(object sender, EventArgs e)
164         {
165             cancel();
166         }
167  
168         private void cancel()
169         {
170             if (_ifCancel)
171                 if (MessageBox.Show(this, _onClosingQuestion, Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
172                 {
173                     if (OnCancelWork != null)
174                         OnCancelWork();
175                 }
176         }
177         #endregion
178         #endregion
179     }
180 }

В самом начале описаны делегаты для использования событий. Одно из них OnCancelWork, которое уведомляет если нажата кнопка Отмена. Доступ к основным переменным описан через аксессоры:
IfCancel – задает возможность прервать работу.
OnClosingQuestion – задает вопрос при нажатии на кнопку Отмена.
Style – задает стиль прогресс бара.

Также описаны публичные методы для управления элементами на форме:
SetValue – задает текущее значение для прогресс бара.
SetMaxValue – задает максимальное значение для прогресс бара.
Перегружаемый Increment – увеличивает на заданное число текущее значение прогресс бара.
SetTitle и SetInfo – задают текст для заголовка окна и текущей информации на форме.
Переназначенный метод Close – корректно закрывает окно.

Это все возможности которые доступны на данный момент. Чтобы подключить шаблон к проекту достаточно нажать Shift+Alt+A и выбрать файла frmProgress.cs. В обозревателе проекта должна появиться новая форма. Вы также можете доработать шаблон «под себя». На форме есть PictureBox, в котором вы можете отображать выбранную картинку. На сегодняшний день это тестовая версия.

Использование шаблона

Теперь давайте рассмотрим пример использования шаблона. Я создал проект и добавил два компонента BackgroundWorker. Один для работы без возможности отмены, другой с этой возможностью. Компонент BackgroundWorker очень удобно использовать при работе с многопоточностью. Он позволяет выполнять заданный код в отдельном потоке и корректно его завершать. Для быстрой разработки программ – вещь просто незаменимая.

При настройке компонента вам необходимо задать свойства WorkerReportsProgress и WorkerSupportCancellation. Первое позволяется использовать событие, которое должно возникнуть при изменении состояния, а второе – возможность прервать работу BackgroundWorker. В настройках событий следует задать все три событие, которые там расположены.

Для выполнения действий в коде пишем:

1 private void btnRun_Click(object sender, EventArgs e)
2 {
3     frmPrg = new frmProgress();
4     frmPrg.SetTitle("Проверка работы");
5     frmPrg.Owner = this;
6     frmPrg.Show();
7     frmPrg.IfCancel = false;
8     bgWorker.RunWorkerAsync();
9 }
10  
11 private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
12 {
13     for (int i = 0; i <= 100; i++)
14     {
15         frmPrg.SetInfo(i.ToString());
16         Thread.Sleep(100);
17         bgWorker.ReportProgress(1);
18     }
19 }
20  
21 private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
22 {
23     frmPrg.Increment();
24 }
25  
26 private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
27 {
28     if (frmPrg != null)
29         frmPrg.Close();
30 }
31  
32 private void btnRunCancel_Click(object sender, EventArgs e)
33 {
34     frmPrg = new frmProgress();
35     frmPrg.SetTitle("Проверка работы");
36     frmPrg.Owner = this;
37     frmPrg.Show();
38     cancel = false;
39     frmPrg.OnCancelWork += new CancelProgress(frmPrg_OnCancelWork);
40     bgWorkerCancel.RunWorkerAsync();
41 }
42  
43 protected void frmPrg_OnCancelWork()
44 {
45     cancel = true;
46     frmPrg.SetInfo("Ожидание отмены");
47 }
48  
49 bool cancel = false;
50 private void bgWorkerCancel_DoWork(object sender, DoWorkEventArgs e)
51 {
52     for (int i = 0; i <= 100; i++)
53     {
54         if (cancel)
55         {
56             bgWorkerCancel.CancelAsync();
57             break;
58         }
59         frmPrg.SetInfo(i.ToString());
60         Thread.Sleep(100);
61         bgWorkerCancel.ReportProgress(1);
62     }
63 }

В первом методе мы описываем создание формы прогресса и задаем необходимые свойства. Обратите внимание, что задается свойство Owner, чтобы форма прогресса была поверх родительской. Ей не требуется при этом задавать свойство TopMost. Далее выполняем bgWorker.RunWorkerAsync(), который выполнит метод bgWorker_DoWork в отдельном потоке. Т.к. код выполняем в отдельном потоке, то нам необходимо реализовать кросспотоковость, т.к. объект frmPrg (форма прогресса) создан в потоке главной формы. Для доступа к элементу можно использовать следующую конструкцию:

1 if (this.InvokeRequired)
2     BeginInvoke(new MethodInvoker(delegate
3     {
4            Text = text;
5     }));
6 else
7     Text = text;

if (this.InvokeRequired) – сначала проверяет нужно ли использовать BeginInvoke. BeginInvoke асинхронный вариант вызова Invoke и позволяет получить доступ к элементу из другого потока.

Если требуется изменить состояние, то вызывается метод bgWorker_ProgressChanged с помощью bgWorkerCancel.ReportProgress(1). В самом bgWorker_ProgressChanged описывается, что нужно сделать в этот момент. В данном случает увеличивается значение прогресс бара.

После того, как метод bgWorker_DoWork закончил работы (неважно успешно или нет), всегда выполняется метод bgWorker_RunWorkerCompleted. В данном случае у нас описан код, который закрывает форму с прогресс баром.

Заключение

Считаю, что с задачей создания шаблона формы прогресса мы справились. Надеюсь, что это форма поможет вам быстрее и качественнее разрабатывать свои программы. На момент написания статьи была выложена бета версия, которая требует глубокого тестирования. Если у вас будут дополнения и пожелания к выполненной работе, пишите.