在目前開發中,流行的開發模式有MVC(模型-視圖-控制器)和MVP(模型-視圖-表示器),其中MVC模式常見於java web開發中,比如Struts2、Struts2,後來微軟也推出了MVC模式的開發框架。MVP模式是從MVC演變過來的,作為一種新的模式,MVP與MVC有着一個重大的區別:在MVP中View並不直接使用Model,它們之間的通信是通過Presenter (MVC中的Controller)來進行的,所有的交互都發生在Presenter內部,而在MVC中View會從直接Model中讀取數據而不是通過 Controller。但是,如果把它們都用在與WPF中,存在着一個重要的缺陷:根本沒有考慮數據綁定技術。也就是説,在這些模式中,控制器或者表示器負責作用與視圖,如在文本庫填寫文字,加載列表框,填充網格。而WPF最大的優點在於它豐富的數據綁定能力。如果在WPF中利用MVC或者MVP,則完全忽略了WPF中數據綁定的便利性。
有沒有一種方法可以從模型中分離視圖,而不用忽略大WPF的特性呢,答案就是採用模型-視圖-視圖模型模式。這個模式是在John Gossman博客中看到的,他以前是Microsoft Expression Blend團隊的成員之一,現在在Microsoft的WPF團隊。模型-視圖-視圖模型模式把模型從視圖分離,但同時充分利用WPF的特性。該模式採用純粹的模型,創建一個包含狀態的抽象視圖,用可視化設計器創建一個視圖,將其數據綁定到抽象視圖。其中可視化設計器可以是Microsoft Expression Blend工具,抽象視圖就是視圖模型。視圖和視圖模型之間通過數據綁定建立雙向連接。經過適當的設置,每一個視圖只包含純粹的XAML以及非常少量的過程代碼,這樣UI美工就可以專注於設計視圖,而不必擔心對開發有任何的影響。
在這裏,還要介紹下視圖模型這個概念。視圖模型存在的目的就是把模型適配到視圖。這就意味着在模型中有一個方法,該方法返回一個特性的類型,比如IList<T>類型,然後視圖把來自模型的類型轉換為類似WPF UI元素的CollectionView類來幫頂數據。此外,因為WPF自然的實現了命令(Command)模式,也就是説某個UI元素,比如按鈕,它有一個屬性為Command,是WPF定義的ICommand類型。於是,我們可以把命令放進視圖模型中,並作為公有屬性提供,以便視圖綁定。這將非常有用,因為它允許把可執行代碼綁定到表單上的按鈕,而不用編寫任何代碼來連接按鈕。WPF的命令模式以及視圖模型的公有Command屬性負責上述工作。
講了那麼多,下面通過實例來實踐下。
一、建立一個模型類,命名為ProjectService,該類中提供了一個供視圖模型類方法的方法GetAllProject,代碼如下:
public class ProjectService
{
public static IList<Project> GetAllProject()
{
List<Project> list = new List<Project>();
list.Add(new Project { ProjectName = "CMMI4", Id = 1 });
list.Add(new Project { ProjectName = "GXQXT", Id = 2 });
return list;
}
}
這段代碼很簡潔,功能非常的簡單,僅僅是提供一個集合給視圖
二、構建一個視圖,主要是在界面中已下拉表的形式向用户顯示列表。為此,需要構建一個響應的視圖模型類,這樣就可以讓視圖類來綁定它。所以在這裏定義了一個視圖模型類ProjectViewModel,該類提供一個列表和兩個命令:
public class ProjectViewModel
{
private CollectionView projects;
private DelegateCommand selectCommand;
private DelegateCommand cancelCommand;
private IView view;
public ProjectViewModel()
{
}
public ProjectViewModel(IView view)
{
this.view = view;
this.projects = new CollectionView(ProjectService.GetAllProject());
this.selectCommand = new DelegateCommand(this.SelectCommandHandler);
this.cancelCommand = new DelegateCommand(this.CancelCommandHandler);
}
public CollectionView Projects
{
get { return this.projects; }
}
public DelegateCommand SelectCommand
{
get { return this.selectCommand; }
}
public DelegateCommand CancelCommand
{
get { return this.cancelCommand; }
}
private void SelectCommandHandler(object sender, EventArgs e)
{
Project project = this.projects.CurrentItem as Project;
System.Windows.MessageBox.Show(project.ProjectName, "Project");
this.view.Close();
}
private void CancelCommandHandler(object sender, EventArgs e)
{
this.view.Close();
}
}
在ProjectViewModel中,用到了IView類型,它是一個接口。主要是提供一個視圖的引用,在這裏,我們在IView中定義了兩個方法,Show方法和Close方法。而且WPF Window類碰巧也實現了這兩個方法:
public interface IView { void Show(); void Close(); }
然後ProjectViewModel要做的另外一件事就是把IList<Project>列表的項目轉換成WPF的CollectionView類,所以在視圖模型類中提供了Projects公有屬性給視圖。接下來的兩個DelegateCommand 屬性,用於視圖類中的按鈕設置ICommand性,DelegateCommand 不僅實現了ICommand接口,而且還允許調用ICommand的Execute方法時,調用一個委託,DelegateCommand 的代碼如下:
public class DelegateCommand : ICommand
{
public delegate void SimpleEventHandler(object sender, EventArgs e);
private SimpleEventHandler handler;
private bool isEnabled = true;
public DelegateCommand(SimpleEventHandler handler)
{
this.handler = handler;
}
public bool CanExecute(object parameter)
{
return this.isEnabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
this.handler(this, EventArgs.Empty);
}
private void OnCanExecuteChanged()
{
if(this.CanExecuteChanged != null)
{
this.CanExecuteChanged(this,EventArgs.Empty);
}
}
public bool IsEnabled
{
get { return this.isEnabled; }
set
{
this.isEnabled = value;
this.OnCanExecuteChanged();
}
}
}
到現在為止,已經接到了視圖模型類,下面將講述視圖實際上如果使用它並和它通訊,視圖類ProjectView的隱藏代碼很少,唯一為它編寫的代碼就是在構造器中用來連接表達的Window元素的DataContext屬性:
public partial class ProjectView : Window, IView { public ProjectView() { InitializeComponent(); this.DataContext = new ProjectViewModel(this); } }
注意,ProjectView實現了IView接口的Show、Close方法。然後在ProjectView視圖的XAML中編寫顯示到UI'的代碼:
<ComboBox Height="26" Margin="175,98,28,0" Name="projectcombox" VerticalAlignment="Top" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Path=Projects}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=ProjectName}"/>
<TextBlock Grid.Column="1" Text="{Binding Path=Id}"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Margin="47,0,0,35" Name="cancelButton" Command="{Binding Path=CancelCommand}" HorizontalAlignment="Left" VerticalAlignment="Bottom">Cancel</Button>
<Button Margin="0,0,28,35" Name="okButton" Command="{Binding Path=SelectCommand}" HorizontalAlignment="Right" VerticalAlignment="Bottom">OK</Button>
這樣一來,一個模型-視圖-視圖模型表示模式的實例就出來了,通過這個實例,我們可以很明瞭的看到。UI設計與後台代碼之間的關聯就沒有那麼強了,界面設計與開發獨立開來。