Makale Özeti

Bu yazımızda Silverlight 2.0 içerisinde bir AutoComplete çözümü olan Silverlight Toolkit paketinden AutoCompleteBox'ın özelliklerini ve özelleştirilerek kullanımını inceliyoruz.

Makale

AutoComplete işlevselliği AJAX günlerinden alışık olduğumuz bir sistem. Herhangi bir TextBox'a kullanıcı yazı yazarken aynı anda uygun alternatifleri göstermek ve aslında arka planda bir arama sistemi kurmak gibi işlemleri uzun zamandır farklı arayüz araçları kullansak da bir şekilde programcılar olarak hazırlayabiliyoruz. Silverlight tarafında ise Silverlight'ın görsel gücünden de faydalanarak çok ilginç çözümler üretmek mümkün. Silverlight dünyasında AutoComplete altyapılarını incelerken Silverlight Toolkit içerisindeki AutoCompleteBox kontrolünü kullanacağız.

Not: Silverlight Toolkit'i kullanabilmeniz için CodePlex üzerindeki adresten kütüphaneyi indirerek içerisindeki Microsoft.Windows.Controls.dll dosyasını projenize referans olarak eklemelisiniz.

En hızlı şekilde AutoCompleteBox kullanımı..

AutoCompleteBox'ın kullanımı aslında çok basit. Hemen bir String Array veya List yaratarak AutoCompleteBox'a bind etmeniz yeterli olacaktır. Tüm filtreleme ve AutoComplete işlemleri otomatik olarak yapılacaktır.

[VB]

    Private Sub Page_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        Dim Liste As New List(Of String)

        Liste.Add("ASP.NET")

        Liste.Add("AJAX")

        Liste.Add("Silverlight")

        Liste.Add("WPF")

        AutoComplete1.ItemsSource = Liste

    End Sub

