Reactive Extensions - WPF Uygulamaları

Bir WPF uygulaması iki thread ile ayağa kalkar; birincisi render işlemlerini yönetir diğeri (UI Thread) ise UI işlemlerini ( Event’lerin yönetilmesi, uygulama kodunun çalıştırılması, ekranın çizilmesi, girdilerin alınması ) yönetir. UI thread her iş öğesini dispatcher objesinde sıralar ve önceliklerine göre bu öğeleri tek bir thread ile çalıştırır. WPF’te UI elementlerinin (Button,ListBox,TextBlock,Grid vs) türedikleri sınıflara bakarsak karşımıza DispatcherObject çıkmaktadır. DispatcherObject, içerisinde ilgili UI elementini sahiplenen thread ile ilgili işlemler yapabileceğimiz “Dispatcher” property’si ve ilgili UI elementine müdahalede bulunan thread’lerin bu nesne üzerinde hakkı olup olmadığını gösteren VerifyAccess ve CheckAccess methodlarını barındırır.. 

Yani WPF’te main thread (UI Thread) üzerinde gerçekleştireceğimiz işlemler için bakmamız gereken yer dispatcher olacak. Reactive Extensions’ın WPF Helpers’ı çıkarma nedeni de main thread üzerinde gerçekleştireceğimiz işlemleri dispatcher üzerinden gerçekleştirmemizi sağlamasıdır.

Reactive Extensions kullanarak WPF üzerinde MVVM UI pattern’ini kullanarak basit bir uygulama yapıp reactive extensions’ın uygulama içerisinde bize ne gibi faydalar sağladığına bir bakalım. Uygulamamız adresini verdiğimiz bir rss içeriğinden sorgulama yapıp sonucu ekranda listeletecek. Arama işlemi yapılırken hedefimiz ekranda bir takılma olmaması olacak.

Yeni bir WPF projesi oluşturalım ve rx dll’lerini projemize ekleyelim. Nuget’ten reactive extensions yazıp “Reactive Extensions – WPF Helpers”’ı yükleyerek  dertsiz tasasız bu işlemi gerçekleştirebiliriz. Projemiz reactive extensions ile geliştirilmeye müsait hale geldiğine göre geliştirmeye başlayabiliriz.

İlk önce rss’ten veriyi çekecek olan sınıfımızı ve bu sınıfımızda kullacağımız Feed entity’sini yazarak işe başlayalım.

public class Feed

{

    public string Title { get; set; }

    public string Description { get; set; }

    public override string ToString()

    {

        return Title;

    }

}

public class RssService

{

    public string RssUrl { get; set; }

   
   
public RssService(string feedUrl)

    {

        RssUrl = feedUrl;

    }

    public List<Feed> GetRssFeeds(string searchText)

    {

        var rssFeed = XDocument.Load(RssUrl);

        
       
var feeds = (from item in rssFeed.Descendants("item")

                     where item.Element("title").Value.ToUpperInvariant().Contains(searchText.ToUpperInvariant())

                     select new Feed()

                     {

                         Title = item.Element("title").Value,

                         Description = item.Element("description").Value

                     }).ToList<Feed>();

        return feeds;

    }

}

Bu iki class’ımızı hazırladıktan sonra WPF tarafına geçip view model’ımızı hazırlayabiliriz. View model’ımızda;

Feed türünden SelectedItem: ListBox’ta seçilen satırın detayları görebilmemiz için
String türünden RssAddress: RSS içeriğimizin url’ini gireceğimiz textBox ile ilişkilendireceğiz.
String türünden SearchText: Kullanıcının arama kriterini gireceği textbox ile ilişkilendireceğiz.
ObservableCollection<Feed> türünden RSSResults: Arama sonuçlarını göstereceğimiz listbox ile ilişkilendireceğiz.

Hazırladığımız RssService class’ını kullanarak arama yapan ve geriye List<Feed> dönen SearchRss metodumuzu yazalım;

