在UWP中實現本身的MVVM設計模式

其實寫這篇博文的時候我是拒絕的,由於這牽扯到一個高大上的東西——"框架"。一提及這個東西,不少朋友就感受有點蒙了,尤爲是編程新手。由於它不像在代碼裏面定義一個變量那麼顯而易見,它是須要在你的整個程序架構上體現出來的,而且對於框架來講,並無什麼固定的代碼格式,你能夠這樣寫,固然也能夠那樣寫。只要最終能夠達到一樣的效果,各個模塊之間可以體現這種框架的思想就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     }
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     }
View Code

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

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>
                    

   
View Code

 

二:爲頁面綁定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     }
View Code

 而後在咱們的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 Code

 並將對應的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>
                    
View Code

 這個地方提醒新手朋友要注意的一個問題,若是你但願你的控件在指定條件下顯示的狀態不同就須要手動觸發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.dllMicrosoft.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>
                    
View Code

這裏面用到了一個非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     }
View Code

而後對應的命令寫法和以前的是同樣的,以下所示:

 

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

寫到這,咱們的MVVM框架已經搭建的差很少了,還算滿意,我運行的效果是這樣的(你的也是這樣的嗎?):

我不知道我用個人這種方式理解和設計應用程序的MVVM框架在諸位眼中是否規範,合法,還請高手不吝賜教呀:)!!!!

四:寫在最後

 若是你可以熟練理解並可以將MVVM運用到本身的項目中,並計劃使用第三方MVVM框架的話,我建議你使用MVVMLight,簡單易用上手快,而且它已經支持UWP的項目模板了。我真的很佩服做者(官網地址)的編碼能力,個人不少思路都是從他的博客中得到靈感的,但願你也是如此!

GitHub  :https://github.com/hippieZhou/MVVM

相關文章
相關標籤/搜索