[C#]

        void Page_Loaded(object sender, RoutedEventArgs e)

        {

                List<String> Liste = new List<string>();

                Liste.Add("ASP.NET");

                Liste.Add("AJAX");

                Liste.Add("Silverlight");

                Liste.Add("WPF");

                AutoComplete1.ItemsSource = Liste;

        }

Yukarıdaki kod ile hazırladığımız basit bir uygulamanın aşağıda da XAML kodunu inceleyebilirsiniz.

[XAML]

<UserControl x:Class="SilverlightApplication3.Page"

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

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

  Width="400" Height="300" xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls">

    <Grid x:Name="LayoutRoot" Background="White">

      <controls:AutoCompleteBox Height="24" Margin="24,24,144,0" VerticalAlignment="Top" x:Name="AutoComplete1"/>

    </Grid>

</UserControl>

Basit bir AutoComplete örneği.
Basit bir AutoComplete örneği.

AutoCompleteBox'ın özellikleri.

IsTextCompletionEnabled - Bu özellik açık olduğunda kullanıcı yazı yazdıkça sadece alternatifler gösterilmez, ek olarak en yakın alternatif sanki yazılmış gibi TextBox içerisinde de gösterilir. Varsayılan değeri True şeklindedir.

SelectedItem - Eğer kullanıcı AutoCompleteBox'ın açılan ListBox kısmından bir öğe seçerse SelectedItemChanged çalışır ve SelectedItem geriye bir Item döndürür. Kullanıcı ListBox içerisinde yer alan bir Item'ın metnini elle yazmışsa SelectedItem geriye değer döndürmeyecektir.

SearchMode - AutoComplete işlemi yapılırken kaynak veride ne şekilde arama yapılacağına karar veren SearchMode özelliği varsayılan değeri olan StartsWith ile gelir. İsterseniz Contains seçeneğini seçerek doğrudan kaynak verinin içindeki tüm metinlerin içinde arama yapılmasını da sağlayabilirsiniz. Eğer kendi arama sisteminizi entegre edecekseniz None seçeneğini seçmeniz gerekecektir.

MinimumPopulateDelay - Kullanıcı yazı yazarken ne kadar süre sonra alternatiflerin gösterileceğini belirler. Eğer veri kaynağı istemci tarafında bir Silverlight değişkeni ise varsayılan değer olan 0 ile herhangi bir sorun yaşamazsınız. Fakat alternatifleri sunucudan her seferinde çekiyorsanız buradaki bekleme süresini uzatmakta büyük fayda olacaktır.

MinimumPrefixLength - Alternatifler gösterilmeden önce kaç karakterlik verinin girilmiş olması gerektiğine dair ayar bu özellik üzerinden yapılabilir.

Kendi filtreleme mekanizmamızı yazalım.

Bir AutoCompleteBox'ı aslında iki şekilde veri bağlamış olabiliriz. Bunlardan birincisi yazımızın başındaki gibi basit bir metin dizisini AutoCompleteBox'a aktarmak ikincisi ise kendi tanımladığımız nesnelerin bir dizisini bağlamak. Gelin her iki durumda da filtreleme işlemlerini nasıl özelleştirebileceğimize bakalım.

Bir önceki örneğimizin üzerinden yola devam edersek zaten hali hazırda bir String listesini alıp AutoCompleteBox'ımıza bağlamıştık. AutoCompleteBox'ın TextFilter özelliğini değiştirirek filtreleme işleminin tam olarak nasıl yapılacağına karar verebiliriz. Bizim yapacağımız örnekte kullanıcının yazdığı metin ile başlayan değil de biten öğeleri göstermeye çalışacağız.

[VB]

    Function Filtreleme(ByVal Search As String, ByVal item As String)

        If item.ToString.EndsWith(Search) Then

            Return True

        Else

            Return False

        End If

    End Function

[C#]

        public bool Filtreleme(string Search, string item)

        {

            if (item.ToString().EndsWith(Search)) {

                return true;

            }

            else {

                return false;

            }

        }

Yukarıdaki fonksiyonumuzu filtreleme işlemlerini yapmak için kullanacağız. Gelen iki parametreden ilki kullanıcının TextBox içerisine yazdığı metin, ikincisi ise o an fonksiyonumuza aktarılan ana kaynak veriden gelen bir Item. Burada kafalar biraz karışabilir o nedenle biraz daha detaya inelim. AutoCompleteBox filtreleme işlemini yaparken elindeki verinin içindeki her bir öğeyi tek tek bizim filtreleme fonksiyonumuza verecek ve söz konusu öğenin gösterilip gösterilmeyeceğine dair bir cevap bekleyecek. Yani eğer veri kaynağında 10 adet öğe varsa bu fonksiyon 10 defa çağrılacak.

[VB]

    Private Sub Page_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        Dim Liste As New List(Of String)

        Liste.Add("ASP.NET")

        Liste.Add("AJAX")

        Liste.Add("Silverlight")

        Liste.Add("WPF")

 

        AutoComplete1.TextFilter = New Microsoft.Windows.Controls.AutoCompleteSearchPredicate(Of String)(AddressOf Filtreleme)

        AutoComplete1.ItemsSource = Liste

    End Sub

[C#]

        void Page_Loaded(object sender, RoutedEventArgs e)

        {

                List<String> Liste = new List<string>();

                Liste.Add("ASP.NET");

                Liste.Add("AJAX");

                Liste.Add("Silverlight");

                Liste.Add("WPF");

 

                AutoComplete1.TextFilter = new Microsoft.Windows.Controls.AutoCompleteSearchPredicate<string>(Filtreleme);

                AutoComplete1.ItemsSource = Liste;

        }

Hazırladığımız filtreleme fonksiyonunu tabi ki AutoCompleteBox'ın TextFilter'ına da aktarmamız gerek. Yukarıdaki kodlardaki kalın satırlarda söz konusu aktarma işleminin nasıl yapıldığını inceleyebilirsiniz. TextFilter bizden bir AutoCompleteSearchPredicate istiyor, biz de kendisine istediğini veriyoruz :)

Peki ya AutoCompleteBox'a kendi yarattığımız nesne türlerinden bir liste bağlamış olsaydık bu filtreleme işlemini nasıl yapacaktık. Böyle bir durumda TextFilter yerine ItemFilter'ı kullanmak zorunda kalacaktık. Gelin ilk olarak örneğimizde ilerleyebilmek için Kitap adında kendi sınıfımızı tanımlayalım.

[VB]

    Public Class Kitap

 

        Private PAdi As String

        Public Property Adi() As String

            Get

                Return PAdi

            End Get

            Set(ByVal value As String)

                PAdi = value

            End Set

        End Property

 

        Private PFiyat As Double

        Public Property Fiyat() As Double

            Get

                Return PFiyat

            End Get

            Set(ByVal value As Double)

                PFiyat = value

            End Set

        End Property

 

    End Class

[C#]

        public class Kitap

        {

            public string Adi { get; set; }

            public double Fiyat { get; set; }

        }

Şimdi bu sınıflar üzerinden yola çıkarak Silverlight tarafında birkaç kitap yaratıp AutoCompleteBox'larımıza DataBind edeceğiz.

[VB]

    Private Sub Page_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded

        Dim Liste As New List(Of Kitap)

        Liste.Add(New Kitap With {.Adi = "ASP.NET AJAX", .Fiyat = 25})

        Liste.Add(New Kitap With {.Adi = "ADO.NET", .Fiyat = 35})

        Liste.Add(New Kitap With {.Adi = "WPF", .Fiyat = 15})

        Liste.Add(New Kitap With {.Adi = "Silverlight", .Fiyat = 25})

 

        AutoComplete1.ItemFilter = New Microsoft.Windows.Controls.AutoCompleteSearchPredicate(Of Object)(AddressOf Filtreleme)

 

        AutoComplete1.ItemsSource = Liste

    End Sub

[C#]

        void Page_Loaded(object sender, RoutedEventArgs e)

        {

 

                  List<Kitap> Liste = new List<Kitap>();

        Liste.Add(new Kitap  {Adi = "ASP.NET AJAX", Fiyat = 25});

        Liste.Add(new Kitap  {Adi = "ADO.NET", Fiyat = 35});

        Liste.Add(new Kitap  {Adi = "WPF", Fiyat = 15});

        Liste.Add(new Kitap  {Adi = "Silverlight", Fiyat = 25});

 

        AutoComplete1.ItemFilter = new Microsoft.Windows.Controls.AutoCompleteSearchPredicate<Object>(Filtreleme);

 

        AutoComplete1.ItemsSource = Liste;

        }

Kodumuz içerisinde çok büyük değişiklikler yok. Bu sefer bir String listesi yerine Kitap listesi yaratıyor ve AutoCompleteBox'ın ItemsSource'una eşitliyoruz. Filtreleme işlemi için yine Filtreleme adında bir metod kullanacağımız için ItemFilter özeliğine de söz konusu metodu bağlıyoruz. Aradaki en önemli fark elimizdeki Predicate'in artık Object türünü taşıyor olması.

[VB]

    Function Filtreleme(ByVal Search As String, ByVal item As Object)

        If CType(item, Kitap).Fiyat < CInt(Search) Then

            Return True

        Else

            Return False

        End If

    End Function

[C#]

        public bool Filtreleme(string Search, object item)

        {

            if (((Kitap)item).Fiyat < int.Parse(Search)) {

                return true;

            }

            else {

                return false;

            }

        }

Filtreleme metodumuzun ikinci parametresi artık Object tipinde. Biz aslında buradaki değişkenin bir Kitap nesnesi olduğunu biliyoruz çünkü AutoCompleteBox tek tek kendisine verilen öğeleri bu filtreleme fonksiyonuna iletecektir. Bu nedenle elimizde gelen objeyi Kitap tipine cast edip bu sefer de kitapların fiyatları üzerinden filtreleme yapıyoruz. Kullanıcıların TextBox içerisinde sayısal bir fiyat gireceğini ve bu fiyatın altındaki kitapların listeleneceğini varsayalım.

Bu şekilde uygulamamızı çalıştırırsak ufak bir karışıklık olacaktır. AutoCompleteBox'a bağladığımız veri Kitap'lardan oluşuyor. Peki AutoCompleteBox bu Kitap nesnelerinin hangi özelliğini ekrana nasıl getirecek? Örneğin kitabın adını mı yoksa fiyatını mı gösterecek AutoComplete esnasında? İşte bu noktada da bizim XAML tarafına geçip birkaç işlem yapmamız gerek.

[XAML]

<UserControl x:Class="SilverlightApplication3.Page"

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

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

  Width="400" Height="300" xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls">

  <UserControl.Resources>

    <DataTemplate x:Key="DataTemplate1">

      <Grid>

        <TextBlock HorizontalAlignment="Left" VerticalAlignment="Top" Text="{Binding Path=Fiyat}" TextWrapping="Wrap"/>

      </Grid>

    </DataTemplate>

  </UserControl.Resources>

    <Grid x:Name="LayoutRoot" Background="White">

      <controls:AutoCompleteBox Height="24" Margin="24,24,144,0" VerticalAlignment="Top" x:Name="AutoComplete1" ItemTemplate="{StaticResource DataTemplate1}"/>

    </Grid>

</UserControl>

Artık bizim AutoCompleteBox'ımızın ItemTemplate'ini değiştirmemiz ve özelleştirmemiz gerekiyor. Böylece tam olarak gelen veriden hangi öğelerin nerelerde nasıl gösterileceğine karar verebiliriz. Yukarıdaki kod içerisinde AutoCompleteBox'ın ItemTemplate özelliğinin değiştirildiğini görebilirsiniz. DataTemplate1 adında yarattığımız DataTemplate'i UserControl.Resources içerisine koyup kullanabiliyoruz. DataTemplate içerisinde ise bir Grid ve TextBlock bulunuyor. Söz konusu TextBlock doğrudan Fiyat adında bir Field'e DataBind edilmiş durumda. Hatırlarsanız bizim Kitap sınıfımızın Fiyat adında bir Property'si vardı. Böylece AutoCompleteBox'a kendisine verilen verilerden her birinin Fiyat özelliğinin DataTemplate içerisindeki bu TextBlock'un Text'ine bağlanmasını sağladık. Tüm bu işlemleri Visual Studio içerisinde XAML yazarak yapabileceğiniz gibi isterseniz Expression Blend'in arayüzünden de tabi ki daha rahatlıkla yapabilirsiniz. Tek yapmanız gereken AutoCompleteBox'a sağ tuş tıklayıp gelen menüden "Edit Other Templates / Edit ItemTemplate" demek. Böylece tasarım modunda Blend içerisinde de DataTemplate'in görselliğini değiştirebilirsiniz.

Artık filtreleme işlemlerini bitirdik, filtreleme esnasından Kitapların sadece fiyatlarının gözükmesini de sağladık. Fakat neden sadece fiyatları gözüksün ki? Kullanıcı TextBox içerisinde 25 yazdığınzda 25YTL'den ucuz kitapların hem adı hem fiyatı yazsa? Ve bunların arasından bir kitap seçilince fiyatı otomatik olarak TextBlock içerisine yerleşse daha güzel olmaz mı? AutoCompleteBox'ın açılan kısmının görselliğini ne de olsa yukarıda değiştirmiştik, gelin şimdi ikinci bir TextBlock ekleyerek onu da Kitap nesnelerinin Adi özelliğine bağlayalım.

[XAML]

    <DataTemplate x:Key="DataTemplate1">

      <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{x:Null}" Orientation="Horizontal">

        <TextBlock Text="{Binding Path=Adi}" Foreground="#FFFF0000" Height="Auto" Width="Auto"/>

        <TextBlock Text="{Binding Path=Fiyat}" Foreground="#FF838383" Height="Auto" Width="Auto"/>

        <TextBlock Text="YTL" TextWrapping="Wrap" Foreground="#FF838383" Height="20" Width="20"/>

      </StackPanel>

    </DataTemplate>

Gördüğünüz gibi DataTemplate'i değiştirerek aslında işimizi çözmüş olduk. Geriye tek bir sorun kalıyor. Kullanıcı herhangi bir Item'ı ListBox içerisinden seçtiğinde TextBox'ın içine ne yazılacak? Kitap nesnesinin Adi mı? yoksa Fiyatı mı? Tabi ki bizim örneğimizde Kitap nesnesinin fiyatı yazılacak aksi halde filtreleme mekanizmamızla çakışacaktır.

[VB]

  Public Class Kitap

 

        Private PAdi As String

        Public Property Adi() As String

            Get

                Return PAdi

            End Get

            Set(ByVal value As String)

                PAdi = value

            End Set

        End Property

 

        Private PFiyat As Double

        Public Property Fiyat() As Double

            Get

                Return PFiyat

            End Get

            Set(ByVal value As Double)

                PFiyat = value

            End Set

        End Property

 

        Public Overrides Function ToString() As String

            Return Me.Fiyat

        End Function

 

    End Class

[C#]

        public class Kitap

        {

            public string Adi { get; set; }

            public double Fiyat { get; set; }

 

            public override string ToString()

            {

                return this.Fiyat.ToString();

            }

        }

Bu da ne şimdi? dediğinizi duyar gibiyim. Maalesef AutoCompleteBox'ın ListBox'tan seçili öğelerin hangi özelliklerinin alınacağına dair bir ayarı yok. Aslında arka planda AutoCompleteBox kendi içerisinde bir öğe seçildiğinde o öğeyi ToString() metodu ile alıp TextBox içerisine yerleştiriyor. Bu durumda bizim de ToString metodunu Override etmemiz her şeyi çözecektir. Artık herhangi bir Kitap nesnesi üzerinden ToString çalıştırılırsa kitabın fiyat bilgisi verilecek.

Özelleştirilmiş bir AutoCompleteBox örneği.
Özelleştirilmiş bir AutoCompleteBox örneği.

Hepinize kolay gelsin.