Silverlight 4 Beta ile Commanding Yapısı

0 dakikada yazıldı

5488 defa okundu

Düzenle

Silverlight 4 Beta ile beraber gelen özelliklerden biri de Command
yapıları. Command yapıları özellikle WPF developer'larının alışık
oldukları yapılar arasında fakat maalesef Silverlight tarafında bugüne
kadar herhangi bir runtime seviyesinde implementasyon yoktu. Özellikle
geniş çaplı iş uygulamalarının da artması ile uygulama içi kod yazım
yapılarında ve disiplinlerinde farklı arayışlar kendini gösterebiliyor.
Bu arayışlar sonucudur ki WPF tarafında MVP, MVVM gibi kod yazım
tasarımları ortaya çıkar.

Silverlight tarafında da aslında uzun bir süredir bu gibi konularda
harici kütüphaneler bulunuyordu. Benim bugüne kadar bu konularda yazı
yazmamamın nedeni ise daha herhangi bir standardın pek de oturumamış
olmasıydı. Bu yazımızda çok hızlı bir şekilde MVVM'in ufak bir kısmından
rüzgar gibi geçerek Silverlight 4'teki Command yapılarına göz atacağız.
Olabildiğince örnek üzerinden giderek yaptıklarımızın amacını da
anlatmaya çalışacağım.

Tüm yapacaklarımızın amacı nedir?

Aslında kod yazım şekilleri ile ilgili genel geçer bir bakış attığınızda
göreceksiniz ki en önemli hedeflerden biri farklı amaçlara hizmet eden
kodları olabildiğince birbirinden ayırmaktır. Bu süreç tabi ki ek bir
emek gerektirir ve bazen gereklidir, bazen ise değildir. Özünde bir
projeye başlarken sorulması gereken soru bu farklı amaçlara hizmet eden
kodları birbirinden ayırmanın söz konusu projede getireceği bir kazancın
olup olmadığının yanı sıra kazancın bu ek süreç için harcanacak emeğe
kıyasla toplamda hala bir kazanç olarak durup durmadığıdır. Tüm bu
soruları sormadan herhangi bir projede kod yazım şekli ile ilgili genel
geçer bir doğru kesinlikle bulunamaz.

Sadede gelirsek, bu makale boyunca anlatacaklarım sizin belki de bugüne
kadar yazdığınız Silverlight projelerinde uyguladığınız stilin çok
dışında olacaktır. Bu makalede anlatacağım uygulama geliştirme tarzı
kesinlikle genel geçer bir doğru değildir ve her projede "profesyonel
olalım" endişesi ile uygulanması gereken bir "guru tarzı" vs değildir :)
Birer yazılımcı olarak göreviniz uygun şartlarda uygun araçlarla uygun
çözümleri en düşük maliyet ve en yüksek verimlilik ile üretmek olduğunu
unutmamanızda fayda var.

Uyarı bölümünü geçtiğimize göre gelelim konumuza. Bahsettiğim gibi
genelde amacımız farklı amaçlara hizmet eden kodları birbirinden
ayırmak. Buna bir örnek olarak XAML ile VB/CS kodlarının ayrı dosyalarda
tutulmasını da verebiliriz. Oysa aynı dosyada da tutma şansımız var
fakat yapmıyoruz. Neden? Çünkü XAML ile VB/CS'in amacı farklı ve ayrı
yerlerde durmaları bize projelerimizin kod yazım süreçlerini
yönetmemizde büyük katkı sağlıyor. İşte bu endişenin devamında UI
(Kullanıcı arayüzü) ile ilgili kodların da veri katmanı ile salt görsel
katman (XAML) arasında kaldığını düşünürsek tam da o noktada bir
karışıklık kendini gösterebiliyor. İşte bu karışıklığı toparlayabilmek
ve bilyonlarca event-handler vs ile uğraşmamak adına Commanding
yapısını kullanabiliriz. Aman dikkat Commanding'in tek faydası tabi ki
bu değil, kodun test edilebilmesi, görsel ekranlar ile görsel ekranlara
veri bağlantısının yapıldığı kodun birbirinden tamamen
ayrıştırılabilmesi, DataBinding mekanizmasını kolaylaştırması gibi
birçok yan etkisi de var.

