其實寫這篇博文的時候我是拒絕的,因為這牽扯到一個高大上的東西——"架構"。一說起這個東西,很多朋友就感覺有點蒙了,尤其是程式設計新手。因為它不像在代碼裡面定義一個變量那麼顯而易見,它是需要在你的整個程式架構上展現出來的,并且對于架構來說,并沒有什麼固定的代碼格式,你可以這樣寫,當然也可以那樣寫。隻要最終可以達到同樣的效果,各個子產品之間能夠展現這種架構的思想就OK。是以當你都是用MVVM框得到兩份架寫的相同需求的Demo看時,發現裡面的很多代碼都不一樣,請不要驚訝,因為你正在接觸一個很抽象的東西,這種東西有的時候還真得你需要自己挖空心思去琢磨一下,光靠别人給你講還是不行的!
--------------------------------切入正題--------------------------------
在進行搭建自己的MVVM架構的時候你需要提起掌握一下知識(至少要熟悉,如果未達标,建議先自行腦補一下,我可能不會做到面面俱到):
1、熟練掌握資料綁定;
2、熟練使用委托;
3、對MVVM架構有一定的了解;
--------------------------------在你決定繼續要往下看的時候我會預設你已經對上述知識有所了解------------------------------
一:為頁面綁定資料
按照規範的MVVM架構來說,一個項目中至少要有要有三個檔案夾:View、ViewModel、Model;這三個檔案夾分别對應該架構的三個組成部分,這一點沒什麼好說的。針對Model中的一些屬性而言,如果想具有屬性通知的功能的話就需要繼承INotifyPropertyChanged接口,并需要自定義一個函數用于觸發對應的PropertyChanged事件,一般情況下我們都會把這一部分封裝到一個類中,供其它類來繼承它。這樣就避免出現代碼備援的問題。示例代碼如下所示:

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 }
View Code
接下來,我們就需要讓對應的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的項目模闆了。我真的很佩服作者(
官網位址)的編碼能力,我的很多思路都是從他的部落格中獲得靈感的,希望你也是如此!
GitHub :
https://github.com/hippieZhou/MVVM