private List<Feed> SearchRss(string searchTerm)

{

    var rssService = new RssService(RssAddress);

    var rssList = rssService.GetRssFeeds(searchTerm);

    return rssList;

}

İhtiyaçlarımızın büyük çoğunluğunu tamamladık. Artık Reactive Extensions’ı kullanmaya başlayabiliriz. MVVM pattern’ini kullandığımız için UI elementlerine direkt erişemiyoruz, bu nedenle oluşturduğumuz “SearchText” property’si her değiştiğinde bize haber verecek bir event oluşturarak işe başlayalım. Event’imizi aşağıdaki gibi tanımlayabiliriz;

public event PropertyChangedEventHandler PropertyChanged;

View Model’ımızın constructor’ında bu işlemi şöyle gerçekleştirebiliriz;

var rssResults = from searchValue in (
                     Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(

                         ev => PropertyChanged += ev,

                         ev => PropertyChanged -= ev)

                     .Where(ev => ev.EventArgs.PropertyName == "SearchText"))

 FromEventPattern metodu generic olarak iki tip alır biri EventHandler’ın türü diğeri ise EventArgs’ın türü. Action olarak handler’ı ekleme ve çıkmarma metodlarını alır. Eğer Where methodunu yazmazsak PropertyChanged event’ımız tüm property’ler için geçerli olacaktır, where koşulu ile sadece SearchText property’si için geçerli olduğunu söylüyoruz.
Eğer event’in SearchText property’si değişir değişmez tetiklenmesini değil de property değiştikten bir süre sonra tetiklenmesini istiyorsak Throttle eklememiz gerek. Throttle metoduna vereceğimiz süre kadar bekledikten sonra event tetiklenecek.
Where metod’undan hemen sonra .Throttle(TimeSpan.FromMilliseconds(3000)) diyerek 3 saniye bekledikten sonra event’imizin tetiklenmesini sağlayabiliriz.
.Select(arg => SearchText)) ile de args olarak gelen nesnenin direkt girilen text olmasını sağlıyoruz. Eğer bunu yazmasaydık searchText’in türü PropertyChangedEventArgs türünde olacaktı, ((ReadRSSViewModel)searchText.Sender).SearchText şeklinde bir kullanım ile SearchText property’sine erişmemiz gerekecekti.
SearchRss adında geriye List<Feed> dönen ve parametre olarak da string olarak aranacak karakterleri alan bir metodumuz vardı.  Bu methodu linq query’sinde direkt kullanırsak subscribe methodumuz her eleman için çalışacak. Burada şöyle bir sorun oluşuyor; bizim linq sorgumuzun sonucu IObservable<Feed>  ancak IObservable PropertyChanged ile ilişkilendirdiğimiz için hiçbir zaman OnCompleted’a girmeyecek çünkü event sürekli aktif. OnCompleted’a girmediği için de biz listemizin içeriğini her arama öncesi temizlememiz için Take ve Finally methodlarını kullanıp bunu biz elle yönetebiliriz. Take gelen sonuçtan kaç tanesini alacağımızı ayarladığımız method, gelen sonucun sayısını da almak için SearchRss methodundan dönen sonuç sayısını global bir değişkene atamamız gerekecek. Yani biraz uğraşmamız gerecek, bunun yerine geri dönüş değerimiz IObservable<List<Feed>> olursa Subscribe List<Feed> türünden tek bir değer gelecek ve biz burada listemizi temizleyip yeni gelen değerlerle doldurabiliriz. Bunu yapabilmemiz için reactive extensions’ın Observable.ToAsync methodunu kullanarak bir Func oluşturacağız;

Func<string, IObservable<List<Feed>>> search = Observable.ToAsync<string, List<Feed>>(SearchRss);

Artık sorgumuzda SearchRss yerine “search” Func’ı kullanabiliriz.
from result in search(searchValue)

                select result;
