其實寫這篇博文的時候我是拒絕的,由於這牽扯到一個高大上的東西——"框架"。一提及這個東西,不少朋友就感受有點蒙了,尤爲是編程新手。由於它不像在代碼裏面定義一個變量那麼顯而易見,它是須要在你的整個程序架構上體現出來的,而且對於框架來講,並無什麼固定的代碼格式,你能夠這樣寫,固然也能夠那樣寫。只要最終能夠達到一樣的效果,各個模塊之間可以體現這種框架的思想就OK。因此當你都是用MVVM框獲得兩份架寫的相同需求的Demo看時,發現裏面的不少代碼都不同,請不要驚訝,由於你正在接觸一個很抽象的東西,這種東西有的時候還真得你須要本身挖空心思去琢磨一下,光靠別人給你講仍是不行的! git
--------------------------------切入正題-------------------------------- github
在進行搭建本身的MVVM框架的時候你須要提起掌握一下知識(至少要熟悉,若是未達標,建議先自行腦補一下,我可能不會作到面面俱到): express
一、熟練掌握數據綁定; 編程
二、熟練使用委託; 架構
三、對MVVM框架有必定的瞭解; 框架
--------------------------------在你決定繼續要往下看的時候我會默認你已經對上述知識有所瞭解------------------------------ async
一:爲頁面綁定數據mvvm
按照規範的MVVM框架來講,一個項目中至少要有要有三個文件夾:View、ViewModel、Model;這三個文件夾分別對應該框架的三個組成部分,這一點沒什麼好說的。針對Model中的一些屬性而言,若是想具備屬性通知的功能的話就須要繼承INotifyPropertyChanged接口,並須要自定義一個函數用於觸發對應的PropertyChanged事件,通常狀況下咱們都會把這一部分封裝到一個類中,供其它類來繼承它。這樣就避免出現代碼冗餘的問題。示例代碼以下所示: ide
1 public class ObservableObject : INotifyPropertyChanged 2 { 3 public event PropertyChangedEventHandler PropertyChanged; 4 public void RaisePropertyChanged(string propertyName) 5 { 6 if (PropertyChanged != null) 7 { 8 PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 9 } 10 } 11 }
接下來,咱們就須要讓對應的Model類來繼承咱們寫的這個具備屬性通知的類,示例代碼以下所示: 函數
1 public class User:ObservableObject 2 { 3 private string _name; 4 public string Name 5 { 6 get { return _name; } 7 set 8 { 9 _name = value; 10 RaisePropertyChanged("Name"); 11 } 12 } 13 14 private int _age; 15 16 public int Age 17 { 18 get { return _age; } 19 set 20 { 21 _age = value; 22 RaisePropertyChanged("Age"); 23 } 24 } 25 26 public User(string name,int age) 27 { 28 this.Name = name; 29 this.Age = age; 30 } 31 32 /// <summary> 33 /// 給ViewModel提供一個獲取Model中集合對象的接口 34 /// </summary> 35 /// <returns></returns> 36 public static ObservableCollection<User> GetUsers() 37 { 38 return new ObservableCollection<User>() 39 { 40 new User("hippieZhou",23), 41 new User("小明",12), 42 new User("老王",50) 43 }; 44 } 45 }
Model已經搭建完成,接着咱們就開始搭建ViewModel,示例代碼以下所示:
1 public class MainPageViewModel : ObservableObject 2 { 3 private ObservableCollection<User> _users; 4 public ObservableCollection<User> Users 5 { 6 get { return _users; } 7 set 8 { 9 _users = value; 10 RaisePropertyChanged("Users"); 11 } 12 } 13 14 public MainPageViewModel() 15 { 16 this.Users = User.GetUsers(); 17 } 18 }
OK,是否是很簡單,接下來就是View中的數據綁定,示例代碼以下所示:
1 <Page.DataContext> 2 <vm:MainPageViewModel/> 3 </Page.DataContext> 4 5 <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 6 <ListView x:Name="lv" Grid.Row="1" ItemsSource="{Binding Users}"> 7 <ListView.ItemTemplate> 8 <DataTemplate> 9 <ListViewItem> 10 <Grid> 11 <Grid.ColumnDefinitions> 12 <ColumnDefinition Width="200"/> 13 <ColumnDefinition Width="*"/> 14 </Grid.ColumnDefinitions> 15 <TextBlock Text="{Binding Name}" Grid.Column="0"/> 16 <TextBlock Text="{Binding Age}" Grid.Column="1"/> 17 </Grid> 18 </ListViewItem> 19 </DataTemplate> 20 </ListView.ItemTemplate> 21 </ListView> 22 </Grid>
二:爲頁面綁定Command
寫到這算是完成了1/3,界面是展現了數據,可是咱們不能對它進行任何的操做,所以咱們還須要讓數據可以動態的增長和刪除,接下來咱們須要使用有關Command的相關知識了,首先,Command屬於ButtonBase的一個字段,若是咱們想爲對應的Command進行綁定的話,那就須要這個綁定源對應的委託繼承自ICommand接口,須要重寫ICommand對應的兩個接口函數。其次,因爲ICommand提供了兩個接口函數CanExecute和Execute,所以當CanExecute爲false時候Execute是不被執行,此時綁定的Command是失效的,那麼對應的控件應該自動處於禁用狀態的,可是在WindowsStore類型的應用再也不像WPF那樣具備CommandManager的功能,不能自動觸發CanExecuteChanged,這樣就致使控件的顏色仍然不是禁用狀態的顏色(儘管沒有執行對應的函數),所以咱們須要手動觸發這個事件,來保證前臺的控件的顯示狀態在指定的條件下發生改變。
在此,咱們通常會採起封裝的思想來處理這種狀況,所以我選擇封裝一個類DelegateCommand,繼承至ICommand,示例代碼以下所示:
1 public sealed class DelegateCommand : ICommand 2 { 3 public event EventHandler CanExecuteChanged; 4 /// <summary> 5 /// 須要手動觸發屬性改變事件 6 /// </summary> 7 public void RaiseCanExecuteChanged() 8 { 9 if (CanExecuteChanged != null) 10 { 11 CanExecuteChanged(this, EventArgs.Empty); 12 } 13 } 14 15 /// <summary> 16 /// 決定當前綁定的Command可否被執行 17 /// true:能夠被執行 18 /// false:不能被執行 19 /// </summary> 20 /// <param name="parameter">不是必須的,能夠依據狀況來決定,或者重寫一個對應的無參函數</param> 21 /// <returns></returns> 22 public bool CanExecute(object parameter) 23 { 24 return this.MyCanExecute == null ? true : this.MyCanExecute(parameter); 25 } 26 27 /// <summary> 28 /// 用於執行對應的命令,只有在CanExecute能夠返回true的狀況下才能夠被執行 29 /// </summary> 30 /// <param name="parameter"></param> 31 public void Execute(object parameter) 32 { 33 try 34 { 35 this.MyExecute(parameter); 36 } 37 catch (Exception ex) 38 { 39 #if DEBUG 40 41 Debug.WriteLine(ex.Message); 42 43 #endif 44 } 45 } 46 47 /// <summary> 48 /// 49 /// </summary> 50 public Action<Object> MyExecute { get; set; } 51 public Func<Object, bool> MyCanExecute { get; set; } 52 53 /// <summary> 54 /// 構造函數,用於初始化 55 /// </summary> 56 /// <param name="execute"></param> 57 /// <param name="canExecute"></param> 58 public DelegateCommand(Action<Object> execute, Func<Object, bool> canExecute) 59 { 60 this.MyExecute = execute; 61 this.MyCanExecute = canExecute; 62 } 63 }
而後在咱們的ViewModel中建立對應的Command就能夠了,咱們能夠將ViewModel改形成下面這個樣子:
1 public class MainPageViewModel : ObservableObject 2 { 3 private ObservableCollection<User> _users; 4 public ObservableCollection<User> Users 5 { 6 get { return _users; } 7 set 8 { 9 _users = value; 10 RaisePropertyChanged("Users"); 11 } 12 } 13 14 private DelegateCommand _addCommand; 15 16 /// <summary> 17 /// 噹噹前集合項的個數小於5時容許用戶繼續添加,不然就不容許用戶添加 18 /// </summary> 19 public DelegateCommand AddCommand 20 { 21 get 22 { 23 return _addCommand ?? (_addCommand = new DelegateCommand 24 ((Object obj) => 25 { 26 //添加一條記錄 27 this.Users.Add(new User(DateTime.Now.ToString(),DateTime.Now.Hour)); 28 //手動觸發CanExecuteChanged事件來改變對應控件的顯示狀態 29 this._addCommand.RaiseCanExecuteChanged(); 30 this._delCommand.RaiseCanExecuteChanged(); 31 }, 32 (Object obj) => this.Users.Count < 5)); 33 } 34 } 35 36 /// <summary> 37 /// 噹噹前集合項的個數大於1時容許用戶繼續刪除,不然就不容許用戶刪除 38 /// </summary> 39 private DelegateCommand _delCommand; 40 public DelegateCommand DelCommand 41 { 42 get 43 { 44 return _delCommand ?? (_delCommand = 45 new DelegateCommand((Object obj) => 46 { 47 //刪除一條記錄 48 this.Users.RemoveAt(0); 49 //手動觸發CanExecuteChanged事件來改變對應控件的顯示狀態 50 this._addCommand.RaiseCanExecuteChanged(); 51 this._delCommand.RaiseCanExecuteChanged(); 52 }, 53 (Object obj) => this.Users.Count > 1)); 54 } 55 } 56 57 public MainPageViewModel() 58 { 59 this.Users = User.GetUsers(); 60 } 61 }
並將對應的View改形成下面這個樣子:
1 <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 2 <Grid.RowDefinitions> 3 <RowDefinition Height="Auto"/> 4 <RowDefinition Height="*"/> 5 </Grid.RowDefinitions> 6 <StackPanel Grid.Row="0" HorizontalAlignment="Center" Width="100"> 7 <Button Content="Add" Command="{Binding AddCommand}" HorizontalAlignment="Stretch" Margin="6"/> 8 <Button Content="Del" Command="{Binding DelCommand}" HorizontalAlignment="Stretch" Margin="6"/> 9 </StackPanel> 10 <ListView x:Name="lv" Grid.Row="1" ItemsSource="{Binding Users}"> 11 <ListView.ItemTemplate> 12 <DataTemplate> 13 <ListViewItem> 14 <Grid> 15 <Grid.ColumnDefinitions> 16 <ColumnDefinition Width="200"/> 17 <ColumnDefinition Width="*"/> 18 </Grid.ColumnDefinitions> 19 <TextBlock Text="{Binding Name}" Grid.Column="0"/> 20 <TextBlock Text="{Binding Age}" Grid.Column="1"/> 21 </Grid> 22 </ListViewItem> 23 </DataTemplate> 24 </ListView.ItemTemplate> 25 </ListView> 26 </Grid>
這個地方提醒新手朋友要注意的一個問題,若是你但願你的控件在指定條件下顯示的狀態不同就須要手動觸發CanExecuteChanged事件。
推薦連接:Implement ICommand.CanExecuteChanged in portable class library (PCL)
Re-enabling the CommandManager feature with RelayCommand in MVVM Light V5
三:Event To Command
接下來算是一個重點內容吧,如何將一個事件綁定到Command上? 這個問題很現實,並非全部的控件都有Command屬性,當一個控件只有Event而沒有Command咱們該怎麼辦?
咱們如今需求是選中一項後彈出一個對話框,顯示你選中項的相關信息(經過EventToCommand來實現)
這個微軟爲咱們提供了一個解決方案,若是你安裝了Blend工具,你能夠把 目錄C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1\ExtensionSDKs\BehaviorsXamlSDKManaged\12.0打開,你會發現有兩個動態庫頗有用:Microsoft.Xaml.Interactions.dll和Microsoft.Xaml.Interactivity.dll;沒錯,就是它倆能夠達成你的心願,迅速將這兩個動態庫加入到工程中,而後你能夠在你的XAML頁面中進行綁定的,我把完整的代碼羅列出來供你們參考:
1 <Page 2 x:Class="MVVM.MainPage" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:local="using:MVVM" 6 xmlns:vm="using:MVVM.ViewModel" 7 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 8 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 9 mc:Ignorable="d" 10 11 xmlns:Interactivity="using:Microsoft.Xaml.Interactivity" 12 xmlns:Core="using:Microsoft.Xaml.Interactions.Core"> 13 <!-- 14 C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1\ExtensionSDKs\BehaviorsXamlSDKManaged\12.0 15 --> 16 <Page.DataContext> 17 <vm:MainPageViewModel/> 18 </Page.DataContext> 19 20 <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 21 <Grid.RowDefinitions> 22 <RowDefinition Height="Auto"/> 23 <RowDefinition Height="*"/> 24 </Grid.RowDefinitions> 25 <StackPanel Grid.Row="0" HorizontalAlignment="Center" Width="100"> 26 <Button Content="Add" Command="{Binding AddCommand}" HorizontalAlignment="Stretch" Margin="6"/> 27 <Button Content="Del" Command="{Binding DelCommand}" HorizontalAlignment="Stretch" Margin="6"/> 28 </StackPanel> 29 <ListView x:Name="lv" Grid.Row="1" ItemsSource="{Binding Users}"> 30 <ListView.ItemTemplate> 31 <DataTemplate> 32 <ListViewItem> 33 <Grid> 34 <Grid.ColumnDefinitions> 35 <ColumnDefinition Width="200"/> 36 <ColumnDefinition Width="*"/> 37 </Grid.ColumnDefinitions> 38 <TextBlock Text="{Binding Name}" Grid.Column="0"/> 39 <TextBlock Text="{Binding Age}" Grid.Column="1"/> 40 </Grid> 41 </ListViewItem> 42 </DataTemplate> 43 </ListView.ItemTemplate> 44 <Interactivity:Interaction.Behaviors> 45 <Core:EventTriggerBehavior EventName="SelectionChanged"> 46 <Core:InvokeCommandAction Command="{Binding ShowDialog}" CommandParameter="{Binding ElementName=lv,Path=SelectedItem,Converter={StaticResource converter}}"/> 47 </Core:EventTriggerBehavior> 48 </Interactivity:Interaction.Behaviors> 49 </ListView> 50 </Grid> 51 </Page>
這裏面用到了一個非MVVM的知識:值轉換器(只是爲了彈出框可以顯示我想要的數據而已,沒什麼其餘的做用),示例代碼以下所示:
1 /// <summary> 2 /// 定義一個值轉換器,用於將綁定的數據格式化爲指定的格式 3 /// </summary> 4 public class ItemConverter : IValueConverter 5 { 6 public object Convert(object value, Type targetType, object parameter, string language) 7 { 8 User user = value as User; 9 if (user != null) 10 { 11 return user.Name; 12 } 13 else 14 { 15 return "you have not select!"; 16 } 17 } 18 19 public object ConvertBack(object value, Type targetType, object parameter, string language) 20 { 21 throw new NotImplementedException(); 22 } 23 }
而後對應的命令寫法和以前的是同樣的,以下所示:
1 private DelegateCommand _showDialog; 2 public DelegateCommand ShowDialog 3 { 4 get 5 { 6 return _showDialog ?? (_showDialog= new DelegateCommand( 7 async (Object obj) => 8 { 9 await new Windows.UI.Popups.MessageDialog(obj.ToString()).ShowAsync(); 10 }, 11 (Object obj) => true)); 12 } 13 }
寫到這,咱們的MVVM框架已經搭建的差很少了,還算滿意,我運行的效果是這樣的(你的也是這樣的嗎?):
我不知道我用個人這種方式理解和設計應用程序的MVVM框架在諸位眼中是否規範,合法,還請高手不吝賜教呀:)!!!!
四:寫在最後
若是你可以熟練理解並可以將MVVM運用到本身的項目中,並計劃使用第三方MVVM框架的話,我建議你使用MVVMLight,簡單易用上手快,而且它已經支持UWP的項目模板了。我真的很佩服做者(官網地址)的編碼能力,個人不少思路都是從他的博客中得到靈感的,但願你也是如此!