W ramach kontynuacji tematu rozpoczętego we wpisie „70-511: Enhancing Usability: Implementacja przetwarzania asynchronicznego (teoria)”, zapraszam do zapoznania się z przykładem kodu źródłowego, który będzie ilustracją do przedstawionej teorii (przykład kodu źródłowego dotyczy implementacji przetwarzania asynchronicznego, ze szczególnym naciskiem na to, jak jest to rozwiązane w WPF).W ramach przykładu zostanie pokazane wykorzystanie BacgroundWorker'a i Dispatcher'a.
W przykładzie zostanie wykorzystane proste okno z dwoma przyciskami (uruchamiającym i anulującym operację do wykonania w tle), paskiem postępu i etykietą pokazującą stan operacji. W kodzie XAML można to zapisać np. tak:
<Window x:Class="AsynchronousProcessing.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <StackPanel> <Button x:Name="Button_GO" Click="Button_GO_Click">Go</Button> <Button Content="Cancel" Height="23" Name="button_Cancel" Click="button_Cancel_Click" /> <ProgressBar Height="20" Name="progressBar1"/> <Label >Result:</Label> <Label Content="Label" Height="28" Name="label_result" /> </StackPanel> </Window>
W klasie okna powinniśmy zadeklarować obiekt BackgroundWorker'a,
np.:
System.ComponentModel.BackgroundWorker worker = new System.ComponentModel.BackgroundWorker();
który inicjujemy w konstruktorze okna:
worker.WorkerSupportsCancellation = true; worker.WorkerReportsProgress = true; worker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler( worker_RunWorkerCompleted ); worker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler( worker_ProgressChanged ); worker.DoWork += new System.ComponentModel.DoWorkEventHandler( worker_DoWork );
Przycisk Go będzie uruchamiał operację w tle
(worker.RunWorkerAsync();).
Przycisk Cancel będzie anulował operację (worker.CancelAsync();).
Pasek postępu będzie aktualizowany jako obsługa zdarzenia
ProgressChanged (progressBar1.Value
= e.ProgressPercentage;). Po zakończeniu operacji w tle
będziemy w etykiecie związane z rezultatem wpisywać rezultat lub
informację, że operacja była anulowana:
void worker_RunWorkerCompleted( object sender, System.ComponentModel.RunWorkerCompletedEventArgs e ) { if ( !e.Cancelled ) label_result.Content = e.Result; else label_result.Content = "canceled"; }
Po wykonaniu powyższej konfiguracji, czas na zapisanie kodu operacji w tle. W tym przypadku będzie to prosta pętla od 0 do 100, w której
będzie sprawdzany warunek anulowania, raportowany będzie postęp
oraz wykonywana będzie pauza na 100 ms:
void worker_DoWork( object sender, System.ComponentModel.DoWorkEventArgs e ) { for ( int i = 0; i < 100; i++ ) { if ( worker.CancellationPending ) { e.Cancel = true; break; } worker.ReportProgress( i ); System.Threading.Thread.Sleep( 100 ); } e.Result = "Done"; }
A co w przypadku, gdy chcielibyśmy w etykiecie rezultatu wypisywać
procent zaawansowania operacji? Można by było wykorzystać
zdarzenie ProgressChanged (i jest to zalecane podejście!),
ale można również ręcznie aktualizować etykietę bezpośrednio z
wątku BackgroundWorker'a, trzeba tylko wykorzystać
Disparcher'a. Można to zapisać w sposób następujący:
delegate void methodewithIntargument( int i ); private void UpdateLabel( int i ) { if ( label_result.Dispatcher.CheckAccess() ) label_result.Content = i; else label_result.Dispatcher.BeginInvoke( new methodewithIntargument( UpdateLabel ), new object[] { i } ); }
Ostatecznie kod całej klasy w tym przykładzie wygląda następująco:
public partial class MainWindow: Window { System.ComponentModel.BackgroundWorker worker = new System.ComponentModel.BackgroundWorker(); delegate void methodewithIntargument( int i ); public MainWindow() { InitializeComponent(); worker.WorkerSupportsCancellation = true; worker.WorkerReportsProgress = true; worker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler( worker_RunWorkerCompleted ); worker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler( worker_ProgressChanged ); worker.DoWork += new System.ComponentModel.DoWorkEventHandler( worker_DoWork ); } void worker_DoWork( object sender, System.ComponentModel.DoWorkEventArgs e ) { for ( int i = 0; i < 100; i++ ) { if ( worker.CancellationPending ) { e.Cancel = true; break; } UpdateLabel( i ); worker.ReportProgress( i ); System.Threading.Thread.Sleep( 100 ); } e.Result = "Done"; } private void UpdateLabel( int i ) { if ( label_result.Dispatcher.CheckAccess() ) label_result.Content = i; else label_result.Dispatcher.BeginInvoke( new methodewithIntargument( UpdateLabel ), new object[] { i } ); } void worker_ProgressChanged( object sender, System.ComponentModel.ProgressChangedEventArgs e ) { progressBar1.Value = e.ProgressPercentage; } void worker_RunWorkerCompleted( object sender, System.ComponentModel.RunWorkerCompletedEventArgs e ) { if ( !e.Cancelled ) label_result.Content = e.Result; else label_result.Content = "canceled"; } private void Button_GO_Click( object sender, RoutedEventArgs e ) { worker.RunWorkerAsync(); } private void button_Cancel_Click( object sender, RoutedEventArgs e ) { worker.CancelAsync(); } }
Brak komentarzy:
Prześlij komentarz