Silverlight 4 ile ne gelmiş?

İlk olarak gelin Silvelright 4'de gelen yeniliğe bir göz atalım.
Olabildiğince basit bir örnekten yola çıkarak örneğimizi makale boyunca
geliştirerek evrim geçirmesini sağlayacağız. Varsayalım ki örneğimizde
bir düğme ve bir de TextBox olacak. TextBox içerisine yazı girildiğinde
düğme tıklanabilir olmalı ve yazıyı almalı. Oysa TextBox boş ise düğmeye
tıklanamamalı. Şimdi böyle bir durumda normal şartlarda ne yapardık bir
bakalım.

[XAML]

<UserControl
x
:Class="SilverlightApplication16.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">

 

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

        <StackPanel>

            <TextBox
x
:Name="txtMetin"
/>

            <Button
x
:Name="btnTikla"
Content
="TIKLA"></Button>

        </StackPanel>

    </Grid>

</UserControl>

Yukarıda basit bir şekilde örneğimizin XAML kodunu görüyorsunuz.
btnTikla adında bir Button ve txtMetin adında da bir
TextBox'ımız var.

[VB]

Partial Public Class MainPage

    Inherits UserControl

 

    Public Sub New()

        InitializeComponent()

    End Sub

 

    Private Sub txtMetin_TextChanged(ByVal sender As Object,
ByVal e As System.Windows.Controls.TextChangedEventArgs) Handles txtMetin.TextChanged

        If String.IsNullOrEmpty(txtMetin.Text) Then

            btnTikla.IsEnabled = False

        Else

            btnTikla.IsEnabled = True

        End If

    End Sub

 

    Private Sub btnTikla_Click(ByVal sender As Object,
ByVal e As System.Windows.RoutedEventArgs) Handles btnTikla.Click

        MessageBox.Show(txtMetin.Text)

    End Sub

End Class

Gördüğünüz gibi tek tek hem düğmenin hem TextBox'ın uygun eventlarını
yakalamamız ve her seferinde de ayrı ayrı kontroller ulaşmamız gerekti.
İşte bu senaryoda kontrollerden herhangi birinin adı değişse hemen
arkaya dönüp kodumuzu da değiştirmemiz gerekecek. Aynı şekilde TextBox
yerine belki de ileride bir Combox konacak? Ve orada seçili nesneye göre
sistem çalışacak? İşte böyle bir durumda da herşeyi baştan toparlamamız
gerekir kod tarafında. Gördüğünüz gibi UI ile ilgili kod ve UI birbiri
ile çok fazla iç içe!

[VB]

Public Class TiklaCommand

    Implements ICommand

 

    Public Function CanExecute(ByVal parameter As Object)
As Boolean Implements System.Windows.Input.ICommand.CanExecute

        If String.IsNullOrEmpty(parameter) Then

            Return False

        Else

            Return True

        End If

    End Function

 

    Public Event CanExecuteChanged(ByVal sender As Object,
ByVal e As System.EventArgs) Implements System.Windows.Input.ICommand.CanExecuteChanged

 

    Public Sub Execute(ByVal parameter As Object)
Implements System.Windows.Input.ICommand.Execute

        MessageBox.Show(parameter)

    End Sub

End Class

