Оновлення 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 = AWSTab100: 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 = Server111: Port = Port112: Login = Login113: Password = Password114: 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.
Delegate context:
Task context:
WinDesktop context:
)
|
|