MVVM - Hierarki & Navigasi

Saat membuat aplikasi MVVM, Anda biasanya menguraikan layar informasi yang kompleks menjadi sekumpulan tampilan induk dan anak, di mana tampilan anak dimuat dalam tampilan induk di panel atau kontrol penampung, dan membentuk hierarki penggunaan itu sendiri.

  • Setelah mendekomposisi Tampilan kompleks, ini tidak berarti bahwa setiap bagian konten anak yang Anda pisahkan ke dalam file XAML-nya sendiri harus berupa tampilan MVVM.

  • Potongan konten hanya menyediakan struktur untuk merender sesuatu ke layar dan tidak mendukung masukan atau manipulasi apa pun oleh pengguna untuk konten tersebut.

  • Ini mungkin tidak memerlukan ViewModel terpisah, tetapi ini bisa saja berupa potongan XAML yang dirender berdasarkan properti yang diekspos oleh ViewModel induk.

  • Terakhir, jika Anda memiliki hierarki Tampilan dan ViewModels, ViewModel induk bisa menjadi hub untuk komunikasi sehingga setiap ViewModel anak dapat tetap dipisahkan dari ViewModels anak lain dan dari induknya sebanyak mungkin.

Mari kita lihat contoh di mana kita akan mendefinisikan hierarki sederhana antara tampilan yang berbeda. Buat proyek Aplikasi WPF baruMVVMHierarchiesDemo

Step 1 - Tambahkan tiga folder (Model, ViewModel, dan Views) ke dalam proyek Anda.

Step 2 - Tambahkan kelas Customer dan Order di folder Model, CustomerListView dan OrderView di folder Views, serta CustomerListViewModel dan OrderViewModel di folder ViewModel seperti yang ditunjukkan pada gambar berikut.

Step 3- Tambahkan textblock di CustomerListView dan OrderView. Berikut adalah file CustomerListView.xaml.

<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>

Berikut ini adalah file OrderView.xaml.

<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>

Sekarang kita membutuhkan sesuatu untuk menjadi tuan rumah tampilan ini, dan tempat yang bagus untuk itu di Jendela Utama kita karena ini adalah aplikasi sederhana. Kami membutuhkan kontrol kontainer sehingga kami dapat menempatkan tampilan kami dan mengubahnya dalam mode navigasi. Untuk tujuan ini, kita perlu menambahkan ContentControl di file MainWindow.xaml kita dan kita akan menggunakan properti kontennya dan mengikatnya ke referensi ViewModel.

Sekarang tentukan template data untuk setiap tampilan dalam kamus sumber daya. Berikut ini adalah file MainWindow.xaml. Perhatikan bagaimana setiap templat data memetakan tipe data (tipe ViewModel) ke Tampilan yang sesuai.

<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>

Setiap kali model tampilan saat ini disetel ke instance CustomerListViewModel, model ini akan menampilkan CustomerListView dengan ViewModel terhubung. Ini adalah order ViewModel, itu akan membuat OrderView dan seterusnya.

Kita sekarang membutuhkan ViewModel yang memiliki properti CurrentViewModel dan beberapa logika dan perintah untuk dapat mengganti referensi ViewModel saat ini di dalam properti.

Mari buat ViewModel untuk MainWindow ini yang disebut MainWindowViewModel. Kita bisa membuat instance ViewModel kita dari XAML dan menggunakannya untuk menyetel properti DataContext dari jendela. Untuk ini, kita perlu membuat kelas dasar untuk merangkum implementasi INotifyPropertyChanged untuk ViewModels kita.

Ide utama di balik kelas ini adalah untuk merangkum implementasi INotifyPropertyChanged dan menyediakan metode pembantu ke kelas turunan sehingga mereka dapat dengan mudah memicu notifikasi yang sesuai. Berikut adalah implementasi kelas BindableBase.

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 { }; 
   } 
}

Sekarang saatnya untuk benar-benar mulai melakukan beberapa pengalihan tampilan menggunakan properti CurrentViewModel kami. Kami hanya perlu beberapa cara untuk mendorong pengaturan properti ini. Dan kami akan membuatnya sehingga pengguna akhir dapat memerintahkan masuk ke daftar pelanggan atau ke tampilan pesanan. Pertama tambahkan kelas baru dalam proyek Anda yang akan mengimplementasikan antarmuka ICommand. Berikut adalah implementasi antarmuka ICommand.

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 
   } 
}

Kita sekarang perlu menyiapkan beberapa navigasi tingkat atas untuk ini ke ViewModels dan logika untuk peralihan itu harus termasuk dalam MainWindowViewModel. Untuk ini kita akan menggunakan metode yang disebut navigasi yang mengambil tujuan string dan mengembalikan properti CurrentViewModel.

private void OnNav(string destination) {
 
   switch (destination) { 
      case "orders": 
         CurrentViewModel = orderViewModelModel; 
      break; 
      case "customers": 
      default: 
         CurrentViewModel = custListViewModel; 
      break; 
   } 
}

Untuk navigasi dari Tampilan berbeda ini, kita perlu menambahkan dua tombol di file MainWindow.xaml kita. Berikut implementasi file XAML selengkapnya.

<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>

Berikut ini adalah implementasi MainWindowViewModel lengkap.

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; 
         } 
      } 
   } 
}

Dapatkan semua ViewModels Anda dari kelas BindableBase. Ketika kode di atas dikompilasi dan dijalankan, Anda akan melihat output berikut.

Seperti yang Anda lihat, kami hanya menambahkan dua tombol dan CurrentViewModel di Jendela Utama kami. Jika Anda mengklik tombol apa pun maka itu akan menavigasi ke Tampilan tertentu itu. Mari klik tombol Pelanggan dan Anda akan melihat bahwa CustomerListView ditampilkan.

Kami menyarankan Anda untuk menjalankan contoh di atas dengan cara langkah demi langkah untuk pemahaman yang lebih baik.