Makale Özeti

Bu yazımızda Silverlight 4 Beta ile beraber Combobox'a gelen basit bir özelliğin DataBinding senaryolarında Silverlight 3'e kıyasla nasıl bir değişiklik yarattığını inceliyoruz.

Makale

Silverlight 4 ile beraber gelen yenilikleri incelemeye başlayacağımız bu makalede değinmek istediğimiz nokta biraz veri uygulamalarına yönelik olacak. Aslına bakıldığında söz konusu yenilik çok basit gibi gözükse de günü birlik silverlight veri uygulamaları geliştirme sürecinde sürekli karşılaştığımız ve canımızı sıkan önemli bir sorunu çözmek için çok değerli. Gelin hep beraber neden bahsettiğime bir göz atalım.

Bir DataGrid içerisinde Combobox sorunsalı!

Bunun neresi sorun yaratıyor diyebilirsiniz. Fakat veritabanında tuttuğumuz değerleri entity tasarımımıza doğrudan yansıttığımızda (ki Visual Studio içerisinde çoğu RAD (Rapid Application Development) aracı bunu öngörür) Silverlight tarafındaki DataBing mekanizmalarında kıl bir durum ile karşılaşıyorduk. Senaryoyu cümlelerle tanımlamak yerine örnek üzerinden gitmeyi daha uygun görüyorum ;)

Örnek veritabanı tasarımı.
Örnek veritabanı tasarımı.

Yukarıda gördüğünüz şekilde veritabanımızda iki tablo var. Bu tablolar birbirine TipID üzerinden bağlı. Yani her Insan'ın bir tipi var ve bu tipler de ayrı bir tabloda FK (Foreign Key) ile bağlı. Malum bu tasarım bizim veritabanı tasarımlarımızın en ufak yapıtaşını temsil edebilir. Böyle minik bir tasarımdan yola çıkarak doğrudan Entity'leri yaratarak ilerlediğimizde bakalım Silverlight içerisinde bir DataGrid ile insanları nasıl gösterebileceğiz?

[VB]

<ServiceContract(Namespace:="")>

<AspNetCompatibilityRequirements(RequirementsMode:=AspNetCompatibilityRequirementsMode.Allowed)>

Public Class Service1

 

    Dim Veri As New DataClasses1DataContext

 

    <OperationContract()>

    Public Function InsanlarGetir() As List(Of Insanlar)

        Return (From inc In Veri.Insanlars).ToList

    End Function

 

    <OperationContract()>

    Public Function TiplerGetir() As List(Of Tipler)

        Return (From inc In Veri.Tiplers).ToList

    End Function

 

End Class

Yukarıda gördüğünüz servis içerisindeki iki metod bize tüm insanları ve tipleri basit bir şekilde döndürecek. Bu metodları kullanarak Silverlight tarafından gerekli veriyi sunucudan çekeren Gridimize bağlamaya çalışacağız.

[VB]

Partial Public Class MainPage

    Inherits UserControl

 

    Public Sub New()

        InitializeComponent()

    End Sub

 

    WithEvents Servis As New ServiceReference1.Service1Client

 

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

        Servis.InsanlarGetirAsync()

    End Sub

 

    Private Sub Servis_InsanlarGetirCompleted(ByVal sender As Object, ByVal e As ServiceReference1.InsanlarGetirCompletedEventArgs) Handles Servis.InsanlarGetirCompleted

        myGrid.ItemsSource = e.Result

    End Sub

End Class

Gördüğünüz en basit hali ile web servisimizden Insanlar listesini alarak Grid'e bağladık. Grid içerisinde kolonları da tabi özelleştirmiş olmamız şart ki böylece uygun bir Combobox'ı da Grid'in bir kolonuna yerleştirebilelim.

[XAML]

        <my:DataGrid x:Name="myGrid" AutoGenerateColumns="False">

            <my:DataGrid.Columns>

                <my:DataGridTextColumn Binding="{Binding Adi}"/>

                <my:DataGridTextColumn Binding="{Binding Adi}"/>

                <my:DataGridTemplateColumn>

                    <my:DataGridTemplateColumn.CellTemplate>

                        <DataTemplate>

                            <ComboBox  />

                        </DataTemplate>

                    </my:DataGridTemplateColumn.CellTemplate>

                </my:DataGridTemplateColumn>

            </my:DataGrid.Columns>

        </my:DataGrid>

