Arayı daha fazla soğutmamak adına MVVM serisinin ikinci kısmını biran önce yazıp paylaşmak istedim:) Önceki yazımızda hatırlarsanız MVVM kullanmadan veriler üzerinde bir takım işlemler gerçekleştiren basit bir WPF uygulaması geliştirmiştik. Bu yazımızda da geliştirmiş olduğumuz uygulamamızı refactor ederek MVVM ile nasıl geliştirilebileceğine adım adım bakıyor olacağız.

  • Öncelikle isimlendirme uyumluluğu açısından projemizde Model, View ve ViewModel klasörlerini oluşturuyoruz. Bunlara ek olarak bir de Command klasörü oluşturuyoruz. Bu klasör RelayCommand sınıfımızı koyacağımızı klasör olacak. MVVM uygulamamızda ben RelayCommand kullanacağım ama siz DelegateCommand yapısını tercih edebilirsiniz.
  • Person ve Repository sınıflarımızı Model klasörü içerisine taşıyoruz. Taşıma işleminden sonra namespace leri düzeltmeyi unutmayalım.
  • Commanding yapısını kullanacağımız için RelayCommand sınıfını Commands klasörümüze ekliyoruz ve ilgili namespace adını klasör adına göre tekrardan düzenliyoruz.
  • ViewModel klasörümüze ise ilk olarak ViewModelBase adında temel bir sınıf ekleyeceğiz. Daha sonra ekleyeceğimiz tüm ViewModel sınıflarımız bu temel sınıftan kalıtım alacaklar. Oluşturacağımız bu sınıf abstract bir sınıf olup içerisinde INotifyPropertyChanged arayüzünü implemente edecek. Burada Abstract sınıf kullanmamızın nedeni kod tekrarı olmasını engellemek ve çok biçimliliği sağlamaktır. INotifyPropertyChanged arayüzünü implemente etmemizin sebebi de kod tarafında yapılan değişikliklerin UI tarafına yansımasını sağlamaktır. Temel sınıfımızın son hali aşağıdaki gibidir:
public abstract class ViewModelBase:INotifyPropertyChanged  
{
    public event PropertyChangedEventHandler PropertyChanged;

 public void OnPropertyChanged(string propertyName)
 {
    var handler = PropertyChanged;
    if (handler != null)
    {
        handler(this, new PropertyChangedEventArgs(propertyName));
    }
 }
}
  • Temel sınıfımızı da oluşturduğumuza göre sıra geldi ViewModel sınıfımızı oluşturmaya. MainViewModel isimli bir sınıf oluşturduktan sonra temel sınıfımız olan ViewModelBase sınıfından kalıtıp, Person listesini ListView kontrolüne yüklemek ve sıralama işlemini gerçekleştirmek için ICommand propertyler oluşturuyoruz.
private RelayCommand loadCommand;  
private RelayCommand sortCommand;  
public ICommand LoadCommand  
{
    get
    {
        if (loadCommand  null)
        {
            loadCommand = new RelayCommand(p => CanLoad(), p => Load());
        }
        return loadCommand;
    }
}
public ICommand SortCommand  
{
    get
    {
        if (sortCommand  null)
        {
            sortCommand = new RelayCommand(p => CanSort(), p => Sort());
        }
        return sortCommand;
    }
}
  • Command propertyler sayesinde sadece ilgili metod çağrımı yapılmaktadır. Dikkat ederseniz RelayCommand nesnelerimizin yapıcı metodları parametre olarak Predicate ve Action almaktadır. Predicate olarak, boolean değer döndüren CanLoad metodumuzu veriyoruz. Asıl Load metodumuzun çalışması bu metodun dönüş değerine bağlıdır. CanLoad metodu geriye true değerini döndürmediği sürece Load metodu çağrılmayacaktır. Peki bu ne işimize yarayacak diye düşünebilirsiniz, örneğin CanLoad metodunda Load metodu içerisinde kullanılacak nesnelerin ya da bağlantıların hazır olup olmadığının kontrolü yapılabilir.
  • Command yapımız da hazır olduğuna göre verilerimizi saklayıp üzerinde manipülasyonlar yapabileceğimiz ObservableCollection tipinden bir liste tanımlıyoruz ve bu listemizi binding işlemlerinde kullanabilmek için property olarak dışarıya açıyoruz. Load metodumuzda da Repository sınıfımızın metodlarından yararlanarak aldığımız kişi listesini ObservableCollection listemize atıyoruz. ObservableCollection kullanmamızın sebebi ise bu tipin kendi içerisinde zaten INotifyPropertyChanged ve INotifyCollectionChanged arayüzlerini implemente ediyor olmasıdır. Bu sayede property üzerindeki değişiklikler için bizim ekstradan herhangi bir kod yazmamıza gerek kalmıyor.
  • Uygulamamızda arama için bir metod yazmak yerine string tipinden bir property oluşturup arama metni girildikçe CollectionViewSource nesnemizin propertydeki değere göre filtrelenmesini sağlıyoruz.