linq sorgumuz yaptığımız son değişiklikten sonra şu hale gelmiş oldu;

IObservable<List<Feed>> observableResults =

                from searchValue in (

       Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(

                        ev => PropertyChanged += ev,

                        ev => PropertyChanged -= ev)

                    .Where(ev => ev.EventArgs.PropertyName == "SearchText")

                    .Throttle(TimeSpan.FromMilliseconds(3000))

                    .Select(arg => SearchText))

                from result in search(searchValue)

                select result;

 

Artık elimizde olan observableResults’a subscribe olup sonucu RSSResults’a aktarabiliriz. RSSResults’ı view’daki bir UI element ile ilişkilendireceğimiz için bu işlemin UI thread’te gerçekleşmesini sağlamalıyız ki DispatcherObject’in VerifyAccess ve CheckAccess metodları bu işlemi yapmamıza engel olmasınlar. Dediklerimizi koda dökecek olursak şöyle bir işlem gerçekleştirmemiz gerekecek;

observableResults.ObserveOn(DispatcherScheduler.Current)
                      .Subscribe(result =>

                      {

                          RSSResults.Clear();

                          result.ForEach(feed => RSSResults.Add(feed));

                      });

ObserveOn ile UI thread üzerinde çalışacağını söylüyoruz ve Subscribe ile de gelen sonucu RSSResults’a aktarıyoruz.

View model’ımızı böylelikle tamamlamış oluyoruz, artık yapmamız gereken tek şey view model’ımızı view’a static resource olarak ekleyip gerekli elementlere bind etmemiz gerekecek. Bunları da XAML tarafında halledeceğiz.

Uygulamanın XAML tarafı aşağıdaki gibidir.

<Window x:Class="WPFRxRss.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:local="clr-namespace:WPFRxRss"

        Title="Reactive Extensions: RSS Reader" Height="334" Width="572"

        MinHeight="334" MinWidth="572" MaxHeight="334" MaxWidth="572"

        >

    <Window.Resources>

        <local:ReadRSSViewModel x:Key="ViewModel"></local:ReadRSSViewModel>

    </Window.Resources>

    <Grid DataContext="{StaticResource ViewModel}" Margin="0,0,2,-1">

        <Label Content="Rss Adresi:" HorizontalAlignment="Left" Margin="10,1,0,0" VerticalAlignment="Top" Width="69" Height="27"/>

        <TextBox Text="{Binding RssAddress}" Margin="84,1,10,276" Grid.ColumnSpan="2" />

        <TextBox x:Name="SearchText" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" Margin="10,42,334,234" />

        <ListBox ItemsSource="{Binding RSSResults}" SelectedItem="{Binding SelectedItem}" Margin="10,75,334,22" />

        <TextBlock HorizontalAlignment="Left" Margin="233,33,0,0" TextWrapping="Wrap" Text="{Binding SelectedItem.Title}" VerticalAlignment="Top" Height="54" Width="319" Grid.ColumnSpan="2"/>

        <TextBox VerticalScrollBarVisibility="Auto" IsReadOnly="True" HorizontalAlignment="Left" Margin="233,92,0,0" TextWrapping="Wrap" Text="{Binding SelectedItem.Description}" VerticalAlignment="Top" Height="181" Width="319"/>

    </Grid>

</Window>

 

Bu uygulama ile WPF ile MVVM uyguladığımız projemizde Reactive Extensions’ı nasıl kullanacağımıza değinmiş olduk. 
Örnek projeyi indirmek için tıklayın.

reactiveextensionswpfxamlcsharp
baris ozaydin tarafından yazıldı.

Yorumlar

17 Şub, 2017 01:01

Thank you for the sensible critique. Me & my neighbour were preparing to do some research about that. We got a good book on that matter from our local library and most books where not as influensive as your information. I am very glad to see such informat

Yorumlarınız

Yorum

Önizleme