有無MVVM的專案開發比較
在這篇筆記中,將會透過三個專案,分別說明:
-
當沒有採用 MVVM 設計方法來進行專案開發的時候,如何透過程式碼後置 (Code Behind) 的方式,把相關商業邏輯串接起來。
-
當採用 MVVM 設計方法來進行專案開發的時候,如何自行定義與架構出您的 ViewModel,並且,在 View 中如何設定目標物件的綁定模式。
-
在這裡,將會說明如何使用 微軟 Microsoft Prism 這個套件提供的 MVVM 功能,快速方便的開發出 MVVM 專案。
這個專案的視覺設計,首先會
-
放置一個
Label
控制項,這個控制項會取得下一個 Entry
使用者輸入的值。
-
接著放置一個
Entry
控制項,讓使用者輸入任何文字,並且會動態即時更新道上一個 Label
控制項內
-
放置一個
Entry
控制項,讓使用者輸入任何文字,並且會更新上一個 Entry
控制項內的值,當然,就會連動更新第一個 Label
控制項的顯示內容。
-
放置一個
Button
按鈕,當按下這個按鈕,會在下一個 Label
內顯示內容
-
放置一個
Label
控制項,用來顯示當按鈕按下之後的內容。
下圖說明了,當使用者在 Entry
控制項輸入任何內容的時候,會在第一個 Label
控制項內顯示出來。
接著,當使用按下按鈕,也會顯示一段訊息。
下圖表示當在第二個 Entry
控制項內輸入了任何內容,前面兩個 Label
& Entry
控制項,都會同步更新同樣的內容。
當沒有採用 MVVM 設計方法來進行專案開發的時候,如何透過程式碼後置 (Code Behind) 的方式,把相關商業邏輯串接起來。
當採用 MVVM 設計方法來進行專案開發的時候,如何自行定義與架構出您的 ViewModel,並且,在 View 中如何設定目標物件的綁定模式。
在這裡,將會說明如何使用 微軟 Microsoft Prism 這個套件提供的 MVVM 功能,快速方便的開發出 MVVM 專案。
放置一個
Label
控制項,這個控制項會取得下一個 Entry
使用者輸入的值。
接著放置一個
Entry
控制項,讓使用者輸入任何文字,並且會動態即時更新道上一個 Label
控制項內
放置一個
Entry
控制項,讓使用者輸入任何文字,並且會更新上一個 Entry
控制項內的值,當然,就會連動更新第一個 Label
控制項的顯示內容。
放置一個
Button
按鈕,當按下這個按鈕,會在下一個 Label
內顯示內容
放置一個
Label
控制項,用來顯示當按鈕按下之後的內容。Entry
控制項輸入任何內容的時候,會在第一個 Label
控制項內顯示出來。Entry
控制項內輸入了任何內容,前面兩個 Label
& Entry
控制項,都會同步更新同樣的內容。沒有使用 MVVM
底下說明的內容,已經實作出專案,並且放是在底下 GitHub 內。
使用 Visual Studio 2015,開啟 MVVM1.sln
方案
開啟查看核心PCL MVVM1
專案內的 MainPage.xaml
,其 XAML 標記宣告內容如下:
這裡依照前面的視覺定義,使用 StackLayout
版面配置物件項目將這些控制項垂直排列出來。
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MVVM1"
x:Class="MVVM1.MainPage">
<StackLayout
HorizontalOptions="Fill" VerticalOptions="Center">
<Label x:Name="lbl輸入文字" />
<Entry x:Name="eny輸入文字1" />
<Entry x:Name="eny輸入文字2" />
<Button x:Name="btn按鈕" Text="請按下我" />
<Label x:Name="lbl按鈕文字" />
</StackLayout>
</ContentPage>
開啟查看核心PCL MVVM1
專案內的 MainPage.xaml.cs
,其code behind 的C#程式碼如下:
在 code beind 內,
-
定義了
Entry
的 TextChanged
的事件,當這個事件發生的時候,要進行其他資料的更新。
-
定義了
Button
的 Clicked
事件,當使用者按下這個按鈕之後,要更新相關控制項的內容。
這樣的定義與處理,好像非常普通,可是,由於相關商業邏輯都寫在 Code Behind 內,會帶來許多不良問題,因此,才會需要接下來的 MVVM 設計方法來改善這些問題。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace MVVM1
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
eny輸入文字1.TextChanged += (s, e) =>
{
lbl輸入文字.Text = eny輸入文字1.Text;
};
eny輸入文字2.TextChanged += (s, e) =>
{
eny輸入文字1.Text = eny輸入文字2.Text;
};
btn按鈕.Clicked += (s, e) =>
{
lbl按鈕文字.Text = $"您已經按下按鈕";
};
}
}
}
MVVM1.sln
方案MVVM1
專案內的 MainPage.xaml
,其 XAML 標記宣告內容如下:StackLayout
版面配置物件項目將這些控制項垂直排列出來。<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MVVM1"
x:Class="MVVM1.MainPage">
<StackLayout
HorizontalOptions="Fill" VerticalOptions="Center">
<Label x:Name="lbl輸入文字" />
<Entry x:Name="eny輸入文字1" />
<Entry x:Name="eny輸入文字2" />
<Button x:Name="btn按鈕" Text="請按下我" />
<Label x:Name="lbl按鈕文字" />
</StackLayout>
</ContentPage>
MVVM1
專案內的 MainPage.xaml.cs
,其code behind 的C#程式碼如下:
定義了
Entry
的 TextChanged
的事件,當這個事件發生的時候,要進行其他資料的更新。
定義了
Button
的 Clicked
事件,當使用者按下這個按鈕之後,要更新相關控制項的內容。using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace MVVM1
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
eny輸入文字1.TextChanged += (s, e) =>
{
lbl輸入文字.Text = eny輸入文字1.Text;
};
eny輸入文字2.TextChanged += (s, e) =>
{
eny輸入文字1.Text = eny輸入文字2.Text;
};
btn按鈕.Clicked += (s, e) =>
{
lbl按鈕文字.Text = $"您已經按下按鈕";
};
}
}
}
使用 INotifyPropertyChanged 的 MVVM
底下說明的內容,已經實作出專案,並且放是在底下 GitHub 內。
使用 Visual Studio 2015,開啟 MVVM1.sln
方案
開啟查看核心PCL MVVM2
專案內的 MainPage.xaml
,其 XAML 標記宣告內容如下:
-
在
ContentPage
的屬性項目 (Property Element)中(ContentPage.BindingContext
) ,定義了這個整個頁面與所有控制項的 BindingContext
可以資料綁定的來源,都是來自於 MainPageViewModel 物件內。
-
在需要的控制項內,使用 XAML 標記延伸 (Markup Extension) 功能,也就是大括號,標示這個屬性值,是要透過資料細節的方式,從 BindingContext 內的 ViewModel 內取得。
-
在這裡,其實已經不再需要
x:Name
這個標記延伸功能,因為,在 ViewModel 內,是無法讀取到任何 View 內的 x:Name
內容(這個標記延伸僅是提供 code behind 程式碼可以抓取到 XAML 內的這個控制項)
-
在按鈕的宣告中,其相關事件將會使用
Command
這個屬性來取代,透過資料細節到 ViewModel 內的特定的 ICommand 屬性;也就是,當使用者按下這個按鈕之後,就會執行綁定的 ICommand 方法。
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MVVM2"
x:Class="MVVM2.MainPage">
<ContentPage.BindingContext>
<local:MainPageViewModel />
</ContentPage.BindingContext>
<StackLayout
HorizontalOptions="Fill" VerticalOptions="Center">
<Label x:Name="lbl輸入文字"
Text="{Binding EntryText1}"/>
<!--這裡若改成 OneWay 會有甚麼效果呢?-->
<Entry x:Name="eny輸入文字1"
Text="{Binding EntryText1, Mode=TwoWay}"/>
<Entry x:Name="eny輸入文字2"
Text="{Binding EntryText2, Mode=TwoWay}"/>
<Button x:Name="btn按鈕" Text="請按下我"
Command="{Binding PushMeCommand}"/>
<Label x:Name="lbl按鈕文字"
Text="{Binding LabelText1}"/>
</StackLayout>
</ContentPage>
由於使用了 MVVM 設計方法,所以,也就不會有任何 code behind 在 MainPage.xaml.cs
內。
開啟查看核心PCL MVVM2
專案內的 MainPageViewModel
,其 ViewModel 的C#程式碼如下:
-
在這個 ViewModel 內,定義了四個屬性 (Property)
-
這個 ViewModel 需要實作
INotifyPropertyChanged
葉面,並且也要實作 OnPropertyChanged
方法
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
namespace MVVM2
{
public class MainPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
#region EntryText1
private string _EntryText1;
public string EntryText1
{
get
{
return _EntryText1;
}
set
{
if (_EntryText1 != value)
{
_EntryText1 = value;
OnPropertyChanged("EntryText1");
}
}
}
#endregion
#region EntryText2
private string _EntryText2;
public string EntryText2
{
get
{
return _EntryText2;
}
set
{
if (_EntryText2 != value)
{
EntryText1 = value;
_EntryText2 = value;
OnPropertyChanged("EntryText2");
}
}
}
#endregion
#region LabelText1
private string _LabelText1;
public string LabelText1
{
get
{
return _LabelText1;
}
set
{
if (_LabelText1 != value)
{
_LabelText1 = value;
OnPropertyChanged("LabelText1");
}
}
}
#endregion
#region Button ICommand
public ICommand PushMeCommand { protected set; get; }
#endregion
public MainPageViewModel()
{
PushMeCommand = new Command(() =>
{
this.LabelText1 = $"您已經按下按鈕";
});
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
}
MVVM1.sln
方案MVVM2
專案內的 MainPage.xaml
,其 XAML 標記宣告內容如下:
在
ContentPage
的屬性項目 (Property Element)中(ContentPage.BindingContext
) ,定義了這個整個頁面與所有控制項的 BindingContext
可以資料綁定的來源,都是來自於 MainPageViewModel 物件內。
在需要的控制項內,使用 XAML 標記延伸 (Markup Extension) 功能,也就是大括號,標示這個屬性值,是要透過資料細節的方式,從 BindingContext 內的 ViewModel 內取得。
在這裡,其實已經不再需要
x:Name
這個標記延伸功能,因為,在 ViewModel 內,是無法讀取到任何 View 內的 x:Name
內容(這個標記延伸僅是提供 code behind 程式碼可以抓取到 XAML 內的這個控制項)
在按鈕的宣告中,其相關事件將會使用
Command
這個屬性來取代,透過資料細節到 ViewModel 內的特定的 ICommand 屬性;也就是,當使用者按下這個按鈕之後,就會執行綁定的 ICommand 方法。<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MVVM2"
x:Class="MVVM2.MainPage">
<ContentPage.BindingContext>
<local:MainPageViewModel />
</ContentPage.BindingContext>
<StackLayout
HorizontalOptions="Fill" VerticalOptions="Center">
<Label x:Name="lbl輸入文字"
Text="{Binding EntryText1}"/>
<!--這裡若改成 OneWay 會有甚麼效果呢?-->
<Entry x:Name="eny輸入文字1"
Text="{Binding EntryText1, Mode=TwoWay}"/>
<Entry x:Name="eny輸入文字2"
Text="{Binding EntryText2, Mode=TwoWay}"/>
<Button x:Name="btn按鈕" Text="請按下我"
Command="{Binding PushMeCommand}"/>
<Label x:Name="lbl按鈕文字"
Text="{Binding LabelText1}"/>
</StackLayout>
</ContentPage>
MainPage.xaml.cs
內。MVVM2
專案內的 MainPageViewModel
,其 ViewModel 的C#程式碼如下:
在這個 ViewModel 內,定義了四個屬性 (Property)
這個 ViewModel 需要實作
INotifyPropertyChanged
葉面,並且也要實作 OnPropertyChanged
方法using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
namespace MVVM2
{
public class MainPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
#region EntryText1
private string _EntryText1;
public string EntryText1
{
get
{
return _EntryText1;
}
set
{
if (_EntryText1 != value)
{
_EntryText1 = value;
OnPropertyChanged("EntryText1");
}
}
}
#endregion
#region EntryText2
private string _EntryText2;
public string EntryText2
{
get
{
return _EntryText2;
}
set
{
if (_EntryText2 != value)
{
EntryText1 = value;
_EntryText2 = value;
OnPropertyChanged("EntryText2");
}
}
}
#endregion
#region LabelText1
private string _LabelText1;
public string LabelText1
{
get
{
return _LabelText1;
}
set
{
if (_LabelText1 != value)
{
_LabelText1 = value;
OnPropertyChanged("LabelText1");
}
}
}
#endregion
#region Button ICommand
public ICommand PushMeCommand { protected set; get; }
#endregion
public MainPageViewModel()
{
PushMeCommand = new Command(() =>
{
this.LabelText1 = $"您已經按下按鈕";
});
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
}
使用 Prism MVVM 套件
底下說明的內容,已經實作出專案,並且放是在底下 GitHub 內。
使用 Visual Studio 2015,開啟 MVVM3.sln
方案
開啟查看核心PCL MVVM3
專案內的 MainPage.xaml
,其 XAML 標記宣告內容如下:
- 在這個 XAML 宣告中,與 MVVM2 不同的地方,在於 Prism 提供了 ViewModel 自動注入之功能,只要在根項目內,使用
prism:ViewModelLocator.AutowireViewModel="True"
語法,就可以在建立 View 的時候,自動產生相對應的 ViewModel 並且,綁定到 BindingContext 屬性內。
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="MVVM3.Views.MainPage"
Title="MainPage">
<StackLayout
HorizontalOptions="Fill" VerticalOptions="Center">
<Label x:Name="lbl輸入文字"
Text="{Binding EntryText1}"/>
<Entry x:Name="eny輸入文字1"
Text="{Binding EntryText1, Mode=TwoWay}"/>
<Entry x:Name="eny輸入文字2"
Text="{Binding EntryText2, Mode=TwoWay}"/>
<Button x:Name="btn按鈕" Text="請按下我"
Command="{Binding PushMeCommand}"/>
<Label x:Name="lbl按鈕文字"
Text="{Binding LabelText1}"/>
</StackLayout>
</ContentPage>
由於使用了 MVVM 設計方法,所以,也就不會有任何 code behind 在 MainPage.xaml.cs
內。
開啟查看核心PCL MVVM3
專案內的 MainPageViewModel
,其 ViewModel 的C#程式碼如下:
-
由於使用了 Prism 框架來開發,所以,繼承了
BindableBase
這個類別,在這個類別內,已經把 MVVM 需要實作的 ViewModel 要處理的內容,都做好了。
-
其他的內容大都與 MVVM2 內的 ViewModel 一樣。
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MVVM3.ViewModels
{
public class MainPageViewModel : BindableBase, INavigationAware
{
//private string _title;
//public string Title
//{
// get { return _title; }
// set { SetProperty(ref _title, value); }
//}
#region EntryText1
private string _EntryText1;
public string EntryText1
{
get
{
return _EntryText1;
}
set
{
SetProperty(ref _EntryText1, value);
}
}
#endregion
#region EntryText2
private string _EntryText2;
public string EntryText2
{
get
{
return _EntryText2;
}
set
{
SetProperty(ref _EntryText2, value);
EntryText1 = value;
}
}
#endregion
#region LabelText1
private string _LabelText1;
public string LabelText1
{
get
{
return _LabelText1;
}
set
{
SetProperty(ref _LabelText1, value);
}
}
#endregion
#region Button ICommand
public DelegateCommand PushMeCommand { protected set; get; }
#endregion
public MainPageViewModel()
{
PushMeCommand = new DelegateCommand(() =>
{
this.LabelText1 = $"您已經按下按鈕";
});
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public void OnNavigatedTo(NavigationParameters parameters)
{
}
}
}
MVVM3.sln
方案MVVM3
專案內的 MainPage.xaml
,其 XAML 標記宣告內容如下:prism:ViewModelLocator.AutowireViewModel="True"
語法,就可以在建立 View 的時候,自動產生相對應的 ViewModel 並且,綁定到 BindingContext 屬性內。<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="MVVM3.Views.MainPage"
Title="MainPage">
<StackLayout
HorizontalOptions="Fill" VerticalOptions="Center">
<Label x:Name="lbl輸入文字"
Text="{Binding EntryText1}"/>
<Entry x:Name="eny輸入文字1"
Text="{Binding EntryText1, Mode=TwoWay}"/>
<Entry x:Name="eny輸入文字2"
Text="{Binding EntryText2, Mode=TwoWay}"/>
<Button x:Name="btn按鈕" Text="請按下我"
Command="{Binding PushMeCommand}"/>
<Label x:Name="lbl按鈕文字"
Text="{Binding LabelText1}"/>
</StackLayout>
</ContentPage>
MainPage.xaml.cs
內。MVVM3
專案內的 MainPageViewModel
,其 ViewModel 的C#程式碼如下:
由於使用了 Prism 框架來開發,所以,繼承了
BindableBase
這個類別,在這個類別內,已經把 MVVM 需要實作的 ViewModel 要處理的內容,都做好了。
其他的內容大都與 MVVM2 內的 ViewModel 一樣。
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MVVM3.ViewModels
{
public class MainPageViewModel : BindableBase, INavigationAware
{
//private string _title;
//public string Title
//{
// get { return _title; }
// set { SetProperty(ref _title, value); }
//}
#region EntryText1
private string _EntryText1;
public string EntryText1
{
get
{
return _EntryText1;
}
set
{
SetProperty(ref _EntryText1, value);
}
}
#endregion
#region EntryText2
private string _EntryText2;
public string EntryText2
{
get
{
return _EntryText2;
}
set
{
SetProperty(ref _EntryText2, value);
EntryText1 = value;
}
}
#endregion
#region LabelText1
private string _LabelText1;
public string LabelText1
{
get
{
return _LabelText1;
}
set
{
SetProperty(ref _LabelText1, value);
}
}
#endregion
#region Button ICommand
public DelegateCommand PushMeCommand { protected set; get; }
#endregion
public MainPageViewModel()
{
PushMeCommand = new DelegateCommand(() =>
{
this.LabelText1 = $"您已經按下按鈕";
});
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public void OnNavigatedTo(NavigationParameters parameters)
{
}
}
}