public string Search  
{
    set
    {
        peopleView.Filter = p =>
        {
            if (((Person)p) != null)
            {
                if (((Person)p).FirstName.StartsWith(value, StringComparison.InvariantCultureIgnoreCase))
                {
                    return true;
                }
            }
            return false;
        };
    }
}
  • Veriler içerisinde arama özelliğini de tamamladığımıza göre UI tarafına geçebiliriz artık. UI tarafında ise binding ifadelerimizi düzeltmemiz yeterli olacaktır. Önceki uygulamamızdaki MainWindow.xaml dosyamızın adını değiştirerek MainWindowView.xaml olarak düzeltiyoruz ve View klasörüne taşıyoruz. Taşıdıktan sonra namespace ‘i düzeltmeyi unutmuyoruz ve önceden yazmış olduğumuz event, property ve değişken tanımlamalarını siliyoruz.
<Grid>  
<Grid.RowDefinitions>  
    <RowDefinition Height="50" />            
    <RowDefinition Height="*" />
</Grid.RowDefinitions>       

<StackPanel Orientation="Horizontal" Grid.Row="0">  
    <Button Content="Load" x:Name="btnLoad" Command="{Binding LoadCommand}"  Width="100" Height="26" VerticalAlignment="Center" Margin="5"/>
    <Button Content="Sort" x:Name="btnSort" Command="{Binding SortCommand}"  Width="100" Height="26" VerticalAlignment="Center" Margin="5"/>
    <TextBlock Text="Search:" VerticalAlignment="Center" />
    <TextBox Text="{Binding Search,UpdateSourceTrigger=PropertyChanged}" Height="25" Width="100"/>
</StackPanel>      

<ListView x:Name="lvPeople" Grid.Row="1" ItemsSource="{Binding People}"  
Height="210"  
Width="620">  
<ListView.View>  
    <GridView>
    <GridViewColumn DisplayMemberBinding="{Binding FirstName}">
    <GridViewColumnHeader Content="FirstName"/>
    </GridViewColumn>
    <GridViewColumn DisplayMemberBinding="{Binding LastName}">
    <GridViewColumnHeader Content="LastName"/>
    </GridViewColumn>
    </GridView>
</ListView.View>  
</ListView>  
</Grid>  
  • Burada dikkat edilmesi gereken noktalardan biri de şudur: ViewModel, View tarafından tamamen habersizdir, ViewModel sadece gerekli işlemleri gerçekleştirip UI tarafa veri sunmak ve kullanıcı etkileşimlerine cevap verebilmek için Property ve Command nesnelerini tanımlamaktadır.
  • Bir diğer husus da UI tarafında bind ettiğimiz property ve commanding yapısını ilgili ViewModel ile ilişkilendirmemiz gerekiyor. Bunun için de aşağıdaki gibi Window kontrolünün DataContext’ine ilgili ViewModel’imizin bir nesnesini atıyoruz. Öncelikle ViewModel nesnemize ulaşabilmek için XAML tarafında bir namespace ekliyoruz.
xmlns:vm="clr-namespace:MVVMDemo.ViewModel"
  • Namespace alanını ekledikten sonra ViewModel’imizi de DataContext’e atıyoruz.
<Window.DataContext>  
   <vm:MainWindowViewModel/>
</Window.DataContext>  
  • Son olarak App.xaml dosyasındaki StartupUri="MainWindow.xaml" tagını silip App.xaml.cs dosyasında OnStartup metodunu override edip ilk açılış ekranını belirtiyoruz.
protected override void OnStartup(StartupEventArgs e)  
{
    base.OnStartup(e);
    var window = new MainWindowView();
    window.Show();
}

Bu yazımızla beraber uygulamamıza adım adım MVVM desenini uygulamış olduk. Uygulamamızın View klasöründeki UI dosyalarının kod tarafına baktığımızda hiç kod yazmadığımızı, aynı zamanda UI tarafın hiçbir şekilde doğrudan Model ile haberleşmediğini farkedeceksiniz. Gördüğünüz gibi MVVM’in temel prensiplerini yerine getirmiş olduk.

Kısaca şöyle bir toparlayacak olursak, öncelikle verilerle ilgili POCO ve Repository sınıflarımızı Model klasörümüze taşıdık. ViewModel’de ise UI tarafındaki işlemlerde kullandığımız event yapısı yerine Commanding yapısını kullandık. Verilerimizi View katmanına sunmak için de ObservableCollection tipinden koleksiyonlar kullandık. Sıralama ve filtreleme işlemlerimizin daha kolay olması için de CollectionViewSource sınıfından yararlandık. UI tarafına baktığımızda çok köklü bir değişiklik yaptığımız söylenemez. ViewModel ilişkisini tanımlamak için DataContext’e, ilişkili ViewModel nesnesini atayıp binding ifadelerini tekrardan düzenledik. Son olarak da ilk açılacak ekranımızı belirledik.

Adım sayısına bakıldığında fazla gibi gelebilir fakat hemen hemen her şeyi irdelemeye çalıştığımız için sayının arttığını düşünüyorum:) Örnek uygulamanın kaynak kodlarına baktığınızda çok daha basit olduğunu göreceksiniz.

About the Author