czwartek, 2 lutego 2012

[RX 11] Reactive Extensions, więcej o zdarzeniach, czyli przykłady z myszką [PL]

Niniejszy post jest kontynuacją cyklu o Reactive Extensions dla .NET ([RX 1], [RX 2], [RX 3], [RX 4], [RX 5], [RX 6], [RX 7], [RX 8], [RX9], [RX10]) i pojawią się w nim przykłady wykorzystania Reactive Extensions.

W ramach przykładu pokazane zostanie okno aplikacji, która w pasku statusu będzie wyświetlać, położenie kursora myszki w oknie aplikacji oraz informacja, czy kursor znajduje się w lewej, czy prawej części okna. Niniejszy przykład zostanie wykonany z wykorzystaniem WPF, należy więc pamiętać, że oprócz standardowej biblioteki Reactive Extensions (System.Reactive.dll) należy również dodać do projektu bibliotekę dedykowaną dla WPF (System.Reactive.Windows.Threading.dll).

Wspomniane okno można przygotować w XAML'u w sposób następujący:

<Window x:Class="RXExample_WpfApplication.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" Initialized="Window_Initialized">
    <DockPanel>
        <StatusBar DockPanel.Dock="Bottom" >
            <Label x:Name="label_mousemove" />
            <Label x:Name="label_LR" />
        </StatusBar>
        <Grid x:Name="grid"></Grid>
    </DockPanel>
</Window>

Niezbędnej inicjacji,w tym m. in. podłączenia się z wykorzystaniem Reactive Extension (Rx) do zdarzeń myszy oraz stworzenia odpowiednich subskrypcji, można dokonać w funkcji Window_Initialized.

Do zdarzenia związanego z ruchem myszą (MouseMove) podłączamy się jak w przedstawionym wcześniej przykładzie:

  var mouseMove = 
    Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>( h => this.MouseMove += h, h => this.MouseMove -= h );

Wyświetlenie pozycji jest proste i wystarczy dokonać subskrypcji:

  mouseMove
    .ObserveOn( DispatcherScheduler.Instance )
    .Subscribe( m => label_mousemove.Content = String.Format( "Mouse x={0},y={1}", m.EventArgs.GetPosition( this ).X, m.EventArgs.GetPosition( this ).Y ) );

Warto tutaj pamiętać o wskazaniu odpowiedniego planisty (ObserveOn( DispatcherScheduler.Instance )), który zaktualizuje nam naszą etykietę z właściwego wątku, obsługującego graficzny interfejs aplikacji.

Zauważmy, że przy powyższej konfiguracji obserwowalnej listy (strumienia danych związanego z ruchem myszy), powiadomienia otrzymujemy przy każdym najmniejszym ruchu myszą. Przy takim podejściu, w przypadku szybkiego ruchu myszą, podawane dane będą nieczytelne, a ich wyświetlanie może w znacznym stopniu pochłaniać cenne zasoby. Jak to rozwiązać? Otóż możemy być zainteresowani otrzymywaniem powiadomień o nowym położeniu kursora np. co 1s, wystarczy do tego wykorzystać funkcję Sample.

  var mouseMove =
    Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>( h => this.MouseMove += h, h => this.MouseMove -= h )
    .Sample( TimeSpan.FromSeconds( 1 ) );

Jeszcze ciekawszym rozwiązaniem może być informowanie o położeniu kursora, tylko w przypadku gdy kursor znajduje się w pewnym miejscu nieruchomo (funkcja Throttle):

  var mouseMove = 
    Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>( h => this.MouseMove += h, h => this.MouseMove -= h )
    .Throttle( TimeSpan.FromMilliseconds( 100 ) );

Pomyślcie jak łatwo można w ten sposób zrobić aplikację obsługującą mapę, nad którą poruszamy kursorem myszki i gdy ten jest nieruchomy, to wtedy wyświetlamy na mapie dookoła kursora dodatkowe informacje (np. punkty POI).

Zauważmy, że powyższe rozwiązania są możliwe dzięki prostym złożeniom. Pokazywałem to już wcześniej w części poświęconej LINQ, czyli bierzemy jedną obserwowalną kolekcję, używamy funkcji rozszerzającej (np. filtru) i tworzymy w ten sposób nową obserwowalną kolekcję. Jeśli już o LINQ mowa, zobaczmy jak można LINQ wykorzystać bardziej w tradycyjny sposób. Dla przykładu umieścimy na pasku statusu informację, w jakiej części okna (lewej, czy prawej) znajduje się kursor:

  double halfWidth = this.Width / 2;
  var polewo = from ev in mouseMove.ObserveOn( DispatcherScheduler.Instance )
                where ev.EventArgs.GetPosition( this ).X < halfWidth
                select ev;
  polewo.ObserveOn( DispatcherScheduler.Instance ).Subscribe( m => label_LR.Content = "lewo" );
  var poprawo = from ev in mouseMove.ObserveOn( DispatcherScheduler.Instance )
                where ev.EventArgs.GetPosition( this ).X >= halfWidth
                select ev;
  poprawo.ObserveOn( DispatcherScheduler.Instance ).Subscribe( m => label_LR.Content = "prawo" );

W następnej części artykułu powrócimy do tego przykładu i rozszerzymy go przykład kompozycji zdarzeń.

Promuj

Brak komentarzy:

Prześlij komentarz

Posty powiązane / Related posts