Silverlight 4 ile beraber artık Commanding yapısı geldiğine göre
yukarıdaki şekilde bir sınıf tanımlayabiliriz. Gördüğünüz bu sınıf
doğrudan ICommand interface'ini implemente ediyor. Bu Interface
içerisinde otomatik olarak CanExecuteChanged eventı, CanExecute
ve Execute metodları bulunuyor. Toplamda iki metoddan CanExecute
metodu kendisine gelen bir parametreye göre olası bir komutun çalışıp
çalışamayacağına kadar veriyor. Bizim örneğimizde gelen parametreyi
TextBox içerisinde metin olarak düşünebilirsiniz. Eğer metin yoksa
geriye False varsa True döndürüyoruz. İkinci metodumuz olan Execute ise
aslında çalıştırılacak komutun ta kendisini tanımlıyor. Basit bir
şekilde şimdilik gelen parametreyi bir MessageBox ile gösteriyoruz.
Hepsi bu kadar. Sıra geldi tüm bu mekanizmayı XAML yani görsel tarafla
bağlamaya. Dikkat edin şu ana kadar ne bir TextBox ne de bir Button'dan
bahsettik! Kodumuzda hiçbir kontrolün adı veya tipi yok! Bizim için tek
önemli olan gelen parametrenin tipi, değeri ve yapacağımız iş!

Yukarıdaki kodu ayrı bir VB dosyası olarak projeye ekledikten sonra
artık sınıfımızı XAML tarafında kullanabilmek adına gerekli
tanımlamaları XAML tarafında yapmalıyız.

[XAML]

<UserControl
x:Class="SilverlightApplication15.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:SilverlightApplication15">

    <UserControl.Resources>

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

    </UserControl.Resources>

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

        <StackPanel>

            <TextBox x:Name="txtMetin"
/>

            <Button Command="{StaticResource
TiklaCommand}"

                   CommandParameter="{Binding Text,
ElementName=txtMetin, Mode=TwoWay}"

                   x:Name="btnTikla"
Content="TIKLA"></Button>

        </StackPanel>

    </Grid>

</UserControl>

İlk olarak her zamanki gibi XMLNS yani XML NameSpace tanımımız ile arka
plandaki sınıfımızı bu tarafa import ediyoruz. Sonrasında da
UserControl'un Resource'ları arasında TiklaCommand'in bir kopyasını
alıyoruz. Artık sıra geldi gerekli Binding'leri ayarlayarak Textbox ve
Button ile Command arasındaki ilişkiyi belirlemeye.

[XAML]

<UserControl
x:Class="SilverlightApplication15.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:SilverlightApplication15">

    <UserControl.Resources>

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

    </UserControl.Resources>

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

        <StackPanel>

            <TextBox
x
:Name="txtMetin"
/>

            <Button
Command
="{StaticResource
TiklaCommand
}"

                  
CommandParameter
="{Binding
Text
,
ElementName
=txtMetin, Mode=TwoWay}"

                   x:Name="btnTikla"
Content
="TIKLA"></Button>

        </StackPanel>

    </Grid>

</UserControl>

Özünde yaptığımız tek şey Button'un Command ve CommandParameter
özelliklerini set etmek. Command olarak hemen StaticResource'lar
arasından daha bir önceki adımda yarattığımız Command'imizi veriyoruz.
Sonrasında parametreyi aktarırkende Element Binding kullanarak
TextBox'ın Text özelliğindeki değeri alıp gönderiyoruz. İşte bu kadar!
Peki şimdi size soruyorum, XAML ile arka plandaki UI işlevselliğini
barındıran kod birbirinden gerçekten de uzaklaşmadı mı? Evet, uzaklaştı.
Daha mı çok uğraştık? Şimdilik hayır ama farklı senaryolarda daha çok
uğraşmamız da gerekebilirdi.

İşi biraz daha karıştıralım!

Gördüğünüz üzere yukarıdaki teknik ile bir uygulama geliştirdiğinizde
onlarca Command ve Bindingler yazacaksınız. Bu gibi bir durumda kodu
biraz daha kısaltmak ve basitleştirmek adına Generic Command tipleri
yaratabilirsiniz.

[VB]

Public Class BirCommand(Of T)

    Implements ICommand

 

    Private executeAction As Action(Of T)

    Private canExecuteAction As Func(Of
T, Boolean)

 

    Sub New(ByVal
executeAction As Action(Of T),
ByVal canExecuteAction As Func(Of
T, Boolean))

        Me.executeAction =
