Makale Özeti

Bu yazımızda Silverlight 4 Beta ile beraber gelen Commanding yapısına göz atarken MVVM dünyasına da minik bir giriş yapıyoruz.

Makale

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.