sobota, 11 czerwca 2011

70-511: Enhancing Usability: Implementacja przetwarzania asynchronicznego (przykład) [PL]

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();
    }
  }
Promuj

Brak komentarzy:

Prześlij komentarz

Posty powiązane / Related posts