executeAction

        Me.canExecuteAction =
canExecuteAction

    End Sub

 

    Public Function CanExecute(ByVal parameter As Object)
As Boolean Implements System.Windows.Input.ICommand.CanExecute

        Return
(canExecuteAction(parameter))

    End Function

 

    Public Event CanExecuteChanged(ByVal sender As Object,
ByVal e As System.EventArgs) Implements System.Windows.Input.ICommand.CanExecuteChanged

 

    Public Sub Execute(ByVal parameter As Object)
Implements System.Windows.Input.ICommand.Execute

        executeAction(parameter)

    End Sub

End Class

Yukarıda gördüğünüz sınıf aslında bizim bir önceki örneğimizde
kullandığımız ICommand interface'ini implemente eden nesnemizin generic
olan hali. Tabi ben biraz VB tembelliği yapıp parametreleri Generic
yapmadım :) Onu da size bırakmış oliyim. Burada önemli olan noktalardan
biri ise sınıfımızın artık bir Constructor'a sahip olarak çalıştıracağı
her iki CanExecute ve Execute fonksyonlarını da dışarıdan alıyor
olması. Böylece artık proje içerisinde istediğimiz zaman hızlıca
ICommand tipinden sınıflar yaratabiliriz.

[VB]

Public Class ViewModel

 

    Public ReadOnly Property Tikla() As ICommand

        Get

            Return New BirCommand(Of String)(Sub(param)

                                                                        
MessageBox.Show(param)

                                                                    
End Sub,

                                                                    
Function(param) As Boolean

                                                                     
     If String.IsNullOrEmpty(param) Then

                                   
                                             Return
False

                                   
                                       Else

                                   
                                             Return
True

                                                               
            End If

                                   
                                 End
Function)

        End Get

    End Property

 

End Class

Yukarıdaki ViewModel sınıfımız içerisinde sadece bir ReadOnly property
var. Söz konusu property'nin de tipi ICommand olmak durumunda. Bu
şekilde bu sınıf içerisine sayfanızda kullandığınız tüm Command'leri
yerleştirebilirsiniz. Örneğimizde Property geriye bizim BirCommand
sınıfımızdan yaratıp döndürüyor. BirCommand sınıfımızın
Constructor'ı da iki ayrı fonksyonu parametre olarak alıyor. Böylece
herşey bir yerde oldu bitti.

[XAML]

<UserControl
x:Class="SilverlightApplication15.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:SilverlightApplication15">

    <UserControl.Resources>

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

    </UserControl.Resources>

    <Grid
x
:Name="LayoutRoot"
Background
="White" DataContext="{StaticResource
ViewModel
}">

        <StackPanel>

            <TextBox x:Name="txtMetin"
/>

            <Button
Command
="{Binding
Tikla
}"

                  
CommandParameter
="{Binding
Text
,
ElementName
=txtMetin, Mode=TwoWay}"

                   x:Name="btnTikla"
Content
="TIKLA"></Button>

        </StackPanel>

    </Grid>

</UserControl>

XAML tarafında ise artık ViewModel sınıfımızı Root elementimiz olan
Grid'e DataContext olarak verip sonrasında içerideki herhangi bir
kontrole de doğrudan Command Binding verebiliyoruz.

İşte bu kadar!

Yazıyı sonlandırmadan önce özellikle yazının başında uyarılarımızı
tekrar hatırlamanızı rica ediyorum. Command sistemi güzeldir hoştur
fakat mecburi değildir. Bu sadece bir yazılım geliştirme stilidir ve
bazı durumlarda mantıklı/faydalı olur bazılarında ise sadece size ek iş
çıkartır. O nedenle benim tavsiyem her iki yolu da bilerek uygun
yerlerde uygun çözümleri uygulamanız ve genel geçer bir doğru aramamanız
olacak.

Hepinize kolay gelsin.