Artık Grid'imiz de hazır durumda. Geriye birkaç eksik kaldı. Combobox'ı nasıl dolduracağız? Combobox'ı dolduracak olan veriye Grid'e gelmiyor. Grid'e sadece Insanlar listesi geliyor ve bu liste içerisinde de sadece seçili Tipler'in ID bilgileri bulunuyor. Bu durumda ortak bir yerlerden tüm Tip listesini çekip Combobox'lara vermek zorundayız. Bunun için harici bir veri kaynağı yaratalım.

[VB]

Public Class TipListesi

    Property Tipler As New System.Collections.ObjectModel.ObservableCollection(Of ServiceReference1.Tipler)

 

    Dim Servis As New ServiceReference1.Service1Client

 

    Sub New()

        AddHandler Servis.TiplerGetirCompleted, Sub(sender As Object, e As ServiceReference1.TiplerGetirCompletedEventArgs)

                                                    Tipler = e.Result

                                                End Sub

        Servis.TiplerGetirAsync()

    End Sub

 

End Class

 

Yukarıdaki sınıfımızı XAML içerisinden veri kaynağı olarak kullanacağız. Sınıf içerisinde listemizi sınıfın bir kopyası alındığı anda dolduruyoruz. Doldurma işlemi için de tabi ki yine web servisimizdeki Tiplere ait metodu çağırmak durumundayız.

[XAML]

<UserControl xmlns:my="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="SilverlightApplication4.MainPage"

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

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

   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

   mc:Ignorable="d"

   d:DesignHeight="300" d:DesignWidth="400"

            xmlns:daron="clr-namespace:SilverlightApplication4">

    <UserControl.Resources>

        <daron:TipListesi x:Name="TumTipler" />

    </UserControl.Resources>

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

        <my:DataGrid x:Name="myGrid" AutoGenerateColumns="False">

            <my:DataGrid.Columns>

                <my:DataGridTextColumn Binding="{Binding Adi}"/>

                <my:DataGridTextColumn Binding="{Binding Adi}"/>

                <my:DataGridTemplateColumn>

                    <my:DataGridTemplateColumn.CellTemplate>

                        <DataTemplate>

                            <ComboBox DisplayMemberPath="Tip" ItemsSource="{Binding Source={StaticResource TumTipler}, Path=Tipler}" />

                        </DataTemplate>

                    </my:DataGridTemplateColumn.CellTemplate>

                </my:DataGridTemplateColumn>

            </my:DataGrid.Columns>

        </my:DataGrid>

    </Grid>

</UserControl>

Yukarıdaki XAML dosyasında özellikle değişen renkli kısımlara dikkat etmekte fayda var. İlk olarak arka planda yarattığımız sınıfımızı XAML'a alabilmek için namespace tanımını yapıyoruz sonrasında da sınıfın bir kopyasını Local Resource olarak yaratıyoruz. Son olarak bu StaticResource'u Combobox'ın ItemsSource'una bağlıyoruz. DisplayMemberPath'i de Tip olarak set ettikten sonra herşey bitti! Artık uygulamamızı çalıştırabiliriz. Her satır Grid içerisinde gösterilirken Combobox'lar da Tipler ile doldurulmuş olacaktır.

Peki de her kayıt için seçili tip seçili gelecek mi?

İşte esas sorun burada başlıyor. Maalesef gelmeyecek! Bizim bir şekilde her kayıt Grid içerisinde yaratılırken elimizde olan TipID ile Combobox içerisindeki nesnelerden birini bağlamamız gerek. Böylece Combobox içerisinden uygun kayıt otomatik olarak seçili gelmeli! Hatta TwoWay Binding kullanarak Combobox içerisinde bir seçim değişikliği olduğunda bunu ana kayıt listemize de yansıtabilmemiz şart.

Bu durumda bakıyoruz Combobox'ın bind edilebilecek ne özellikleri var! Elde sadece SelectedItem bulunuyor! Şimdi ben bu SelectedItem'ı nasıl TipID'ye bağlayacağım? TipID integer oysa SelectedItem Tipler tipinde. İşte bu ikisini birbirine bağlamanın yolu bir ValueConverter kullanmak!

[VB]

Imports System.Windows.Data

 

Public Class TipMatchConverter

    Implements IValueConverter

 

    Dim TumTipler As New TipListesi

    Dim Tipler As System.Collections.ObjectModel.ObservableCollection(Of ServiceReference1.Tipler)

 

    Sub New()

        Tipler = TumTipler.Tipler

    End Sub

 

    Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert

        Return (From inc In Tipler Where inc.ID = CInt(value)).SingleOrDefault

    End Function

 

    Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack

        Return CType(value, ServiceReference1.Tipler).ID

    End Function

