Silverlight 4 Beta ile Commanding Yapısı
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.
Daron Yöndem'in Blogundan Alıntıdır