Serinin önceki yazıları
MVVM serisinin ikinci bölümüyle birlikte konuya hızlı bir şekilde kaldığımız yerden devam ediyoruz. Bir önceki bölümde MVVM’in ne tür bir mimari desen olduğu, temel bir kaç kuralı, avantajları ve dezavantajları gibi noktalara değinmiştik. Bu bölümde ise, basit bir “Hello World” uygulaması üzerinden View ve ViewModel kavramlarını anlamaya çalışacağız. Ayrıca View ve ViewModel arasındaki çok önemli iki etkileşim olan Notifiable Property ve Command’in nasıl oluşturulduklarını da öğreneceğiz.
Örnek uygulama Visual Studio 2010 IDE ’si üzerinde Silverlight 4 ile gerçekleştirilecek ve MVVM Toolkit’de yer alan kütüphaneler kullanılacaktır. Dolayısıyla başlamadan önce Toolkit’in kurulması gerektiğini hatırlatmakta fayda var. Laurent Bugnion’ın hazırladığı MVVM Toolkit’i ile ilgili daha ayrıntılı bilgi almak için serinin ilk yazısını incelemenizi tavsiye ederim.
Artık hazır olduğumuza göre, MVVM dünyasına giriş yapmak üzere “Hello World” projemizi oluşturabiliriz. Bu ilk örnekte, felsefenin aslını daha rahat görebilmek için, az sayıda kontrol ve iş içeren, oldukça sade bir arayüz üzerinden hareket edeceğiz. Hedefimiz ise, kullanıcının Button’a her tıklamasında, TextBlock’taki sayının bir arttığı basit bir Silverlight uygulaması geliştirmek. Örneğe geçmeden önce izlenecek yöntemle ilgili birkaç noktanın da altını çizmekte fayda var. İçinizden “Ne var bunda, + butonuna çift tıklar, oluşan metoda birkaç satır kod ekler ve hallederim” dediğinizi duyar gibiyim; ancak asıl amaç bahsi geçen bu minik programı MVVM mimari desenini takip ederek geliştirmek.
Klasik yaklaşım düşünüldüğünde, bu basit örnek, aşağıdaki adımlar izlenerek rahatlıkla yapılabilir.
- Button kontrolünün Click olayına bağlanmak üzere, sayfanın içinde bir metot oluştur.
- Oluşturulan metotta, TextBlock kontrolünün Text özelliğini okuyarak, mevcut değeri bir tamsayı değişkene ata.
- Değişkende saklanan değeri bir arttır.
- Yeni değeri string’e dönüştür ve tekrar TextBlock kontrolünün Text özelliğine ata.
Bu durumda yazılması gereken metot da aşağıdakine benzer olacaktır :
private void button1_Click(object sender, RoutedEventArgs e)
{
int deger = int.Parse(textBlock1.Text);
deger++;
textBlock1.Text = deger.ToString();
}
Yukarıdaki metodun yazılması ve Click olayına bağlanmasıyla, hiç şüphesiz ekranda görünen sayının artması sağlanır; ancak söz konusu proje daha büyük olduğunda, izlenen bu yolun birçok dezavantajı bulunmaktadır. Arayüzde gerçekleşen işlerin (Business Logic) kodlanmaları sırasında kontrollere erişmek ve kontrolleri hedefleyen kodlar çalıştırmak bahsi geçen dezavantajların temelini oluşturmaktadır. Herşeyden önce bu yaklaşımın izlendiği projelerde, tasarımcılar ile programcıların bir arada çalışmaları daha güçtür. Örneğin, tasarımcı XAML içerisinde bir elemente Name niteliğini uygulayarak yeni bir isim verdiğinde, programcının kod tarafında ilgili elemente erişmek için kullandığı field’ın da adı değişecektir, dolayısıyla tasarımcının yaptığı basit bir isim değişikliği dahi problem oluşturabilmektedir. Bir başka örnek : Tasarımcı bir DataGrid’in kolon sırasını değiştirdiğinde, programcının ilgili kolonlardaki değerleri okumasında, kolon indeksleri değiştiğinden bir takım hatalar meydana gelebilir; hatta farkedilmesi güç, mantıksal hatalar dahi oluşabilir. Bunun yanı sıra, bahsi geçen klasik yöntemin, “önce A yapılır biter, sonra B başlar” yerine, Agile yaklaşımların benimsendiği projelerde daha fazla can sıktığını da söyleyebilirim.
Tabi ki dezavantajlar, yalnızca tasarımcı – programcı entegrasyonuyla da ilgili değildir. Klasik yaklaşım izlenerek oluşturulan bir projede, programcı hem kontrollere hem de diğer nesnelere ilişkin kodlamalar yapmaktadır. Dolayısıyla programcı, çekirdek iş mantığı bir yana; arayüzde yer alan kontroller, giriş değerleri için yapılan tip dönüşümleri, hesaplanan sonuçların kontrollere bağlanmaları vb. bir çok yan işlemi de düşünmek ve ele almak zorundadır. Hal böyle olunca, kişi, iş mantığından uzaklaşabilir, hatalar yapabilir ve basit bir takım geliştirmeleri yaparken bile gereksiz vakit harcayabilir.
MVVM tam olarak bu noktada devreye girmekte ve kodlamanın, tasarımdan bağımsız hale getirilmesi için bir çözüm sunmaktadır. Temel hedef, iş mantığı ile görsel elementlerin bağımsızlığını sağlamak olduğundan, MVVM öncelikle iş mantığı ile görsel elementleri iki keskin kavramla birbirlerinden ayırmaktadır: View ve ViewModel. View, yalnızca görsel elementleri içermekte olup, tüm iş mantığı ViewModel’de yer almaktadır. Kilit nokta, ViewModel’in View’i tanımamasıdır. Diğer bir deyişle ViewModel’in kodlanması sırasında View’de yer alan kontroller dikkate alınmaz, hatta kontrollere erişilemez, yalnızca yapılan işlere odaklanılır.
Klasik yaklaşım ve doğurduğu temel dezavantajları ile MVVM programcısının bakış açısından bahsettiğimize göre, uygulamanın MVVM deseni kullanılarak oluşturulmasına nihayet başlayabiliriz.
Hello MVVM
Yeni bir Silverlight Application projesi başlatarak, öncelikle MVVM Toolkit’ini kullanmak üzere gerekli kütüphaneleri referans edelim.
Ardından MainPage’i aşağıdaki XAML kodlarıyla oluşturalım.
<Grid x:Name="LayoutRoot" Background="White">
<Button Content="+" Height="23" HorizontalAlignment="Left" Margin="64,92,0,0" VerticalAlignment="Top" Width="75" />
<TextBlock Height="44" FontSize="30" HorizontalAlignment="Left" Margin="153,82,0,0" Text="TextBlock" VerticalAlignment="Top" />
</Grid>
ViewModel’in Oluşturulması
Yukarıdaki XAML kodlarından faydalanarak arayüzü tamamladıktan sonra, projeye, MainPageViewModel olarak isimlendirdiğimiz bir Class ekleyelim. MVVM tasarım deseni düşünüldüğünde MainPage, View; MainPageViewModel ise onun ViewModel’i; diğer bir deyişle, MainPage’deki işlerin, elementlere erişmeksizin kodlandığı yer olacaktır.
public class MainPageViewModel : GalaSoft.MvvmLight.ViewModelBase
{ }
Oluşturulan MainPageViewModel sınıfının, MVVM Toolkit kütüphanelerini referans ederek ulaştığımız ViewModelBase sınıfından kalıtıldığına da dikkat etmek gerekir. ViewModelBase, tüm ViewModel sınıflarının kalıtılacakları taban sınıftır ve her bir ViewModel’de ihtiyaç duyulan bir takım ortak üyeleri içermektedir.
ViewModelBase taban sınıfıyla ilgili bilinmesi gereken bir diğer özellik de, sınıfın, WPF ve Silverlight’ta veri bağlamasından hatırlayacağımız INotifyPropertyChanged arayüzünü uygulamış olmasıdır.
abstract class ViewModelBase : INotifyPropertyChanged, ICleanup, IDisposable
{ … }
Bu durumda oluşturulan MainPageViewModel sınıfı da kalıtım prensibi gereği, property değişiklerini arayüze haber verebilir niteliktedir. Yazının şu aşamasında çok da gerekli olmayan bu bilgi, örnek ilerledikçe çok önemli bir hal alacağından, şimdiden kulağa küpe yapılması şiddetle tavsiye edilir
MVVM Toolkit’i kullanmak yerine, kendi MVVM kütüphanenizi oluşturmanız halinde, tüm ViewModel’lere taban sınıf olma görevi gören ve INotifyPropertyChanged arayüzünü uygulayan ViewModelBase sınıfını da sizin sıfırdan oluşturmanız gerekir.
Bir ViewModel’in kodlamasına, View’de yer alan durum bilgileri ile kullanıcının tetikleyeceği işlemleri belirleyerek başlanmalıdır. Bu şekilde düşünüldüğünde, MainPage’de bir adet Int32 tipinden değerin tutulması gerektiği söylenebilir. Tabi ki bir de kullanıcı tarafından tetiklenmesi istenen, sayı arttırma işi söz konusudur. Özetle MainPageViewModel’de bir tane Int32 tipinden değer tutulması gerekirken, bir de bu değerin arttırılmasını sağlayan eylem bulunmalıdır. Belirlenen bu iki nokta, aslında ViewModel tasarımı açısından da iki yeni kavramı beraberinde getirmektedir:
- Notifiable Property : Arayüzde yer alan her bir durum bilgisi için oluşturulan ve değer değişikliklerini arayüze haber verebilen property’dir. Durum değeriyle aynı tipte oluşturulur. O halde örnekte kullandığımız MainPageViewModel sınıfına, Int32 tipinden bir Notifiable Property tanımlanması gerekmektedir.
- Command : Kullanıcının tetikleyeceği her bir “eylem” için oluşturulan, RelayCommand tipindeki özel property’lerdir. Her bir Command tek bir işi ifade etmektedir. Dolayısıyla örnekte yer alan MainPageViewModel sınıfına, sayının arttırılmasına karşılık bir Command eklenmelidir.
Sayının değerini tutacak Notifiable Property’nin ViewModel’de tanımlanması için aşağıdaki kodların sınıfa eklenmesi yeterlidir.
public class MainPageViewModel : GalaSoft.MvvmLight.ViewModelBase
{
int _sayi;public int Sayi
{
get { return _sayi; }
set
{
if (_sayi == value)
return;_sayi = value;
//RaisePropertyChanged metodu, ViewModelBase sınıfında tanımlı bir metot olup, INotifyPropertyChanged arayüzüyle gelen PropertyChanged olayının tetiklenmesini sağlamaktadır. Böylece, arayüzde Sayi özelliğine bağlanmış elementlere değerin değiştiği haberi ulaşacaktır.
base.RaisePropertyChanged("Sayi");
}
}
}
Dikkat edilirse, sınıfa eklenen Sayi isimli üye, sıradan bir property olmasıyla birlikte, tek önemli farkı, RaisePropertyChanged metodu aracılığıyla meydana gelen değer değişikliklerini arayüze bildirmesidir. Böylece Sayi’ya bağlanmış elementler, sayının değişmesi durumunda bu değişiklikten haberdar olacaklar ve yenilenerek son değeri görüntüleyebileceklerdir.
Sayi property’sinin oluşturulmasından sonra geriye, yalnızca, sayının arttırılmasını sağlayacak Command’i tanımlamak kalmaktadır. Yukarıda da belirtildiği üzere ViewModel içerisinde bir Command tanımı yapmak için, RelayCommand tipinden bir property’nin oluşturulması gerekmektedir. Diğer bir deyişle her bir RelayCommand tipinden property, arayüzde, kullanıcının tetikleyeceği bir eylemi ifade etmektedir.
MVVM Toolkit kütüphanelerinde yer alan ViewModelBase ve RelayCommand sınıfları, farklı MVVM kütüphanelerinde farklı isimlerle bulunabilirler. Sınıf isimlerinden ziyade bu sınıfların görevlerini kavramak daha doğru olacaktır. ViewModelBase, tüm ViewModel’lerin taban sınıfıdır, RelayCommand ise kullanıcının tetikleyeceği her bir eylemi ifade etmek üzere kullanılan sınıftır.
Sayı arttırma işini ifade edecek Command’in oluşturulması için, MainPageViewModel sınıfına SayiArttirCommand isimli RelayCommand tipinden property aşağıdaki gibi eklenebilir.
//Command
public RelayCommand SayiArttirCommand { get; set; }
Tabi ki SayiArttirCommand tetiklendiğinde yapılacak işi de bir yerde tanımlamak gerekir. Bunun için ViewModel’in Constructor’ında bir RelayCommand nesnesi örnekleyerek komut oluşturulmalı ve çalıştırılması istenen metot belirtilmelidir.
Ekran görüntüsünden de farkedileceği üzere, RelayCommand nesnesinin örneklenmesi sırasında Action tipinden bir parametre gönderilmelidir. .NET Framework içerisinde yer alan Action tipi, void() imzasına sahip metotları işaret eden bir delege olup, bu örnekte, butona tıklandığında çalışması istenen metodun adresini saklamak üzere kullanılmaktadır. RelayCommand nesnesine parametre olarak geçilen bu metot, ViewModel sınıfında tanımlanabileceği gibi anonim olarak da yazılabilir. Bu bilgiler ışığında kodlar düzenlediğinde, Constructor metodun son şekli aşağıdaki gibi olacaktır.
public MainPageViewModel()
{
SayiArttirCommand = new RelayCommand(() => Sayi++);
}
Bazı durumlarda komut çalıştırılmadan önce bir takım koşulların sağlanıp sağlanmadığını kontrol etmek gerekebilir. Böyle bir ihtiyaç söz konusu olduğunda, geriye bool dönen bir metot tanımlayarak, RelayCommand’in oluşturulması sırasında Constructor’a ikinci parametre olarak belirtilir. Bu kullanım şekli, serinin devam eden yazılarında da incelenecektir.
Evet, nihayet ViewModel’in hazırlanması tamamlandığına göre, sınıfın son halini görebilir, bazı önemli noktaların altını çizebiliriz.
public class MainPageViewModel : GalaSoft.MvvmLight.ViewModelBase
{
int _sayi;
public int Sayi
{
get { return _sayi; }
set
{
if (_sayi == value)
return;_sayi = value;
base.RaisePropertyChanged("Sayi");
}
}public RelayCommand SayiArttirCommand { get; set; }
public MainPageViewModel()
{
SayiArttirCommand = new RelayCommand(() => Sayi++);
}
}
Dikkat edilmesi gereken önemli noktalar şunlardır :
- Kodlamayı yaparken arayüzdeki elementlere erişmeyiz.
- ViewModel’in oluşturulmasında tasarımdan bağımsız bir bakış açımız olmalı : “Bir Int32 değer var ve kullanıcının ekrandaki bir tetiklemesiyle bu değer birer birer artacak”
- Arayüzde tutulan durum bilgileri için Notifiable Property oluşturulur.
- Arayüzde kullanıcının tetikleyeceği sayı arttırma işi için Relay Command nesnesi oluşturulur.
- Oluşturulan RelayCommand, kullanıcı butona tıkladığında çalıştırılacak metodu, Action delegesi aracılığıyla işaret etmektedir.
ViewModel’in View’e Referans Edilmesi
ViewModel’in tamamlanmasıyla, arayüze ilişkin tüm kodlama da tamamlanmıştır. Bu noktadan sonra tek yapılması gereken, View ile ViewModel’i arasındaki etkileşimleri belirtmektir. Tabi ki öncelikle View’in, kendisine ait ViewModel’i örneklemesi ve referans etmesi gerekmektedir. Bu amaçla, MainPage’in Constructor’ında aşağıda görülen düzenlemeler yapılmalıdır.
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();DataContext = new MainPageViewModel();
}
}
Aslında yapılan işlem oldukça basittir. MainPage, yani View, MainPageViewModel nesnesini, yani ViewModel’ini oluşturmuş ve sonrasında oluşan nesneye ait referansı DataContext property’sine atamıştır. Böylelikle MainPage’de yer alan elementler ile MainPageViewModel’de yer alan property’ler arasında, aşağıdaki şemada, oklarla belirtilen Binding ilişkileri kolayca oluşturulabilecektir.
Şemada görülen Binding ‘leri tanımlamak için tek yapılması gereken; TextBlock’un Text değeri ile Button’ın Command property’lerini, DataContext’te yer alan MainPageViewModel nesnesinin ilgili property’lerine Binding Markup Extension’ını kullanarak bağlamaktır.
<Grid x:Name="LayoutRoot" Background="White">
<Button Content="+" Height="23" HorizontalAlignment="Left" Margin="64,92,0,0" VerticalAlignment="Top" Width="75" Command="{Binding SayiArttirCommand}" />
<TextBlock Height="44" FontSize="30" HorizontalAlignment="Left" Margin="153,82,0,0" Text="{Binding Sayi}" VerticalAlignment="Top" />
</Grid>
Hepsi bu kadar Gerekli Binding kurallarını belirttikten sonra uygulamanın son halini çalıştırırsanız, MVVM desenini kullanarak hedeflenen noktaya ulaştığımızı siz de test ederek görebilirsiniz.
Programın çalışma zamanındaki akışını ise aşağıdaki şemada inceleyebilirsiniz.
Böylece geldik bir makalenin daha sonuna. Bu yazıda çok basit bir Silverlight uygulamasını MVVM mimari desenini kullanarak oluşturduk ve konuya “Hello World” tadında bir giriş yaptık. Bir sonraki yazı itibariyle MVVM serisine kaldığımız noktadan hızla devam edeceğiz.
Faydalı olması dileğiyle…