MVVM - Hızlı Kılavuz
Kodunuzu düzenlemenin iyi düzenlenmiş ve belki de en yeniden kullanılabilir yolu 'MVVM' modelini kullanmaktır. Model, View, ViewModel (MVVM pattern) tamamen bakımı yapılabilir, test edilebilir ve genişletilebilir uygulamalar yazmak için kodunuzu nasıl organize edeceğiniz ve yapılandıracağınız konusunda size rehberlik etmekle ilgilidir.
Model - Sadece verileri tutar ve iş mantığıyla hiçbir ilgisi yoktur.
ViewModel - Model ve Görünüm arasında bağlantı / bağlantı görevi görür ve eşyaların güzel görünmesini sağlar.
View - Sadece biçimlendirilmiş verileri tutar ve esasen her şeyi Modele devreder.
Ayrılmış Sunum
Uygulama mantığını arka planda kodlamaya veya XAML'ye koymanın neden olduğu sorunları önlemek için, ayrılmış sunum olarak bilinen bir teknik kullanmak en iyisidir. Doğrudan kullanıcı arabirimi nesneleriyle çalışmak için gereken minimum düzeyde XAML ve arka plan koduna sahip olacağımızdan kaçınmaya çalışıyoruz. Kullanıcı arabirimi sınıfları ayrıca, sol tarafta aşağıdaki şekilde gösterildiği gibi karmaşık etkileşim davranışları, uygulama mantığı ve diğer her şey için kod içerir.
Ayrık sunumla, kullanıcı arayüzü sınıfı çok daha basittir. Elbette XAML'ye sahip, ancak arkasındaki kod pratik olduğu kadar az da yapıyor.
Uygulama mantığı, genellikle model olarak adlandırılan ayrı bir sınıfa aittir.
Ancak, hikayenin tamamı bu değil. Burada durursanız, sizi veri bağlama çılgınlığı yoluna götürecek çok yaygın bir hatayı tekrar edeceksiniz.
Birçok geliştirici, XAML'deki öğeleri doğrudan modeldeki özelliklere bağlamak için veri bağlamayı kullanmaya çalışır.
Şimdi bazen bu normal olabilir, ama çoğu zaman değildir. Sorun, modelin, kullanıcının uygulamayla nasıl etkileşimde bulunduğuyla değil, tamamen uygulamanın ne yaptığıyla ilgili olmasıdır.
Verileri sunma şekliniz genellikle dahili olarak yapılandırılmasından biraz farklıdır.
Dahası, çoğu kullanıcı arabirimi, uygulama modeline ait olmayan bazı durumlara sahiptir.
Örneğin, kullanıcı arayüzünüz bir sürükle ve bırak kullanıyorsa, sürüklenen öğenin şu anda nerede olduğu, olası bırakma hedeflerinin üzerinde hareket ederken görünümünün nasıl değişmesi gerektiği ve bu bırakma hedeflerinin nasıl olabileceği gibi şeylerin izlenmesi gerekir. öğe üzerlerine sürüklenirken değiştirin.
Bu tür bir durum şaşırtıcı derecede karmaşık hale gelebilir ve kapsamlı bir şekilde test edilmesi gerekir.
Pratikte, normalde kullanıcı arayüzü ve model arasında başka bir sınıfın oturmasını istersiniz. Bunun iki önemli rolü var.
İlk olarak, uygulama modelinizi belirli bir kullanıcı arayüzü görünümü için uyarlar.
İkincisi, herhangi bir önemsiz etkileşim mantığının yaşadığı yerdir ve bununla, kullanıcı arayüzünüzün istediğiniz şekilde davranmasını sağlamak için gereken kodu kastediyorum.
MVVM kalıbı nihayetinde MVC modelinin modern yapısıdır, bu nedenle ana amaç, alan mantığı ve sunum katmanı arasında net bir ayrım sağlamak için hala aynıdır. MVVM modelinin bazı avantajları ve dezavantajları şunlardır.
Temel fayda, ayrılığa ulaşmanın ve buna sahip olmaktan elde ettiğiniz verimliliği elde etmenin ötesinde Görünüm ve Model arasında gerçek bir ayrıma izin vermektir. Bunun gerçek anlamda anlamı, modelinizin değişmesi gerektiğinde, görüşe ihtiyaç duymadan kolayca değiştirilebileceğidir ve bunun tersi de geçerlidir.
Aşağıdaki gibi MVVM uygulamasından çıkan üç önemli anahtar nokta vardır.
Sürdürülebilirlik
Farklı kod türlerinin temiz bir şekilde ayrılması, bu daha ayrıntılı ve odaklanmış bölümlerden birine veya birkaçına girmeyi ve endişelenmeden değişiklikler yapmayı kolaylaştırmalıdır.
Bu, çevik kalabileceğiniz ve yeni sürümlere hızla geçmeye devam edebileceğiniz anlamına gelir.
Test edilebilirlik
MVVM ile her bir kod parçası daha ayrıntılıdır ve doğru uygulanırsa, dış ve iç bağımlılıklarınız, test etmek istediğiniz çekirdek mantığa sahip parçalardan ayrı kod parçalarında olur.
Bu, temel bir mantığa karşı birim testleri yazmayı çok daha kolaylaştırır.
Yazıldığında doğru çalıştığından ve bakımda bir şeyler değiştiğinde bile çalışmaya devam ettiğinden emin olun.
Genişletilebilirlik
Temiz ayırma sınırları ve daha ayrıntılı kod parçaları nedeniyle bazen sürdürülebilirlikle çakışır.
Bu parçalardan herhangi birini daha yeniden kullanılabilir hale getirme şansınız daha yüksektir.
Aynı zamanda mimaride doğru yerlere benzer şeyler yapan yeni kod parçalarını değiştirme veya ekleme yeteneğine de sahiptir.
MVVM modelinin açık amacı, arka plan kodundaki iş mantığı miktarını azaltan Görünümün soyutlanmasıdır. Bununla birlikte, diğer bazı sağlam avantajlar şunlardır:
- ViewModel'in birim testi, arka planda kodlama veya olay odaklı koddan daha kolaydır.
- Garip UI otomasyonu ve etkileşimi olmadan test edebilirsiniz.
- Sunum katmanı ve mantık gevşek bir şekilde birleştirilmiştir.
Dezavantajları
- Bazı insanlar basit kullanıcı arayüzleri için MVVM'nin gereğinden fazla olabileceğini düşünüyor.
- Benzer şekilde daha büyük durumlarda, ViewModel'i tasarlamak zor olabilir.
- Karmaşık veri bağlamalarımız olduğunda hata ayıklama biraz zor olabilir.
MVVM modeli üç bölümden oluşur - Model, Görünüm ve ViewModel. Başlangıçtaki geliştiricilerin çoğu, bir Model, View ve ViewModel'in neyi içermesi veya içermemesi gerektiği ve her bir parçanın sorumluluklarının neler olduğu konusunda biraz kafa karışıklığı yaşıyor.
Bu bölümde, ne tür bir kodun nereye gittiğini açıkça anlayabilmeniz için MVVM modelinin her bir parçasının sorumluluklarını öğreneceğiz. MVVM, aşağıdaki şekilde gösterildiği gibi istemci tarafı için gerçekten katmanlı bir mimaridir.
Sunum katmanı, görünümlerden oluşur.
Mantıksal katman, görünüm modelleridir.
Sunum katmanı, model nesnelerinin birleşimidir.
Bunları üreten ve devam ettiren istemci hizmetleri, ya iki katmanlı bir uygulamada ya da servis çağrıları aracılığıyla uygulamanıza yönlendirilmiş erişimdir.
İstemci hizmetleri resmi olarak MVVM modelinin bir parçası değildir, ancak daha fazla ayırma elde etmek ve yinelenen koddan kaçınmak için genellikle MVVM ile birlikte kullanılır.
Model Sorumlulukları
Genel olarak model, anlaşılması en basit olanıdır. Uygulamadaki görünümleri destekleyen istemci tarafı veri modelidir.
Bellekte veri barındırmak için özelliklere sahip nesnelerden ve bazı değişkenlerden oluşur.
Bu özelliklerden bazıları diğer model nesnelerine başvurabilir ve bir bütün olarak model nesneler olan nesne grafiğini oluşturabilir.
Model nesneleri, WPF'de veri bağlama anlamına gelen özellik değişikliği bildirimlerini yükseltmelidir.
Son sorumluluk, isteğe bağlı olan doğrulamadır, ancak INotifyDataErrorInfo / IDataErrorInfo gibi arabirimler aracılığıyla WPF veri bağlama doğrulama özelliklerini kullanarak doğrulama bilgilerini model nesnelerine gömebilirsiniz.
Sorumlulukları Görüntüle
Görünümlerin temel amacı ve sorumlulukları, kullanıcının ekranda gördüklerinin yapısını tanımlamaktır. Yapı, statik ve dinamik parçalar içerebilir.
Statik bölümler, bir görünümün oluşturduğu denetimlerin denetimlerini ve düzenini tanımlayan XAML hiyerarşisidir.
Dinamik kısım, Görünümün parçası olarak tanımlanan animasyonlara veya durum değişikliklerine benzer.
MVVM'nin birincil amacı, görünümde arkasında hiçbir kod olmaması gerektiğidir.
Görünüşte arkasında bir kod olmaması imkansızdır. Görünüşe göre en azından kurucuya ve bileşeni başlatmak için bir çağrıya ihtiyacınız var.
Buradaki fikir, olay işleme, eylem ve veri işleme mantık kodunun, View'da arkasındaki kodda olmaması gerektiğidir.
Ayrıca, UI öğesine referans olması gereken herhangi bir kodun arkasındaki koda girmesi gereken başka tür kodlar da doğal olarak görünüm kodudur.
ViewModel Sorumlulukları
ViewModel, MVVM uygulamasının ana noktasıdır. ViewModel'in birincil sorumluluğu, görünüme veri sağlamaktır, böylece görünüm bu verileri ekrana koyabilir.
Ayrıca kullanıcının verilerle etkileşime girmesine ve verileri değiştirmesine izin verir.
ViewModel'in diğer önemli sorumluluğu, bir görünüm için etkileşim mantığını kapsüllemektir, ancak bu, uygulamanın tüm mantığının ViewModel'e girmesi gerektiği anlamına gelmez.
Kullanıcıya veya görünümdeki herhangi bir değişikliğe bağlı olarak doğru şeyin gerçekleşmesi için çağrıların uygun şekilde sıralanmasını sağlayabilmelidir.
ViewModel, farklı bir görünüme ne zaman gitme zamanının geldiğine karar vermek gibi herhangi bir gezinme mantığını da yönetmelidir.
Bu bölümde, basit giriş ekranı ve zaten alışmış olabileceğiniz WPF uygulaması için MVVM kalıplarını nasıl kullanacağınızı öğreneceğiz.
MVVM yaklaşımını kullanacağımız basit bir örneğe bakalım.
Step 1 - Yeni bir WPF Uygulama projesi MVVMDemo oluşturun.
Step 2 - Üç klasörü (Model, ViewModel ve Görünümler) projenize ekleyin.
Step 3 - Model klasörüne bir StudentModel sınıfı ekleyin ve aşağıdaki kodu bu sınıfa yapıştırın
using System.ComponentModel;
namespace MVVMDemo.Model {
public class StudentModel {}
public class Student : INotifyPropertyChanged {
private string firstName;
private string lastName;
public string FirstName {
get {
return firstName;
}
set {
if (firstName != value) {
firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
public string LastName {
get {return lastName; }
set {
if (lastName != value) {
lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
}
public string FullName {
get {
return firstName + " " + lastName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
Step 4 - ViewModel klasörüne başka bir StudentViewModel sınıfı ekleyin ve aşağıdaki kodu yapıştırın.
using MVVMDemo.Model;
using System.Collections.ObjectModel;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
}
}
Step 5 - Görünümler klasörüne sağ tıklayıp Ekle> Yeni Öğe… öğesini seçerek yeni bir Kullanıcı Denetimi (WPF) ekleyin.
Step 6- Ekle düğmesini tıklayın. Şimdi XAML dosyasını göreceksiniz. Aşağıdaki kodu, farklı UI öğeleri içeren StudentView.xaml dosyasına ekleyin.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
Step 7 - Şimdi StudentView'ı aşağıdaki kodu kullanarak MainPage.xaml dosyanıza ekleyin.
<Window x:Class = "MVVMDemo.MainWindow"
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"
xmlns:local = "clr-namespace:MVVMDemo"
xmlns:views = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Grid>
<views:StudentView x:Name = "StudentViewControl" Loaded = "StudentViewControl_Loaded"/>
</Grid>
</Window>
Step 8 - ViewModel'den View'u güncelleyecek olan MainPage.xaml.cs dosyasındaki Loaded olayının uygulaması burada verilmiştir.
using System.Windows;
namespace MVVMDemo {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void StudentViewControl_Loaded(object sender, RoutedEventArgs e) {
MVVMDemo.ViewModel.StudentViewModel studentViewModelObject =
new MVVMDemo.ViewModel.StudentViewModel();
studentViewModelObject.LoadStudents();
StudentViewControl.DataContext = studentViewModelObject;
}
}
}
Step 9 - Yukarıdaki kod derlendiğinde ve çalıştırıldığında, ana pencerenizde aşağıdaki çıktıyı alacaksınız.
Daha iyi anlamak için yukarıdaki örneği adım adım uygulamanızı öneririz.
Bu bölümde, görünümlerinizi ViewModel'e bağlamanın farklı yollarını ele alacağız. Öncelikle, onu XAML'de ilan edebileceğimiz View first yapısına bir göz atalım. Ana pencereden bir görüntüyü bağladığımız son bölümde örneği gördüğümüz gibi. Şimdi görünümleri bağlamanın başka yollarını göreceğiz.
Bu bölümde de aynı örneği kullanacağız. Aşağıda aynı Model sınıfı uygulaması verilmiştir.
using System.ComponentModel;
namespace MVVMDemo.Model {
public class StudentModel {}
public class Student : INotifyPropertyChanged {
private string firstName;
private string lastName;
public string FirstName {
get { return firstName; }
set {
if (firstName != value) {
firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
public string LastName {
get { return lastName; }
set {
if (lastName != value) {
lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
}
public string FullName {
get {
return firstName + " " + lastName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
İşte ViewModel sınıfı uygulaması. Bu sefer LoadStudents yöntemi varsayılan yapıcıda çağrılır.
using MVVMDemo.Model;
using System.Collections.ObjectModel;
namespace MVVMDemo.ViewModel{
public class StudentViewModel {
public StudentViewModel() {
LoadStudents();
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
}
}
Görünüm ister Pencere, Kullanıcı Kontrolü veya Sayfa olsun, ayrıştırıcı genellikle yukarıdan aşağıya ve soldan sağa çalışır. Karşılaştığı her öğe için varsayılan kurucuyu çağırır. Bir görünüm oluşturmanın iki yolu vardır. Bunlardan herhangi birini kullanabilirsiniz.
- XAML'de İlk Yapıyı Görüntüleyin
- Arka Plan Kodunda İlk Yapıyı Görüntüleyin
XAML'de İlk Yapıyı Görüntüleyin
Bir yol, ViewModel'inizi aşağıdaki kodda gösterildiği gibi DataContext özelliği için ayarlayıcıya yuvalanmış bir öğe olarak eklemektir.
<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>
İşte tam Görünüm XAML dosyası.
<UserControl x:Class="MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
Arka Plan Kodunda İlk Yapıyı Görüntüleyin
Başka bir yol da, View first yapısını elde etmenin, DataContext özelliğini örnekle oraya ayarlayarak View'unuzun arkasındaki kodda görünüm modelini kendiniz oluşturmaktır.
Tipik olarak, DataContext özelliği yapıcı görünüm yönteminde ayarlanır, ancak ayrıca görünümün Load olayı tetiklenene kadar yapıyı erteleyebilirsiniz.
using System.Windows.Controls;
namespace MVVMDemo.Views {
/// <summary>
/// Interaction logic for StudentView.xaml
/// </summary>
public partial class StudentView : UserControl {
public StudentView() {
InitializeComponent();
this.DataContext = new MVVMDemo.ViewModel.StudentViewModel();
}
}
}
Görünüm modelini XAML yerine arka planda kodlamada oluşturmanın bir nedeni, View modeli yapıcısının parametreleri alması, ancak XAML ayrıştırmasının yalnızca varsayılan oluşturucuda tanımlanmışsa öğeleri oluşturabilmesidir.
Şimdi bu durumda, XAML View dosyası aşağıdaki kodda gösterildiği gibi görünecektir.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300"
d:DesignWidth = "300">
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal"<
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
Bu Görünümü MainWindow'da MainWindow.XAML dosyasında gösterildiği gibi bildirebilirsiniz.
<Window x:Class = "MVVMDemo.MainWindow"
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"
xmlns:local = "clr-namespace:MVVMDemo"
xmlns:views = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Grid>
<views:StudentView x:Name = "StudentViewControl"/>
</Grid>
</Window>
Yukarıdaki kod derlendiğinde ve çalıştırıldığında, ana pencerenizde aşağıdaki çıktıyı göreceksiniz.
Daha iyi anlamak için yukarıdaki örneği adım adım uygulamanızı öneririz.
Bu bölümde, ViewModel'in nasıl bağlanacağını ele alacağız. Bu, View ilk inşasını tartıştığımız son bölümün devamıdır. Şimdi, ilk yapının bir sonraki şekli birmeta-pattern olarak bilinen ViewModelLocator. Sözde bir modeldir ve MVVM modelinin üstüne katmanlanmıştır.
MVVM'de her Görünümün kendi ViewModel'ine bağlanması gerekir.
ViewModelLocator, kodu merkezileştirmek ve görünümü daha fazla ayırmak için basit bir yaklaşımdır.
Bu, ViewModel türü ve nasıl inşa edileceği hakkında açıkça bilgi sahibi olmak zorunda olmadığı anlamına gelir.
ViewModelLocator'ı kullanmak için bir dizi farklı yaklaşım vardır, ancak burada PRISM çerçevesinin parçası olana en benzer olanı kullanıyoruz.
ViewModelLocator, ViewModel'in View'e bağlanma sürecini otomatikleştiren ilk yapıyı görüntülemek için standart, tutarlı, bildirimsel ve gevşek bir şekilde bağlı bir yol sağlar. Aşağıdaki şekil, ViewModelLocator'ın üst düzey sürecini temsil etmektedir.
Step 1 - Hangi Görünüm türünün oluşturulmakta olduğunu öğrenin.
Step 2 - Söz konusu Görünüm tipi için ViewModel'i tanımlayın.
Step 3 - Bu ViewModel'i oluşturun.
Step 4 - View DataContext'i ViewModel olarak ayarlayın.
Temel kavramı anlamak için, son bölümdeki aynı örneğe devam ederek basit ViewModelLocator örneğine bir göz atalım. StudentView.xaml dosyasına bakarsanız, ViewModel'i statik olarak bağladığımızı göreceksiniz.
Şimdi, aşağıdaki programda gösterildiği gibi, bu XAML kodunu yorumlayın ve kodu arka planda koddan kaldırın.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<!--<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>-->
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
Şimdi yeni bir klasör VML oluşturalım ve aşağıdaki kodda gösterildiği gibi tek bir ekli özellik (bağımlılık özelliği) içeren yeni bir genel sınıf ViewModelLocator ekleyelim. AutoHookedUpViewModel.
public static bool GetAutoHookedUpViewModel(DependencyObject obj) {
return (bool)obj.GetValue(AutoHookedUpViewModelProperty);
}
public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) {
obj.SetValue(AutoHookedUpViewModelProperty, value);
}
// Using a DependencyProperty as the backing store for AutoHookedUpViewModel.
//This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoHookedUpViewModelProperty =
DependencyProperty.RegisterAttached("AutoHookedUpViewModel",
typeof(bool), typeof(ViewModelLocator), new PropertyMetadata(false,
AutoHookedUpViewModelChanged));
Ve şimdi temel bir ekleme özelliği tanımını görebilirsiniz. Özelliğe davranış eklemek için, bu özellik için ViewModel'i View için otomatik olarak bağlama işlemini içeren değiştirilmiş bir olay işleyicisi eklememiz gerekir. Bunu yapacak kod aşağıdaki gibidir -
private static void AutoHookedUpViewModelChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) {
if (DesignerProperties.GetIsInDesignMode(d)) return;
var viewType = d.GetType();
string str = viewType.FullName;
str = str.Replace(".Views.", ".ViewModel.");
var viewTypeName = str;
var viewModelTypeName = viewTypeName + "Model";
var viewModelType = Type.GetType(viewModelTypeName);
var viewModel = Activator.CreateInstance(viewModelType);
((FrameworkElement)d).DataContext = viewModel;
}
Aşağıda ViewModelLocator sınıfının tam uygulaması verilmiştir.
using System;
using System.ComponentModel;
using System.Windows;
namespace MVVMDemo.VML {
public static class ViewModelLocator {
public static bool GetAutoHookedUpViewModel(DependencyObject obj) {
return (bool)obj.GetValue(AutoHookedUpViewModelProperty);
}
public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) {
obj.SetValue(AutoHookedUpViewModelProperty, value);
}
// Using a DependencyProperty as the backing store for AutoHookedUpViewModel.
//This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoHookedUpViewModelProperty =
DependencyProperty.RegisterAttached("AutoHookedUpViewModel",
typeof(bool), typeof(ViewModelLocator), new
PropertyMetadata(false, AutoHookedUpViewModelChanged));
private static void AutoHookedUpViewModelChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) {
if (DesignerProperties.GetIsInDesignMode(d)) return;
var viewType = d.GetType();
string str = viewType.FullName;
str = str.Replace(".Views.", ".ViewModel.");
var viewTypeName = str;
var viewModelTypeName = viewTypeName + "Model";
var viewModelType = Type.GetType(viewModelTypeName);
var viewModel = Activator.CreateInstance(viewModelType);
((FrameworkElement)d).DataContext = viewModel;
}
}
}
Yapılacak ilk şey, projemizin kök dizininde o ViewModelLocator türüne ulaşabilmemiz için bir ad alanı eklemektir. Ardından, bir görünüm türü olan yönlendirme öğesinde, AutoHookedUpViewModel özelliğini ekleyin ve bunu true olarak ayarlayın.
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
İşte StudentView.xaml dosyasının tam uygulaması.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<!--<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>-->
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
Yukarıdaki kod derlendiğinde ve yürütüldüğünde, ViewModelLocator'ın söz konusu View için ViewModel'i bağladığını göreceksiniz.
Bununla ilgili dikkat edilmesi gereken en önemli şey, görünümün artık ViewModel'in türünün ne olduğu veya nasıl inşa edildiğiyle bağlantılı olmamasıdır. Hepsi ViewModelLocator içindeki merkezi konuma taşındı.
Bu bölümde, veri bağlamanın MVVM modelini nasıl desteklediğini öğreneceğiz. Veri bağlama, MVVM'yi MVC ve MVP gibi diğer UI ayırma modellerinden ayıran temel özelliktir.
Veri bağlama için bir görünüme veya UI öğeleri kümesine sahip olmanız ve ardından bağlamaların işaret edeceği başka bir nesneye ihtiyacınız vardır.
Bir görünümdeki UI öğeleri, ViewModel tarafından gösterilen özelliklere bağlıdır.
View ve ViewModel'in oluşturulma sırası, önce View'u ele aldığımız için duruma bağlıdır.
Bir View ve ViewModel oluşturulur ve View'ün DataContext'i ViewModel'e ayarlanır.
Bağlamalar, View ve ViewModel arasında ileri ve geri veri akışı sağlamak için OneWay veya TwoWay veri bağlamaları olabilir.
Aynı örnekteki veri bağlamalarına bir göz atalım. Aşağıda StudentView XAML kodu verilmiştir.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<!--<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>-->
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
Yukarıdaki XAML koduna bakarsanız, ItemsControl'ün ViewModel tarafından sunulan Öğrenciler koleksiyonuna bağlı olduğunu göreceksiniz.
Ayrıca, Öğrenci modelinin özelliğinin kendi bireysel bağlamalarına sahip olduğunu ve bunların Metin Kutularına ve TextBlock'a bağlı olduğunu da görebilirsiniz.
ItemSource of ItemsControl, Students özelliğine bağlanabilir, çünkü View için genel DataContext ViewModel olarak ayarlanmıştır.
Buradaki özelliklerin tek tek bağlamaları da DataContext bağlamalarıdır, ancak bir ItemSource'un çalışma şekli nedeniyle ViewModel'in kendisine karşı bağlanmazlar.
Bir öğe kaynağı kendi koleksiyonuna bağlandığında, işleme sırasında her öğe için bir konteyner oluşturur ve bu konteynerin DataContext'ini öğeye ayarlar. Dolayısıyla, bir satırdaki her metin kutusu ve metin bloğu için genel DataContext, koleksiyondaki bireysel bir Öğrenci olacaktır. Ayrıca TextBoxes için bu bağlamaların TwoWay veri bağlama olduğunu ve TextBlock için TextBlock'u düzenleyemediğiniz için OneWay veri bağlama olduğunu da görebilirsiniz.
Bu uygulamayı tekrar çalıştırdığınızda, aşağıdaki çıktıyı göreceksiniz.
Şimdi ilk satırın ikinci metin kutusundaki metni Allain'den Upston'a değiştirelim ve odağı kaybetmek için sekme tuşuna basalım. TextBlock metninin de güncellendiğini göreceksiniz.
Bunun nedeni, TextBoxes'ın bağlanmalarının TwoWay'e ayarlanması ve Modelin de güncellenmesi ve modelden tekrar TextBlock'un güncellenmesidir.
Bir şablon, kontrolün genel görünümünü ve görsel görünümünü tanımlar. Her kontrol için, bu kontrole görünüm veren, kendisiyle ilişkilendirilmiş varsayılan bir şablon vardır. WPF uygulamasında, bir kontrolün görsel davranışını ve görsel görünümünü özelleştirmek istediğinizde kendi şablonlarınızı kolayca oluşturabilirsiniz. Mantık ve şablon arasındaki bağlantı, veri bağlama ile sağlanabilir.
MVVM'de, ViewModel ilk yapısı olarak bilinen başka bir birincil form vardır.
ViewModel'in ilk yapım yaklaşımı, WPF'deki örtük veri şablonlarının yeteneklerinden yararlanır.
Örtük veri şablonları, veri bağlamayı kullanan bir öğe için geçerli kaynak sözlüğünden uygun bir şablonu otomatik olarak seçebilir. Bunu, veri bağlama ile oluşturulan veri nesnesinin türüne göre yaparlar. Öncelikle, bir veri nesnesine bağlanan bazı öğelere sahip olmanız gerekir.
Öncelikle veri şablonlarından, özellikle örtük veri şablonlarından yararlanarak modeli nasıl görüntüleyebileceğinizi anlayacağınız basit örneğimize tekrar bir göz atalım. İşte StudentViewModel sınıfımızın uygulaması.
using MVVMDemo.Model;
using System.Collections.ObjectModel;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public StudentViewModel() {
LoadStudents();
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
}
}
Yukarıdaki ViewModel'in değişmediğini görebilirsiniz. Bir önceki bölümden aynı örnekle devam edeceğiz. Bu ViewModel sınıfı, Students collection özelliğini ortaya çıkarır ve onu yapım aşamasında doldurur. StudentView.xaml dosyasına gidelim, mevcut uygulamayı kaldıralım ve Kaynaklar bölümünde bir veri şablonu tanımlayalım.
<UserControl.Resources>
<DataTemplate x:Key = "studentsTemplate">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
Şimdi bir liste kutusu ekleyin ve bu liste kutusunu aşağıdaki kodda gösterildiği gibi Öğrenciler özelliğine bağlayın.
<ListBox ItemsSource = "{Binding Students}" ItemTemplate = "{StaticResource studentsTemplate}"/>
Kaynak bölümünde, DataTemplate bir studentTemplate anahtarına sahiptir ve ardından bu şablonu gerçekten kullanmak için bir ListBox öğesinin ItemTemplate özelliğini kullanmamız gerekir. Şimdi liste kutusuna, bu Öğrencileri işlemek için o belirli şablonu kullanması talimatını verdiğimizi görebilirsiniz. Aşağıda StudentView.xaml dosyasının tam uygulaması verilmiştir.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate x:Key = "studentsTemplate">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox
ItemsSource = "{Binding Students}"
ItemTemplate = "{StaticResource studentsTemplate}"/>
</Grid>
</UserControl>
Yukarıdaki kod derlendiğinde ve çalıştırıldığında, bir ListBox içeren aşağıdaki pencereyi göreceksiniz. Her ListBoxItem, TextBlock ve Text kutularında görüntülenen Student sınıfı nesne verilerini içerir.
Bunu örtük bir şablon yapmak için, ItemTemplate özelliğini bir liste kutusundan kaldırmamız ve aşağıdaki kodda gösterildiği gibi şablon tanımımıza bir DataType özelliği eklememiz gerekir.
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource = "{Binding Students}"/>
</Grid>
DataTemplate'te, x: Type işaretleme uzantısı çok önemlidir, bu da XAML'deki bir operatör türü gibidir. Bu nedenle, temel olarak MVVMDemo.Model ad alanında bulunan Öğrenci veri türüne işaret etmemiz gerekir. Aşağıda güncellenmiş tam XAML dosyası verilmiştir.
<UserControl x:Class="MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:data = "clr-namespace:MVVMDemo.Model"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource = "{Binding Students}"/>
</Grid>
</UserControl>
Bu uygulamayı tekrar çalıştırdığınızda, uygun DataTemplate bulunarak oluşturulan nesnenin türünü otomatik olarak eşleştirdiği için, Öğrenciler ile veri şablonunun aynı işlemesini almaya devam edeceksiniz.
Daha iyi anlamak için yukarıdaki örneği adım adım uygulamanızı öneririz.
Bu bölümde, MVVM uygulamalarına nasıl etkileşim ekleneceğini ve mantığın temiz bir şekilde nasıl çağrılacağını öğreneceğiz. Tüm bunların, MVVM modelinin kalbi olan gevşek bağlantı ve iyi yapılanma sağlanarak yapıldığını da göreceksiniz. Tüm bunları anlamak için önce komutları öğrenelim.
Komutlar aracılığıyla Model İletişimini Görüntüle / Görüntüle
Komut kalıbı iyi bir şekilde belgelenmiştir ve birkaç on yıldır tasarım modelini sıklıkla kullanır. Bu örüntüde iki ana aktör vardır, çağıran ve alan.
Invoker
Çağıran, bazı zorunlu mantığı çalıştırabilen bir kod parçasıdır.
Tipik olarak, bir UI çerçevesi bağlamında kullanıcının etkileşime girdiği bir UI öğesidir.
Bu, uygulamanın başka bir yerindeki başka bir mantık kodu parçası olabilir.
Alıcı
Alıcı, çağıran kişi ateşlediğinde yürütülmesi amaçlanan mantıktır.
MVVM bağlamında, alıcı genellikle ViewModel'inizde çağrılması gereken bir yöntemdir.
Bu ikisi arasında, çağıran ile alıcının birbirini açıkça bilmesine gerek olmadığı anlamına gelen bir engelleme katmanınız var. Bu tipik olarak, çağırıcıya maruz kalan bir arayüz soyutlaması olarak temsil edilir ve bu arayüzün somut bir uygulaması alıcıyı arayabilir.
Komutları ve bunları View ile ViewModel arasında iletişim kurmak için nasıl kullanacağınızı öğreneceğiniz basit bir örneğe bakalım. Bu bölümde, son bölümdeki aynı örnekle devam edeceğiz.
StudentView.xaml dosyasında, öğrenci verilerini ViewModel'den bağlayan bir ListBox var. Şimdi bir öğrenciyi ListBox'tan silmek için bir düğme ekleyelim.
Önemli olan, düğme üzerindeki komutlarla çalışmanın çok kolay olmasıdır çünkü bir ICommand'e bağlanmak için bir komut özelliğine sahiptirler.
Böylece, ViewModel'imizde bir ICommand'e sahip olan ve aşağıdaki kodda gösterildiği gibi butonun command özelliğinden ona bağlanan bir özelliği açığa çıkarabiliriz.
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75" />
Projenize ICommand arayüzünü uygulayacak yeni bir sınıf ekleyelim. Aşağıda ICommand arayüzünün uygulanması yer almaktadır.
using System;
using System.Windows.Input;
namespace MVVMDemo {
public class MyICommand : ICommand {
Action _TargetExecuteMethod;
Func<bool> _TargetCanExecuteMethod;
public MyICommand(Action executeMethod) {
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action executeMethod, Func<bool> canExecuteMethod){
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged() {
CanExecuteChanged(this, EventArgs.Empty);
}
bool ICommand.CanExecute(object parameter) {
if (_TargetCanExecuteMethod != null) {
return _TargetCanExecuteMethod();
}
if (_TargetExecuteMethod != null) {
return true;
}
return false;
}
// Beware - should use weak references if command instance lifetime
is longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter) {
if (_TargetExecuteMethod != null) {
_TargetExecuteMethod();
}
}
}
}
Gördüğünüz gibi, bu ICommand'in basit bir delege uygulamasıdır; burada biri executeMethod ve diğeri de canExecuteMethod için inşa sırasında aktarılabilen iki delegemiz var.
Yukarıdaki uygulamada, biri yalnızca executeMethod ve diğeri hem executeMethod hem de I canExecuteMethod için olmak üzere iki aşırı yüklenmiş kurucu vardır.
StudentView Model sınıfına MyICommand türünün bir özelliğini ekleyelim. Şimdi StudentViewModel'de bir örnek oluşturmamız gerekiyor. İki parametre alan MyICommand'ın aşırı yüklenmiş yapıcısını kullanacağız.
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
Şimdi OnDelete ve CanDelete yöntemlerinin uygulamasını ekleyin.
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
Ayrıca, kullanıcının Seçilen Öğeyi ListBox'tan silebilmesi için yeni bir SelectedStudent eklememiz gerekir.
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
Aşağıda ViewModel sınıfının tam uygulaması verilmiştir.
using MVVMDemo.Model;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
}
}
StudentView.xaml'de, SelectStudent özelliğine bağlanacak bir ListBox'a SelectedItem özelliğini eklememiz gerekir.
<ListBox ItemsSource = "{Binding Students}" SelectedItem = "{Binding SelectedStudent}"/>
Tam xaml dosyası aşağıdadır.
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:data = "clr-namespace:MVVMDemo.Model"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Orientation = "Horizontal">
<ListBox ItemsSource = "{Binding Students}"
SelectedItem = "{Binding SelectedStudent}"/>
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75" />
</StackPanel>
</Grid>
</UserControl>
Yukarıdaki kod derlendiğinde ve çalıştırıldığında, aşağıdaki pencereyi göreceksiniz.
Sil düğmesinin devre dışı bırakıldığını görebilirsiniz. Herhangi bir öğeyi seçtiğinizde etkinleştirilecektir.
Herhangi bir öğeyi seçip sil tuşuna bastığınızda. Seçilen öğe listesinin silindiğini ve sil düğmesinin tekrar devre dışı bırakıldığını göreceksiniz.
Daha iyi anlamak için yukarıdaki örneği adım adım uygulamanızı öneririz.
MVVM uygulamaları oluştururken, genellikle karmaşık bilgi ekranlarını, alt görünümlerin paneller veya konteyner kontrollerindeki üst görünümler içinde bulunduğu ve kendilerinin bir kullanım hiyerarşisi oluşturduğu bir üst ve alt görünümler kümesine ayırırsınız.
Karmaşık Görünümleri ayrıştırdıktan sonra, kendi XAML dosyasına ayırdığınız her alt içerik parçasının mutlaka bir MVVM görünümü olması gerektiği anlamına gelmez.
İçerik yığını, sadece ekrana bir şeyler işlemek için bir yapı sağlar ve bu içerik için kullanıcı tarafından herhangi bir girdi veya manipülasyonu desteklemez.
Ayrı bir ViewModel'e ihtiyaç duymayabilir, ancak yalnızca ana ViewModel tarafından sunulan özelliklere dayalı olarak işleyen bir yığın XAML olabilir.
Son olarak, bir Görünümler ve ViewModel hiyerarşisine sahipseniz, ana ViewModel iletişimler için bir merkez haline gelebilir, böylece her alt ViewModel, diğer alt ViewModel'lerden ve üstlerinden mümkün olduğunca ayrı kalabilir.
Farklı görünümler arasında basit bir hiyerarşi tanımlayacağımız bir örneğe bakalım. Yeni bir WPF Uygulama projesi oluşturunMVVMHierarchiesDemo
Step 1 - Üç klasörü (Model, ViewModel ve Görünümler) projenize ekleyin.
Step 2 - Aşağıdaki görüntüde gösterildiği gibi Model klasöründe Müşteri ve Sipariş sınıfları, Görünümler klasöründe CustomerListView ve OrderView ve ViewModel klasöründe CustomerListViewModel ve OrderViewModel ekleyin.
Step 3- Hem CustomerListView hem de OrderView'da metin blokları ekleyin. İşte CustomerListView.xaml dosyası.
<UserControl x:Class="MVVMHierarchiesDemo.Views.CustomerListView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<TextBlock Text = "Customer List View"/>
</Grid>
</UserControl>
OrderView.xaml dosyası aşağıdadır.
<UserControl x:Class = "MVVMHierarchiesDemo.Views.OrderView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d ="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views" mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<TextBlock Text = "Order View"/>
</Grid>
</UserControl>
Şimdi bu görünümleri barındıracak bir şeye ve bunun için MainWindow'umuzda iyi bir yere ihtiyacımız var çünkü bu basit bir uygulama. Görüşlerimizi yerleştirebileceğimiz ve onları navigasyon tarzında değiştirebileceğimiz bir konteyner kontrolüne ihtiyacımız var. Bu amaçla, MainWindow.xaml dosyamıza ContentControl eklememiz gerekiyor ve bunun content özelliğini kullanacağız ve bunu bir ViewModel referansına bağlayacağız.
Şimdi bir kaynak sözlüğünde her görünüm için veri şablonlarını tanımlayın. MainWindow.xaml dosyası aşağıdadır. Her veri şablonunun bir veri türünü (ViewModel türü) karşılık gelen bir Görünüme nasıl eşlediğine dikkat edin.
<Window x:Class = "MVVMHierarchiesDemo.MainWindow"
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"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo"
xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views"
xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
<views:OrderView/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content = "{Binding CurrentView}"/>
</Grid>
</Window>
Geçerli görünüm modeli, bir CustomerListViewModel'in bir örneğine ayarlandığında, ViewModel bağlanmış bir CustomerListView oluşturur. Bu bir sipariş ViewModel, OrderView'ı oluşturacak ve benzeri.
Şimdi, bir CurrentViewModel özelliğine ve özellik içindeki ViewModel'in geçerli referansını değiştirebilmek için bazı mantığa ve komuta sahip bir ViewModel'e ihtiyacımız var.
Bu MainWindow için MainWindowViewModel adlı bir ViewModel oluşturalım. XAML'den ViewModel'imizin bir örneğini oluşturabilir ve bunu pencerenin DataContext özelliğini ayarlamak için kullanabiliriz. Bunun için, ViewModel'lerimiz için INotifyPropertyChanged uygulamasının kapsüllenmesi için bir temel sınıf oluşturmamız gerekiyor.
Bu sınıfın arkasındaki ana fikir, INotifyPropertyChanged uygulamasını kapsüllemek ve türetilmiş sınıfa yardımcı yöntemler sağlamaktır, böylece uygun bildirimleri kolayca tetikleyebilirler. BindableBase sınıfının uygulaması aşağıdadır.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo {
class BindableBase : INotifyPropertyChanged {
protected virtual void SetProperty<T>(ref T member, T val,
[CallerMemberName] string propertyName = null) {
if (object.Equals(member, val)) return;
member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}
Şimdi, CurrentViewModel özelliğimizi kullanarak gerçekten bazı görünüm geçişleri yapmaya başlama zamanı. Bu mülkün yerleşimini sürmek için bir yola ihtiyacımız var. Ve bunu, son kullanıcının müşteri listesine veya sipariş görünümüne gitme komutunu verebilmesi için yapacağız. Öncelikle projenize ICommand arayüzünü uygulayacak yeni bir sınıf ekleyin. Aşağıda ICommand arayüzünün uygulanması yer almaktadır.
using System;
using System.Windows.Input;
namespace MVVMHierarchiesDemo {
public class MyICommand<T> : ICommand {
Action<T> _TargetExecuteMethod;
Func<T, bool> _TargetCanExecuteMethod;
public MyICommand(Action<T> executeMethod) {
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod) {
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged() {
CanExecuteChanged(this, EventArgs.Empty);
}
#region ICommand Members
bool ICommand.CanExecute(object parameter) {
if (_TargetCanExecuteMethod != null) {
T tparm = (T)parameter;
return _TargetCanExecuteMethod(tparm);
}
if (_TargetExecuteMethod != null) {
return true;
}
return false;
}
// Beware - should use weak references if command instance lifetime is
longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter) {
if (_TargetExecuteMethod != null) {
_TargetExecuteMethod((T)parameter);
}
}
#endregion
}
}
Şimdi bunlara ViewModels için üst düzey gezinme ayarlamamız gerekiyor ve bu anahtarlama mantığı MainWindowViewModel'e ait olmalıdır. Bunun için, bir dize hedefi alan ve CurrentViewModel özelliğini döndüren navigate adında bir yöntem kullanacağız.
private void OnNav(string destination) {
switch (destination) {
case "orders":
CurrentViewModel = orderViewModelModel;
break;
case "customers":
default:
CurrentViewModel = custListViewModel;
break;
}
}
Bu farklı Görünümlerde gezinmek için MainWindow.xaml dosyamıza iki düğme eklememiz gerekiyor. Aşağıda tam XAML dosyası uygulaması verilmiştir.
<Window x:Class = "MVVMHierarchiesDemo.MainWindow"
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"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo"
xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views"
xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
<views:OrderView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto" />
<RowDefinition Height = "*" />
</Grid.RowDefinitions>
<Grid x:Name = "NavBar">
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "*" />
<ColumnDefinition Width = "*" />
<ColumnDefinition Width = "*" />
</Grid.ColumnDefinitions>
<Button Content = "Customers"
Command = "{Binding NavCommand}"
CommandParameter = "customers"
Grid.Column = "0" />
<Button Content = "Order"
Command = "{Binding NavCommand}"
CommandParameter = "orders"
Grid.Column = "2" />
</Grid>
<Grid x:Name = "MainContent" Grid.Row = "1">
<ContentControl Content = "{Binding CurrentViewModel}" />
</Grid>
</Grid>
</Window>
Tam MainWindowViewModel uygulaması aşağıdadır.
using MVVMHierarchiesDemo.ViewModel;
using MVVMHierarchiesDemo.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo {
class MainWindowViewModel : BindableBase {
public MainWindowViewModel() {
NavCommand = new MyICommand<string>(OnNav);
}
private CustomerListViewModel custListViewModel = new CustomerListViewModel();
private OrderViewModel orderViewModelModel = new OrderViewModel();
private BindableBase _CurrentViewModel;
public BindableBase CurrentViewModel {
get {return _CurrentViewModel;}
set {SetProperty(ref _CurrentViewModel, value);}
}
public MyICommand<string> NavCommand { get; private set; }
private void OnNav(string destination) {
switch (destination) {
case "orders":
CurrentViewModel = orderViewModelModel;
break;
case "customers":
default:
CurrentViewModel = custListViewModel;
break;
}
}
}
}
Tüm ViewModel'lerinizi BindableBase sınıfından türetin. Yukarıdaki kod derlendiğinde ve çalıştırıldığında, aşağıdaki çıktıyı göreceksiniz.
Gördüğünüz gibi MainWindow'umuza sadece iki düğme ve bir CurrentViewModel ekledik. Herhangi bir düğmeye tıklarsanız, o belirli Görünüme gidecektir. Müşteriler düğmesine tıklayalım ve CustomerListView'ün görüntülendiğini göreceksiniz.
Daha iyi anlamak için yukarıdaki örneği adım adım uygulamanızı öneririz.
Bu bölümde, doğrulamaları öğreneceğiz. Ayrıca, WPF bağlamalarının zaten desteklediği ancak onu MVVM bileşenlerine bağlayarak doğrulama yapmanın temiz bir yolunu da inceleyeceğiz.
MVVM'de doğrulama
Uygulamanız son kullanıcılardan veri girişini kabul etmeye başladığında, bu girişi doğrulamayı düşünmeniz gerekir.
Genel gereksinimlerinize uygun olduğundan emin olun.
WPF, girişi doğrulamak için bağlama sisteminde bazı harika yapılara ve özelliklere sahiptir ve yine de MVVM yaparken tüm bu özelliklerden yararlanabilirsiniz.
Doğrulamanızı destekleyen ve hangi özellikler için hangi kuralların var olduğunu tanımlayan mantığın, Görünümün kendisi değil, Model veya ViewModel'in bir parçası olması gerektiğini unutmayın.
Yine de WPF veri bağlama tarafından desteklenen doğrulamayı ifade etmenin tüm yollarını kullanabilirsiniz:
- Bir özelliğe istisnaların atılması ayarlanır.
- IDataErrorInfo arabirimini uygulama.
- INotifyDataErrorInfo uygulanıyor.
- WPF doğrulama kurallarını kullanın.
Genel olarak, INotifyDataErrorInfo önerilir ve WPF .net 4.5'e tanıtılmıştır ve nesnenin özelliklerle ilişkili hatalar için sorgulanmasını destekler ve ayrıca diğer tüm seçeneklerle birlikte birkaç eksikliği giderir. Özellikle, eşzamansız doğrulamaya izin verir. Özelliklerin kendileriyle ilişkili birden fazla hataya sahip olmasına izin verir.
Doğrulama Ekleme
Girdi görünümümüze doğrulama desteği ekleyeceğimiz bir örneğe bir göz atalım ve büyük bir uygulamada buna muhtemelen uygulamanızda birkaç yerde ihtiyacınız olacak. Bazen Görünümlerde, bazen ViewModellerde ve bazen bu yardımcı nesnelerde model nesnelerinin etrafında sarmalayıcılar bulunur.
Doğrulama desteğini, daha sonra farklı senaryolardan devralabileceğiniz ortak bir temel sınıfa koymak için iyi bir uygulamadır.
Temel sınıf, özellikler değiştiğinde doğrulama tetiklenecek şekilde INotifyDataErrorInfo özelliğini destekler.
ValidatableBindableBase adlı yeni bir sınıf oluşturun. Bir özellik değişikliği işlemesi için zaten bir temel sınıfa sahip olduğumuzdan, temel sınıfı ondan türetelim ve ayrıca INotifyDataErrorInfo arayüzünü uygulayalım.
Aşağıda ValidatableBindableBase sınıfının uygulaması verilmiştir.
using System;
using System.Collections.Generic;
using System.ComponentModel;
//using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace MVVMHierarchiesDemo {
public class ValidatableBindableBase : BindableBase, INotifyDataErrorInfo {
private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
public event EventHandler<DataErrorsChangedEventArgs>
ErrorsChanged = delegate { };
public System.Collections.IEnumerable GetErrors(string propertyName) {
if (_errors.ContainsKey(propertyName))
return _errors[propertyName];
else
return null;
}
public bool HasErrors {
get { return _errors.Count > 0; }
}
protected override void SetProperty<T>(ref T member, T val,
[CallerMemberName] string propertyName = null) {
base.SetProperty<T>(ref member, val, propertyName);
ValidateProperty(propertyName, val);
}
private void ValidateProperty<T>(string propertyName, T value) {
var results = new List<ValidationResult>();
//ValidationContext context = new ValidationContext(this);
//context.MemberName = propertyName;
//Validator.TryValidateProperty(value, context, results);
if (results.Any()) {
//_errors[propertyName] = results.Select(c => c.ErrorMessage).ToList();
} else {
_errors.Remove(propertyName);
}
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
}
Şimdi AddEditCustomerView ve AddEditCustomerViewModel'i ilgili klasörlere ekleyin. AddEditCustomerView.xaml kodu aşağıdadır.
<UserControl x:Class = "MVVMHierarchiesDemo.Views.AddEditCustomerView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto" />
<RowDefinition Height = "Auto" />
</Grid.RowDefinitions>
<Grid x:Name = "grid1"
HorizontalAlignment = "Left"
DataContext = "{Binding Customer}"
Margin = "10,10,0,0"
VerticalAlignment = "Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "Auto" />
<ColumnDefinition Width = "Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto" />
<RowDefinition Height = "Auto" />
<RowDefinition Height = "Auto" />
<RowDefinition Height = "Auto" />
</Grid.RowDefinitions>
<Label Content = "First Name:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "0"
VerticalAlignment = "Center" />
<TextBox x:Name = "firstNameTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "0"
Text = "{Binding FirstName, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
<Label Content = "Last Name:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "1"
VerticalAlignment = "Center" />
<TextBox x:Name = "lastNameTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "1"
Text = "{Binding LastName, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
<Label Content = "Email:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "2"
VerticalAlignment = "Center" />
<TextBox x:Name = "emailTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "2"
Text = "{Binding Email, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
<Label Content = "Phone:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "3"
VerticalAlignment = "Center" />
<TextBox x:Name = "phoneTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "3"
Text = "{Binding Phone, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120" />
</Grid>
<Grid Grid.Row = "1">
<Button Content = "Save"
Command = "{Binding SaveCommand}"
HorizontalAlignment = "Left"
Margin = "25,5,0,0"
VerticalAlignment = "Top"
Width = "75" />
<Button Content = "Add"
Command = "{Binding SaveCommand}"
HorizontalAlignment = "Left"
Margin = "25,5,0,0"
VerticalAlignment = "Top"
Width = "75" />
<Button Content = "Cancel"
Command = "{Binding CancelCommand}"
HorizontalAlignment = "Left"
Margin = "150,5,0,0"
VerticalAlignment = "Top"
Width = "75" />
</Grid>
</Grid>
</UserControl>
AddEditCustomerViewModel uygulaması aşağıdadır.
using MVVMHierarchiesDemo.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.ViewModel {
class AddEditCustomerViewModel : BindableBase {
public AddEditCustomerViewModel() {
CancelCommand = new MyIcommand(OnCancel);
SaveCommand = new MyIcommand(OnSave, CanSave);
}
private bool _EditMode;
public bool EditMode {
get { return _EditMode; }
set { SetProperty(ref _EditMode, value);}
}
private SimpleEditableCustomer _Customer;
public SimpleEditableCustomer Customer {
get { return _Customer; }
set { SetProperty(ref _Customer, value);}
}
private Customer _editingCustomer = null;
public void SetCustomer(Customer cust) {
_editingCustomer = cust;
if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged;
Customer = new SimpleEditableCustomer();
Customer.ErrorsChanged += RaiseCanExecuteChanged;
CopyCustomer(cust, Customer);
}
private void RaiseCanExecuteChanged(object sender, EventArgs e) {
SaveCommand.RaiseCanExecuteChanged();
}
public MyIcommand CancelCommand { get; private set; }
public MyIcommand SaveCommand { get; private set; }
public event Action Done = delegate { };
private void OnCancel() {
Done();
}
private async void OnSave() {
Done();
}
private bool CanSave() {
return !Customer.HasErrors;
}
}
}
SimpleEditableCustomer sınıfının uygulaması aşağıdadır.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.Model {
public class SimpleEditableCustomer : ValidatableBindableBase {
private Guid _id;
public Guid Id {
get { return _id; }
set { SetProperty(ref _id, value); }
}
private string _firstName;
[Required]
public string FirstName {
get { return _firstName; }
set { SetProperty(ref _firstName, value); }
}
private string _lastName;
[Required]
public string LastName {
get { return _lastName; }
set { SetProperty(ref _lastName, value); }
}
private string _email;
[EmailAddress]
public string Email {
get { return _email; }
set { SetProperty(ref _email, value); }
}
private string _phone;
[Phone]
public string Phone {
get { return _phone; }
set { SetProperty(ref _phone, value); }
}
}
}
Yukarıdaki kod derlendiğinde ve çalıştırıldığında, aşağıdaki pencereyi göreceksiniz.
Müşteri Ekle düğmesine bastığınızda aşağıdaki görünümü göreceksiniz. Kullanıcı herhangi bir alanı boş bıraktığında, o alan vurgulanacak ve kaydet düğmesi devre dışı bırakılacaktır.
Bu bölümde, bağımlılık enjeksiyonu hakkında kısaca tartışacağız. İletişimin diğer ucunda neler olup bittiğini açıkça bilmeden iletişim kurmalarına olanak tanıyan veri bağlama ayrıştırıcı Görünümleri ve ViewModel'leri zaten ele aldık.
Şimdi ViewModel'imizi müşteri hizmetlerinden ayırmak için benzer bir şeye ihtiyacımız var.
Nesne yönelimli programlamanın ilk günlerinde, geliştiriciler uygulamalardaki sınıf örneklerini oluşturma ve alma sorunuyla karşı karşıya kaldılar. Bu sorun için çeşitli çözümler önerilmiştir.
Son birkaç yıldır, bağımlılık enjeksiyonu ve kontrolün tersine çevrilmesi (IoC) geliştiriciler arasında popülerlik kazandı ve Singleton modeli gibi bazı eski çözümlere göre öncelik kazandı.
Bağımlılık Enjeksiyonu / IoC Konteynerleri
IoC ve bağımlılık enjeksiyonu birbiriyle yakından ilişkili iki tasarım modelidir ve kapsayıcı temelde bu modellerin her ikisini de sizin için yapan bir altyapı kodu yığınıdır.
IoC modeli, inşaat için sorumluluk devretmekle ilgilidir ve bağımlılık enjeksiyon modeli, zaten oluşturulmuş bir nesneye bağımlılıklar sağlamakla ilgilidir.
Her ikisi de inşaata iki aşamalı bir yaklaşım olarak ele alınabilir. Bir konteyner kullandığınızda, konteyner aşağıdaki gibi çeşitli sorumluluklar alır -
- İstendiğinde bir nesne oluşturur.
- Kap, bu nesnenin neye bağlı olduğunu belirleyecektir.
- Bu bağımlılıkları oluşturmak.
- Bunları inşa edilmekte olan nesneye enjekte etmek.
- Yinelemeli olarak işlem yapmak.
ViewModels ile istemci hizmetleri arasındaki ayrışmayı kesmek için bağımlılık enjeksiyonunu nasıl kullanabileceğimize bir göz atalım. Bununla ilgili bağımlılık enjeksiyonu kullanarak kaydetme işleme AddEditCustomerViewModel formunu bağlayacağız.
Öncelikle Hizmetler klasöründeki projemizde yeni bir arayüz oluşturmamız gerekiyor. Projenizde bir hizmetler klasörünüz yoksa, önce onu oluşturun ve aşağıdaki arabirimi Hizmetler klasörüne ekleyin.
using MVVMHierarchiesDemo.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.Services {
public interface ICustomersRepository {
Task<List<Customer>> GetCustomersAsync();
Task<Customer> GetCustomerAsync(Guid id);
Task<Customer> AddCustomerAsync(Customer customer);
Task<Customer> UpdateCustomerAsync(Customer customer);
Task DeleteCustomerAsync(Guid customerId);
}
}
Aşağıda, ICustomersRepository uygulaması yer almaktadır.
using MVVMHierarchiesDemo.Model;
using System;
using System.Collections.Generic;
using System.Linq; using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.Services {
public class CustomersRepository : ICustomersRepository {
ZzaDbContext _context = new ZzaDbContext();
public Task<List<Customer>> GetCustomersAsync() {
return _context.Customers.ToListAsync();
}
public Task<Customer> GetCustomerAsync(Guid id) {
return _context.Customers.FirstOrDefaultAsync(c => c.Id == id);
}
public async Task<Customer> AddCustomerAsync(Customer customer){
_context.Customers.Add(customer);
await _context.SaveChangesAsync();
return customer;
}
public async Task<Customer> UpdateCustomerAsync(Customer customer) {
if (!_context.Customers.Local.Any(c => c.Id == customer.Id)) {
_context.Customers.Attach(customer);
}
_context.Entry(customer).State = EntityState.Modified;
await _context.SaveChangesAsync();
return customer;
}
public async Task DeleteCustomerAsync(Guid customerId) {
var customer = _context.Customers.FirstOrDefault(c => c.Id == customerId);
if (customer != null) {
_context.Customers.Remove(customer);
}
await _context.SaveChangesAsync();
}
}
}
Kaydetme işlemini yapmanın basit yolu, AddEditCustomerViewModel'e yeni bir ICustomersRepository örneği eklemek ve AddEditCustomerViewModel ve CustomerListViewModel yapıcısını aşırı yüklemektir.
private ICustomersRepository _repo;
public AddEditCustomerViewModel(ICustomersRepository repo) {
_repo = repo;
CancelCommand = new MyIcommand(OnCancel);
SaveCommand = new MyIcommand(OnSave, CanSave);
}
Aşağıdaki kodda gösterildiği gibi OnSave yöntemini güncelleyin.
private async void OnSave() {
UpdateCustomer(Customer, _editingCustomer);
if (EditMode)
await _repo.UpdateCustomerAsync(_editingCustomer);
else
await _repo.AddCustomerAsync(_editingCustomer);
Done();
}
private void UpdateCustomer(SimpleEditableCustomer source, Customer target) {
target.FirstName = source.FirstName;
target.LastName = source.LastName;
target.Phone = source.Phone;
target.Email = source.Email;
}
Tam AddEditCustomerViewModel aşağıdadır.
using MVVMHierarchiesDemo.Model;
using MVVMHierarchiesDemo.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.ViewModel {
class AddEditCustomerViewModel : BindableBase {
private ICustomersRepository _repo;
public AddEditCustomerViewModel(ICustomersRepository repo) {
_repo = repo;
CancelCommand = new MyIcommand(OnCancel);
SaveCommand = new MyIcommand(OnSave, CanSave);
}
private bool _EditMode;
public bool EditMode {
get { return _EditMode; }
set { SetProperty(ref _EditMode, value); }
}
private SimpleEditableCustomer _Customer;
public SimpleEditableCustomer Customer {
get { return _Customer; }
set { SetProperty(ref _Customer, value); }
}
private Customer _editingCustomer = null;
public void SetCustomer(Customer cust) {
_editingCustomer = cust;
if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged;
Customer = new SimpleEditableCustomer();
Customer.ErrorsChanged += RaiseCanExecuteChanged;
CopyCustomer(cust, Customer);
}
private void RaiseCanExecuteChanged(object sender, EventArgs e) {
SaveCommand.RaiseCanExecuteChanged();
}
public MyIcommand CancelCommand { get; private set; }
public MyIcommand SaveCommand { get; private set; }
public event Action Done = delegate { };
private void OnCancel() {
Done();
}
private async void OnSave() {
UpdateCustomer(Customer, _editingCustomer);
if (EditMode)
await _repo.UpdateCustomerAsync(_editingCustomer);
else
await _repo.AddCustomerAsync(_editingCustomer);
Done();
}
private void UpdateCustomer(SimpleEditableCustomer source, Customer target) {
target.FirstName = source.FirstName;
target.LastName = source.LastName;
target.Phone = source.Phone;
target.Email = source.Email;
}
private bool CanSave() {
return !Customer.HasErrors;
}
private void CopyCustomer(Customer source, SimpleEditableCustomer target) {
target.Id = source.Id;
if (EditMode) {
target.FirstName = source.FirstName;
target.LastName = source.LastName;
target.Phone = source.Phone;
target.Email = source.Email;
}
}
}
}
Yukarıdaki kod derlendiğinde ve çalıştırıldığında, aynı çıktıyı göreceksiniz, ancak şimdi ViewModel'ler daha gevşek bir şekilde ayrıştırılmış durumda.
Müşteri Ekle düğmesine bastığınızda aşağıdaki görünümü göreceksiniz. Kullanıcı herhangi bir alanı boş bıraktığında, o alan vurgulanacak ve kaydet düğmesi devre dışı bırakılacaktır.
Bir olay, durumdaki bir değişikliğe tepki veren ve bildirim için kaydedilen tüm uç noktaları bildiren bir programlama yapısıdır. Öncelikle, olaylar fare ve klavye aracılığıyla bir kullanıcıyı bilgilendirmek için kullanılır, ancak bunların kullanışlılığı bununla sınırlı değildir. Bir durum değişikliği tespit edildiğinde, belki bir nesne yüklendiğinde veya başlatıldığında, ilgili üçüncü tarafları uyarmak için bir olay tetiklenebilir.
MVVM (Model-View-ViewModel) tasarım modelini kullanan bir WPF uygulamasında, görünüm modeli, uygulamanın sunum mantığını ve durumunu işlemekten sorumlu olan bileşendir.
Görünümün arka plan kod dosyası, Button veya ComboBox gibi herhangi bir Kullanıcı Arabirimi (UI) öğesinden kaynaklanan olayları işlemek için hiçbir kod içermemeli veya etki alanına özel herhangi bir mantık içermemelidir.
İdeal olarak, bir Görünümün arka plan kodu, yalnızca InitializeComponent yöntemini çağıran bir kurucu ve belki de XAML'de ifade edilmesi zor veya verimsiz olan görünüm katmanını kontrol etmek veya etkileşimde bulunmak için bazı ek kodlar, örneğin karmaşık animasyonlar içerir.
Uygulamamızdaki düğme tıklama olaylarının basit bir örneğine bakalım. Aşağıda, iki düğme göreceğiniz MainWindow.xaml dosyasının XAML kodu verilmiştir.
<Window x:Class = "MVVMHierarchiesDemo.MainWindow"
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"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo"
xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views"
xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
<views:OrderView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto" />
<RowDefinition Height = "*" />
</Grid.RowDefinitions>
<Grid x:Name = "NavBar">
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "*" />
<ColumnDefinition Width = "*" />
<ColumnDefinition Width = "*" />
</Grid.ColumnDefinitions>
<Button Content = "Customers"
Command = "{Binding NavCommand}"
CommandParameter = "customers"
Grid.Column = "0" />
<Button Content = "Order"
Command = "{Binding NavCommand}"
CommandParameter = "orders"
Grid.Column = "2" />
</Grid>
<Grid x:Name = "MainContent" Grid.Row = "1">
<ContentControl Content = "{Binding CurrentViewModel}" />
</Grid>
</Grid>
</Window>
Yukarıdaki XAML dosyasında button Click özelliğinin kullanılmadığını ancak Command ve CommandParameter özelliklerinin, düğmeye basıldığında farklı Görünümler yüklemek için kullanıldığını görebilirsiniz. Şimdi MainWindowViewModel.cs dosyasında komut uygulamasını tanımlamanız gerekir, ancak View dosyasında tanımlamamanız gerekir. Tam MainWindowViewModel uygulaması aşağıdadır.
using MVVMHierarchiesDemo.ViewModel;
using MVVMHierarchiesDemo.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo {
class MainWindowViewModel : BindableBase {
public MainWindowViewModel() {
NavCommand = new MyICommand<string>(OnNav);
}
private CustomerListViewModel custListViewModel = new CustomerListViewModel();
private OrderViewModel orderViewModelModel = new OrderViewModel();
private BindableBase _CurrentViewModel;
public BindableBase CurrentViewModel {
get { return _CurrentViewModel; }
set { SetProperty(ref _CurrentViewModel, value); }
}
public MyICommand<string> NavCommand { get; private set; }
private void OnNav(string destination) {
switch (destination) {
case "orders":
CurrentViewModel = orderViewModelModel;
break;
case "customers":
default:
CurrentViewModel = custListViewModel;
break;
}
}
}
}
Tüm ViewModel'lerinizi BindableBase sınıfından türetin. Yukarıdaki kod derlendiğinde ve çalıştırıldığında, aşağıdaki çıktıyı göreceksiniz.
Gördüğünüz gibi MainWindow'umuza sadece iki düğme ve bir CurrentViewModel ekledik. Şimdi herhangi bir düğmeyi tıklarsanız, o belirli Görünüme gidecektir. Müşteriler düğmesine tıklayalım ve CustomerListView'ün görüntülendiğini göreceksiniz.
Daha iyi anlamak için yukarıdaki örneği adım adım uygulamanızı öneririz.
Birim testinin arkasındaki fikir, ayrı kod parçalarını (birimler) almak ve kodu beklenen şekilde kullanan test yöntemleri yazmak ve ardından beklenen sonuçları alıp almadıklarını görmek için test etmektir.
Kendileri kod olan birim testleri, tıpkı projenin geri kalanı gibi derlenir.
Ayrıca, her testte hızlanabilen, testin sırasıyla başarılı veya başarısız olduğunu belirtmek için olumlu veya olumsuz bir şekilde olumlu bir şekilde olumlu veya olumsuz bir etki yapabilen test çalıştıran yazılım tarafından yürütülürler.
Daha önce oluşturulmuş bir örneğe bakalım. Öğrenci Modeli'nin uygulanması aşağıdadır.
using System.ComponentModel;
namespace MVVMDemo.Model {
public class StudentModel {}
public class Student : INotifyPropertyChanged {
private string firstName;
private string lastName;
public string FirstName {
get { return firstName; }
set {
if (firstName != value) {
firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
public string LastName {
get { return lastName; }
set {
if (lastName != value) {
lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
}
public string FullName {
get {
return firstName + " " + lastName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
Aşağıdaki StudentView uygulamasıdır.
<UserControl x:Class="MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:data = "clr-namespace:MVVMDemo.Model"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Orientation = "Horizontal">
<ListBox ItemsSource = "{Binding Students}"
SelectedItem = "{Binding SelectedStudent}"/>
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75" />
</StackPanel>
</Grid>
</UserControl>
StudentViewModel uygulaması aşağıdadır.
using MVVMDemo.Model;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
public int GetStudentCount() {
return Students.Count;
}
}
}
MainWindow.xaml dosyası aşağıdadır.
<Window x:Class = "MVVMDemo.MainWindow"
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"
xmlns:local = "clr-namespace:MVVMDemo"
xmlns:views = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Grid>
<views:StudentView x:Name = "StudentViewControl"/>
</Grid>
</Window>
Aşağıda, ICommand arayüzünü uygulayan MyICommand uygulaması yer almaktadır.
using System;
using System.Windows.Input;
namespace MVVMDemo {
public class MyICommand : ICommand {
Action _TargetExecuteMethod;
Func<bool> _TargetCanExecuteMethod;
public MyICommand(Action executeMethod) {
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action executeMethod, Func<bool> canExecuteMethod) {
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged() {
CanExecuteChanged(this, EventArgs.Empty);
}
bool ICommand.CanExecute(object parameter) {
if (_TargetCanExecuteMethod != null) {
return _TargetCanExecuteMethod();
}
if (_TargetExecuteMethod != null) {
return true;
}
return false;
}
// Beware - should use weak references if command instance lifetime
is longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter) {
if (_TargetExecuteMethod != null) {
_TargetExecuteMethod();
}
}
}
}
Yukarıdaki kod derlendiğinde ve çalıştırıldığında, ana pencerenizde aşağıdaki çıktıyı göreceksiniz.
Yukarıdaki örnek için bir birim testi yazmak için Çözüme yeni bir Test Projesi ekleyelim.
Referanslar'a sağ tıklayarak projeye referans ekleyin.
Mevcut projeyi seçin ve Tamam'a tıklayın.
Şimdi, aşağıdaki kodda gösterildiği gibi Öğrenci Sayısını kontrol edecek basit bir Test ekleyelim.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVVMDemo.ViewModel;
namespace MVVMTest {
[TestClass]
public class UnitTest1 {
[TestMethod]
public void TestMethod1() {
StudentViewModel sViewModel = new StudentViewModel();
int count = sViewModel.GetStudentCount();
Assert.IsTrue(count == 3);
}
}
}
Bu testi gerçekleştirmek için Test → Çalıştır → Tüm Testler menü seçeneğini seçin.
Test Gezgini'nde Testin Geçtiğini görebilirsiniz, çünkü StudentViewModel'e üç öğrenci eklenir. Aşağıdaki kodda gösterildiği gibi sayım koşulunu 3'ten 4'e değiştirin.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVVMDemo.ViewModel;
namespace MVVMTest {
[TestClass]
public class UnitTest1 {
[TestMethod] public void TestMethod1() {
StudentViewModel sViewModel = new StudentViewModel();
int count = sViewModel.GetStudentCount();
Assert.IsTrue(count == 4);
}
}
}
Test planı tekrar yürütüldüğünde, öğrenci sayısı 4'e eşit olmadığı için testin başarısız olduğunu göreceksiniz.
Daha iyi anlamak için yukarıdaki örneği adım adım uygulamanızı öneririz.
Bu bölümde, mevcut MVVM araç setlerini veya çerçevelerini tartışacağız. Bu çerçeveleri, MVVM modelini kendiniz uygulamak için bir grup tekrarlayan kod yazmanıza gerek kalmaması için de kullanabilirsiniz. İşte en popüler çerçevelerden bazıları -
- Prism
- MVVM Işık
- Caliburn Micro
Prizma
Prism, zengin, esnek ve bakımı kolay Windows Presentation Foundation (WPF) masaüstü uygulamalarını kolayca tasarlamanıza ve oluşturmanıza yardımcı olacak örnekler ve belgeler biçiminde rehberlik sağlar. Microsoft Silverlight tarayıcı eklentisi ve Windows uygulamaları ile oluşturulmuş Zengin İnternet Uygulamaları (RIA'lar).
Prism, kaygıların ayrılması ve gevşek bağlantı gibi önemli mimari tasarım ilkelerini içeren tasarım modellerini kullanır.
Prism, bağımsız olarak gelişebilen ancak genel uygulamaya kolayca ve sorunsuz bir şekilde entegre edilebilen gevşek bağlı bileşenler kullanarak uygulamalar tasarlamanıza ve oluşturmanıza yardımcı olur.
Bu tür uygulamalar, bileşik uygulamalar olarak bilinir.
Prism, kullanıma hazır bir dizi özelliğe sahiptir. Prism'in bazı önemli özellikleri aşağıdadır.
MVVM Kalıbı
Prism, MVVM modelini destekler. Önceki bölümlerde uygulanana benzer bir Bindablebase sınıfına sahiptir.
Esnek bir ViewModelLocator'a sahiptir, ancak bu kuralları geçersiz kılmanıza ve Görünümlerinizi ve ViewModel'lerinizi gevşek bir şekilde bağlı bir şekilde bildirimsel olarak bağlamanıza izin verir.
Modülerlik
Kodunuzu parçalar halinde tamamen gevşek bir şekilde bağlanmış sınıf kitaplıklarına ayırma ve bunları çalışma zamanında son kullanıcı için uyumlu bir bütün halinde bir araya getirme yeteneğidir, bu arada kod tamamen ayrılmış halde kalır.
UI Bileşimi / Bölgeler
Kullanıcı arabirimi kapsayıcısının kendisine açık bir referansa sahip olması gereken, ekleme işlemini yapan View olmadan görünümleri kapsayıcılara ekleme yeteneğidir.
Navigasyon
Prism, ileri ve geri gezinme ve görünüm modellerinizin gezinme sürecine doğrudan katılmasına olanak tanıyan gezinme yığını gibi bölgelerin üstüne katman oluşturan gezinme özelliklerine sahiptir.
Komutlar
Prism'in komutları vardır, bu yüzden onların önceki bölümlerde kullandığımız MyICommand'a çok benzeyen bir delege komutu vardır, ancak sizi bellek sızıntılarından koruyacak ekstra sağlamlığı vardır.
Pub / Sub Etkinlikleri
Prism ayrıca Pub / Sub etkinlikleri için desteğe sahiptir. Bunlar, yayıncı ve abonenin farklı yaşam sürelerine sahip olabildiği ve olaylar aracılığıyla iletişim kurmak için birbirlerine açık referanslara sahip olmak zorunda olmadıkları gevşek bağlı olaylardır.
MVVM Işık
MVVM Light, Laurent Bugnion tarafından üretilir ve Görünümünüzü Modelinizden ayırmanıza yardımcı olur, bu da daha temiz ve bakımı ve genişletmesi daha kolay uygulamalar oluşturur.
Ayrıca test edilebilir uygulamalar oluşturur ve çok daha ince bir kullanıcı arabirimi katmanına (otomatik olarak test edilmesi daha zordur) sahip olmanızı sağlar.
Bu araç seti, Blend kullanıcılarının veri kontrolleriyle çalışırken "bir şeyler görmelerini" sağlamak için tasarım zamanı verilerinin oluşturulması da dahil olmak üzere kullanıcı arayüzünü Blend olarak açıp düzenlemeye özel önem vermektedir.
Caliburn Micro
Bu, MVVM modelini uygulamanıza yardımcı olan ve aynı zamanda kullanıma hazır birkaç şeyi destekleyen başka bir küçük açık kaynaklı çerçevedir.
Caliburn Micro, tüm XAML platformlarında uygulamalar oluşturmak için tasarlanmış küçük ama güçlü bir çerçevedir.
MVVM ve diğer kanıtlanmış UI kalıpları için güçlü destek ile Caliburn Micro, kod kalitesinden veya test edilebilirliğinden ödün vermeden çözümünüzü hızlı bir şekilde oluşturmanıza olanak tanır.