End Class

Hazırladığımız bu Converter yapısını da SelectedItem'ın bindinginde artık rahatlıkla kullanabiliriz.

[XAML]

<UserControl xmlns:my="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="SilverlightApplication4.MainPage"

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

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

   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

   mc:Ignorable="d"

   d:DesignHeight="300" d:DesignWidth="400"

            xmlns:daron="clr-namespace:SilverlightApplication4">

    <UserControl.Resources>

        <daron:TipListesi x:Name="TumTipler" />

        <daron:TipMatchConverter x:Name="TipMatchConverter" />

    </UserControl.Resources>

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

        <my:DataGrid x:Name="myGrid" AutoGenerateColumns="False">

            <my:DataGrid.Columns>

                <my:DataGridTextColumn Binding="{Binding Adi}"/>

                <my:DataGridTextColumn Binding="{Binding Adi}"/>

                <my:DataGridTemplateColumn>

                    <my:DataGridTemplateColumn.CellTemplate>

                        <DataTemplate>

                            <ComboBox DisplayMemberPath="Tip" ItemsSource="{Binding Source={StaticResource TumTipler}, Path=Tipler}"

                                     SelectedItem="{Binding TipID, Converter={StaticResource TipMatchConverter}, Mode=TwoWay}"/>

                        </DataTemplate>

                    </my:DataGridTemplateColumn.CellTemplate>

                </my:DataGridTemplateColumn>

            </my:DataGrid.Columns>

        </my:DataGrid>

    </Grid>

</UserControl>

Kendimizi hamallık yapmış gibi hissetmemiz normal :) Çünkü biraz öyle oldu. Fakat yapacak birşey yok. Silverlight 3.0'da uygulayabileceğiniz en temiz çözümlerden biri bu. Bu arada unutmadan belirtiyim aslında Converter içerisinde servisten verinin kesinlikle gelip gelmediğini de kontrol etmenizde fayda var. Kullanıcının bağlantı hızına göre sorunlar yaşanabilir. Tavsiyem Tipler'i global bir yerlerde tutup oradan kullanmanız olabilir.

Peki Silverlight 4.0 ile ne oldu?

Silverlight 4.0 ile gelen özellikle çok basit :) Aslında bu basit özelliğin değerini bilelim diye yukarıdaki senaryoyu özellikle anlatmak istedim. Silverlight 4.0 ile Combobox'lara SelectedValue geliyor! :D Oley!

Böylece rahatlıkla Combobox'ın SelectedValuePath'ini ID olarak ayarlayıp Tipler nesnesindeki ID kolonunun DataGrid'e gelen Insanlar nesnesindeki TipID ile SelectedValue üzerinden bind edilmesini sağlayabiliyoruz.

[XAML]

<UserControl xmlns:my="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="SilverlightApplication4.MainPage"

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

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

   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

   mc:Ignorable="d"

   d:DesignHeight="300" d:DesignWidth="400"

            xmlns:daron="clr-namespace:SilverlightApplication4">

    <UserControl.Resources>

        <daron:TipListesi x:Name="TumTipler" />

    </UserControl.Resources>

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

        <my:DataGrid x:Name="myGrid" AutoGenerateColumns="False">

            <my:DataGrid.Columns>

                <my:DataGridTextColumn Binding="{Binding Adi}"/>

                <my:DataGridTextColumn Binding="{Binding Soyadi}"/>

                <my:DataGridTemplateColumn>

                    <my:DataGridTemplateColumn.CellTemplate>

                        <DataTemplate>

                            <ComboBox DisplayMemberPath="Tip" ItemsSource="{Binding Source={StaticResource TumTipler}, Path=Tipler}"

                                    SelectedValue="{Binding TipID, Mode=TwoWay}" SelectedValuePath="ID" />

                        </DataTemplate>

                    </my:DataGridTemplateColumn.CellTemplate>

                </my:DataGridTemplateColumn>

            </my:DataGrid.Columns>

        </my:DataGrid>

    </Grid>

</UserControl>

İşte gördüğünüz üzere :) bu kadar basit görülebilecek bir değişiklik aslında ne kadar da hayat kurtarıcı olabililyor. Silverlight 4.0 release olsa da hemen şu dertlerden kurtulsak :)

Hepinize kolay gelsin.