Оновлення StatusLabel з потоку BackGroundWorker - приклад застосування Action, Delegate, Invoke, AddressOf, Extension, Expression.
У більшості своїх десктопних програм я звик використовувати StatusLabel, у якої я відображаю поточний статут операції. Цю StatusLabel ви бачите на скринах вище. Наразі мені прийшло на думку, що це дуже цікавий приклад застосування багатьох конструкцій Бейсіку у декількох стрічках коду. Це може бути цікавим прикладом для починаючих вивчати Бейсік.
Неможна просто написати StatusLabel.text="start", а потім StatusLabel.text="end", тому давайте розберемо крок за кроком усі компоненти коду, які дозволяють відображати на формі поточний статут будь-якої операції, тобто вивести на форму буль-яке інформаційне повідомлення.
У зв'язку з тим що Windows-форма просто підвисає, коли щось виконується у тому ж потоці, тому звичайно для будь-яких операцій довше однієї секунди використовується BackGroundWorker, але справа в тому, что Windows-форма руйнується, коли на неї щось написати з іншого потоку.
- Отже перша частина коду оновлення статуту буже Extension-функция. Вона розміщується обов'язково у окремому файлі типа MODULE та звичайно виглядає ось так. Як самому написати ці єктеншени? Це дуже просто, Ці функції додаються до статичного блоку функцій і поширюють набор функцій того об'єкта, який перерахований першим у параметрах. Це просто умовність компілятора, про яку потрібно пам'ятати. У нашому випадку ці функція буде ось така.
Зверніть увагу на цікавості цієї функції - що таке Acrion, що таке Extension, що таке Invoke.
1: Imports System
2: Imports System.Windows.Forms
3: Imports System.Runtime.CompilerServices
4:
5: Module ControlExtensions
6: <Extension()>
7: Sub InvokeOnUiThreadIfRequired(ByVal control As Control, ByVal action As Action)
8: If control.InvokeRequired Then
9: control.BeginInvoke(action)
10: Else
11: action.Invoke()
12: End If
13: End Sub
14: End Module
- Наступний компонент нашого пазлу - це звичайна публічна функція, яка надає доступ до extension-функції. Це буде звичайна функція екземпляру класу форми, а не якесь статичне поширення класу контролів, як попередня функція. Цю функцію UpdateLabel можливо викликати з коду іншої форми, іншого класу, дати їй якийсь текст та він з'явиться на StatusLabel.
Побудована ця функція вона дуже цікаво, за допомогою Expression, це дуже цікава річ, це не код, а лише нібито шаблон коду, нагадує Generic-класи, але для окремої функції.
47: Public Sub UpdateLabel(ProgressTXT As String)
48: Me.InvokeOnUiThreadIfRequired(Sub() StatusLabel.Text = ProgressTXT)
49: Me.InvokeOnUiThreadIfRequired(Sub() StatusStrip1.Refresh())
50: End Sub
- Наступний крок цього пазлу - BackGroundWorker, який використовується, щоб поток GUI не підвісав. У данному випадку у мене працює два BackGroundWorker'а один за одним. Початок та кінець у ньому працює у потоці форми, а от DoWork працює вже у іншому потоці. Найбільш цікавим у цьому коді є передача адресу функції UpdateLabel через оператор AddressOf, саме так задається будь-який власний callback-хандлер для якогось зовнішнього алгоритму.
71: Dim WithEvents BGW1 As BackgroundWorker
72: Dim BGW1_Prm As New Object
73: Dim WithEvents BGW2 As BackgroundWorker
74: Dim BGW2_Prm As New Object
75:
76: Private Sub ReadEmailButton_Click(sender As Object, e As EventArgs) Handles ReadEmailButton.Click
77: BGW1 = New BackgroundWorker
78: BGW1_Prm = New With {.Server = ServerTextBox1.Text, .Port = PortNumericUpDown1.Value, .Login = LoginTextBox1.Text, .Pass = PasswordTextBox1.Text}
79: BGW1.RunWorkerAsync(BGW1_Prm)
80: End Sub
81:
82: Private Sub BGW1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BGW1.DoWork
83: Dim Box1 As New MyMailClient(e.Argument.Server, e.Argument.Port, e.Argument.Login, e.Argument.Pass)
84: Box1.SaveMessageFromInbox(SelectedPath, DateTimePickerBox1.Value, AddressOf UpdateLabel)
85: End Sub
86:
87: Private Sub BGW1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BGW1.RunWorkerCompleted
88: BGW2 = New BackgroundWorker
89: BGW2_Prm = New With {.Server = ServerTextBox2.Text, .Port = PortNumericUpDown2.Value, .Login = LoginTextBox2.Text, .Pass = PasswordTextBox2.Text}
90: BGW2.RunWorkerAsync(BGW1_Prm)
91: End Sub
92:
93: Private Sub BGW2_DoWork(sender As Object, e As DoWorkEventArgs) Handles BGW2.DoWork
94: Dim Box1 As New MyMailClient(e.Argument.Server, e.Argument.Port, e.Argument.Login, e.Argument.Pass)
95: Box1.SaveMessageFromInbox(SelectedPath, DateTimePickerBox2.Value, AddressOf UpdateLabel)
96: End Sub
97:
98: Private Sub BGW2_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BGW2.RunWorkerCompleted
99: TabControl1.SelectedTab = AWSTab
100: End Sub
- Тепер переходимо до зовнішньої програми, яка вже викликається у іншому потоці, який нам зробив BackGroundWorker, та називається у мене SaveMessageFromInbox. I тут є багато цікавостей, по-перше, як прийняти параметр AddressOf у функції? А приймається він, як Delegate, а по-друге, як його викликати, а викликається він за допомогою Invoke.
1: Public Delegate Sub ShowProcess(ProgressTXT As String)
2:
3: Public Class MyMailClient
4: Property Server As String
5: Property Port As Integer
6: Property Login As String
7: Property Password As String
8:
9: Public Sub New(Server1 As String, Port1 As Integer, Login1 As String, Password1 As String)
10: Server = Server1
11: Port = Port1
12: Login = Login1
13: Password = Password1
14: End Sub
15:
16: Public Sub SaveMessageFromInbox(Directory As String, FromDate As Date, ShowProcess As ShowProcess)
17: ShowProcess.Invoke("Start: " & Server)
18: Try
19: Using client = New ImapClient()
..:
49: ShowProcess.Invoke(Left(Message.Subject, 50))
..:
55: ShowProcess.Invoke("Done: " & Server)
56: End Using
57: Catch ex As Exception
Отже, давайте підсумуємо - щоб вивести на форму статут Start/Done нам потрібно було грамотно використати такі мовно-сінтаксичні можливості Бейсіку як Action, Delegate, Invoke, AddressOf, Extension, Expression. А також зрозуміти як працює BackGroundWorker.
|