Model View View-Model 模式(MVVM)

MVVM是wp7开发的一种设计模式,其目的也是为了将数据层(Model)与UI层(View)分开。ViewModel则是用来连接数据层与UI层的C#类。个人感觉,MVVM和MVC是差不多的。下面就用微软的sample code来理解下MVVM 设计模式。

 

这个例子是一个游戏成就的记录工具,分为两个部分:收集品成就,以及等级成就。

收集品成就 又分为药品数量,硬币数量,心数量。

等级成就 有1,2,3三个等级。

 

游戏效果如下

 

Model View View-Model 模式(MVVM)_第1张图片

 

下面讲实现代码,先建立一个Silverlight for Windows Phone工程。

solution结构如下

 

 

 建立Model层类

using System; using System.ComponentModel; namespace MVVMTestApp.Model { //实现INotifyPropertyChanged接口的目的是,当Property改变的时候通知view刷新。 public class Accomplishment : INotifyPropertyChanged { // 成就名. public string Name { get; set; } // 成就种类. public string Type { get; set; } // 每种收集品的名称. private int _count; public int Count { get { return _count; } set { _count = value; //当收集品数量改变时触发属性改变事件 RaisePropertyChanged("Count"); } } // 等级是否完成 private bool _completed; public bool Completed { get { return _completed; } set { _completed = value; //当等级成就完成度改变时触发属性改变事件 RaisePropertyChanged("Completed"); } } public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } // Create a copy of an accomplishment to save. // 克隆出一个对象,但是不包括已绑定的数据 public Accomplishment GetCopy() { Accomplishment copy = (Accomplishment)this.MemberwiseClone(); return copy; } } }

 

建立ViewModel层类

using System; using System.Windows; using System.Collections.ObjectModel; using System.IO.IsolatedStorage; using MVVMTestApp.Model; namespace MVVMTestApp.ViewModelNamespace { /* 该类的作用是,建立一条用以存储成就元素的链表,并提供初期化,存储,取得链表的方法 */ public class ViewModel { //这种容器的特点是当发生item的增删改时会触发事件 public ObservableCollection<Accomplishment> Accomplishments { get; set; } public void GetAccomplishments() { if (IsolatedStorageSettings.ApplicationSettings.Count > 0) { GetSavedAccomplishments(); } else { GetDefaultAccomplishments(); } } public void GetDefaultAccomplishments() { ObservableCollection<Accomplishment> a = new ObservableCollection<Accomplishment>(); // Items to collect a.Add(new Accomplishment() { Name = "Potions", Type = "Item" }); a.Add(new Accomplishment() { Name = "Coins", Type = "Item" }); a.Add(new Accomplishment() { Name = "Hearts", Type = "Item" }); a.Add(new Accomplishment() { Name = "Swords", Type = "Item" }); a.Add(new Accomplishment() { Name = "Shields", Type = "Item" }); // Levels to complete a.Add(new Accomplishment() { Name = "Level 1", Type = "Level" }); a.Add(new Accomplishment() { Name = "Level 2", Type = "Level" }); a.Add(new Accomplishment() { Name = "Level 3", Type = "Level" }); Accomplishments = a; //MessageBox.Show("Got accomplishments from default"); } public void GetSavedAccomplishments() { ObservableCollection<Accomplishment> a = new ObservableCollection<Accomplishment>(); foreach (Object o in IsolatedStorageSettings.ApplicationSettings.Values) { a.Add((Accomplishment)o); } Accomplishments = a; //MessageBox.Show("Got accomplishments from storage"); } public void SaveAccomplishments() { IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings; foreach (Accomplishment a in Accomplishments) { if (settings.Contains(a.Name)) { settings[a.Name] = a; } else { settings.Add(a.Name, a.GetCopy()); } } settings.Save(); MessageBox.Show("Finished saving accomplishments"); } } }

 

 建立两个新View,ItemView.xaml(收集品的表示区域)和LevelView.xaml(等级的表示区域)

 编辑ItemView.xaml,在GRID element里添加以下source

 <ListBox ItemsSource="{Binding}"> <ListBox.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="200"/> <ColumnDefinition Width="80"/> <ColumnDefinition Width="100"/> </Grid.ColumnDefinitions> <!--Mode 属性用于定义绑定模式,它将决定数据如何在源和目标之间流动。除 OneWay 之外,还有另外三种绑定模式:OneTime、OneWayToSource 和 TwoWay。 正如前面的代码段中所示,使用 OneWay 绑定时,每当源发生变化,数据就会从源流向目标。尽管我在示例中显式指定了此绑定模式,但其实 OneWay 绑定是 TextBlock 的 Text 属性的默认绑定模式,无需对其指定。和 OneWay 绑定一样,OneTime 绑定也会将数据从源发送到目标;但是,仅当启动了应用程序或 DataContext 发生更改时才会如此操作,因此,它不会侦听源中的更改通知。与 OneWay 和 OneTime 绑定不同,OneWayToSource 绑定会将数据从目标发送到源。最后,TwoWay 绑定会将源数据发送到目标,但如果目标属性的值发生变化,则会将它们发回给源。 确定依赖项属性绑定在默认情况下是单向还是双向的编程方法是:使用 GetMetadata 来获取属性的属性元数据,然后检查 BindsTwoWayByDefault 属性的布尔值。 --> <TextBlock x:Name="Item" Text="{Binding Path=Name, Mode=OneWay}" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" /> <TextBox x:Name="Count" Text="{Binding Path=Count, Mode=TwoWay}" Grid.Column="1" TextAlignment="Center" InputScope="Number"/> <TextBlock x:Name="Check" Text="{Binding Path=Count, Mode=OneWay}" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox>

 

建立第二个view

在头部的UserControl增加以下代码。这里表示要引用MVVMTestApp.View的source。这里:src是自定义的tag名,也可以用别的命名。

下面调用BoolOpposite的时候要跟前缀src:。

xmlns:src="clr-namespace:MVVMTestApp.View"

 

在UserControl 和 GRID 之间, 增加以下代码,这里添加source的目的是增加一个Converter。

因为开发者认为等级成就完成后,是不能退回去的。也就是说当等级成就为false时,该等级是可编辑的。

但等级变成true以后就不能再编辑了。

 

<UserControl.Resources>
    <src:BoolOpposite x:Key="BoolOpposite"/>
</UserControl.Resources>

 

GRID element里添加以下source

 <ListBox ItemsSource="{Binding}"> <ListBox.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="200"/> <ColumnDefinition Width="80"/> <ColumnDefinition Width="100"/> </Grid.ColumnDefinitions> <TextBlock x:Name="Level" Text="{Binding Path=Name, Mode=OneWay}" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center"/> <CheckBox x:Name="Completed" IsChecked="{Binding Path=Completed, Mode=TwoWay}" Grid.Column="1" HorizontalAlignment="Center" IsEnabled="{Binding Path=Completed, Converter={StaticResource BoolOpposite}}"/> <TextBlock x:Name="Check" Text="{Binding Path=Completed, Mode=OneWay}" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox>

 

这里,CheckBox是否钩上,以及能否编辑都与Completed属性绑定,但是能否编辑增加了反转Completed属性的Converter:BoolOpposite

<CheckBox x:Name="Completed" IsChecked="{Binding Path=Completed, Mode=TwoWay}" Grid.Column="1" HorizontalAlignment="Center" IsEnabled="{Binding Path=Completed, Converter={StaticResource BoolOpposite}}"/>

 

将第二个View里,UI层的对应代码替换如下:

Copy using System; using System.Windows.Controls; using System.Globalization; namespace MVVMTestApp.View { public partial class LevelView : UserControl { public LevelView() { InitializeComponent(); } } public class BoolOpposite : System.Windows.Data.IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { bool b = (bool)value; return !b; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { string s = value as string; bool b; if (bool.TryParse(s, out b)) { return !b; } return false; } } }

 

编辑主View,在phone tag里添加一下代码:

xmlns:views="clr-namespace:MVVMTestApp.View"

ContentPanel GRID element里,增加以下属性

<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"></Grid>

替换ContentPanel GRID element 的默认代码如下

 <!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <StackPanel>
        <TextBlock Text="Items Collected" Foreground="{StaticResource PhoneAccentBrush}" Style="{StaticResource PhoneTextLargeStyle}" />
        <views:ItemView x:Name="ItemViewOnPage" Height="200"/>
        <TextBlock Text="Levels Completed" Foreground="{StaticResource PhoneAccentBrush}" Style="{StaticResource PhoneTextLargeStyle}" />
        <views:LevelView x:Name="LevelViewOnPage" Height="200"/>
    </StackPanel>
</Grid>
修改主View对应的UI层代码
using System;
using System.Linq;
using System.Windows;
using Microsoft.Phone.Controls;
using MVVMTestApp.ViewModelNamespace;

namespace MVVMTestApp
{
    public partial class MainPage : PhoneApplicationPage
    {
        private ViewModel vm;

        // Constructor
        public MainPage()
        {
            InitializeComponent();
            vm = new ViewModel();
        }


        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            // Later, you will replace this next line with something better.
            vm.GetAccomplishments();


            // There are two different views, but only one view model.
            // 这里使用Linq从.Accomplishments里检索出Accomplishment集,绑定到ListBox控件

            // Set the data context for the Item view.
            // from 临时item对象 in item容器 where 条件 select 临时item对象里的值
            ItemViewOnPage.DataContext = from Accomplishment in vm.Accomplishments where Accomplishment.Type == "Item" select Accomplishment;

            // Set the data context for the Level view.
            LevelViewOnPage.DataContext = from Accomplishment in vm.Accomplishments where Accomplishment.Type == "Level" select Accomplishment;

            // If there is only one view, you could use the following code
            // to populate the view.
            //AccomplishmentViewOnPage.DataContext = vm.Accomplishments;
        }
    }
}

 
增加一个类用以维护页面的状态。
using System;

namespace MVVMTestApp
{
    public static class StateUtilities
    {
        private static Boolean isLaunching;

        public static Boolean IsLaunching
        {
            get { return isLaunching; }
            set { isLaunching = value; }
        }
    }
} 
修改App.xaml对应的Code,替换如下
private void Application_Launching(object sender, LaunchingEventArgs e)
{
    StateUtilities.IsLaunching = true;
}

private void Application_Activated(object sender, ActivatedEventArgs e)
{
    StateUtilities.IsLaunching = false;
} 
 主View 的对应Code,替换如下: 
      
      
      
      
// Old instance of the application
// The user started the application from the Back button.
if (!StateUtilities.IsLaunching && this.State.ContainsKey("Accomplishments"))
{
    vm = (ViewModel)this.State["Accomplishments"];
    //MessageBox.Show("Got data from state");
}
// New instance of the application
// The user started the application from the application list,
// or there is no saved state available.
else
{
    vm.GetAccomplishments();
    //MessageBox.Show("Did not get data from state");
}
OnNavigatedTo 函数后增加以下方法,当State里没有Accomplishments属性时,增添Accomplishments属性,否则替换它。
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedFrom(e);

    if (this.State.ContainsKey("Accomplishments"))
    {
        this.State["Accomplishments"] = vm;
    }
    else
    {
        this.State.Add("Accomplishments", vm);
    }

  
增加application bar, OnNavigatedFrom method后面添加一下代码
private void AppBarSave_Click(object sender, EventArgs e)
{
    vm.SaveAccomplishments();
}
在MainPage.xaml 里,把</phone:PhoneApplicationPage.ApplicationBar>-->替换如下:
<phone:PhoneApplicationPage.ApplicationBar>
    <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True" >
        <shell:ApplicationBarIconButton IconUri="AppBarSave.png" Text="Save"  Click="AppBarSave_Click" />
    </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
完成!

你可能感兴趣的:(Model View View-Model 模式(MVVM))