简单MVVM教程2(转自CSDN-ktei2008)

上一次,我只是开了个头而已,然而在这一章中,我们将看到一点实际的代码了。我构想了很久,怎样让新手能快速掌握我想要传达的知识,然后我得出一个结论:一定一定要简单化,并且要有看的见摸的着的代码实例。好吧,我们开始。

打开你的VS2010,新建一个WPF项目,命名为MvvmTutorial即可。紧接着,在当前Solution添加4个文件夹,分别为:Infrastructure, Views, ViewModels, Models。然后,把App.xaml改成如下:

<Application x:Class="MvvmTutorial.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"></Application>

把MainWindow.xaml,MainWindow.xaml.cs删掉。在Views下添加一个新的WPF Window,命名为ShellView。在App.xaml.cs中加入:

protectedoverridevoid OnStartup(StartupEventArgs e)
{
bbase.OnStartup(e);
var shell =new ShellView();
shell.Show();
}

都完成了吗?现在按F5,你应该可以看到你的程序正常运行,并且已经可以看到主窗体了。至此,我们只是做了一些准备工作。Shell其实就是壳的意思,每一个Windows程序都会有一个主窗体,也就是一个壳,我们在这个壳上面拼凑各种View来构成一个丰富的应用程序。

现在我们来看一下具体我们要做些什么。我说过,尽量简单化,所以我们这章的任务很简单:就是把一些联系人显示在主窗体里。我们的联系人很简单,只有两个属性:名字和电话号码。我个人喜欢从ViewModel这一层出发,但是许多朋友还是习惯从Model层写起,那么我们就先来研究一下Model,即实体。在第一章中有人问是否应该在Model里实现INotifyPropertyChanged接口,我个人认为这个没有特别的死的做法。有点人愿意把Model尽量简单化,也就是说只是一个数据的载体而已,而把所有与View打交道的事情全部推给ViewModel。然而对于我们这个简单的例子,我选择让Model也实现INotifyPropertyChanged接口,如此一来,当我们对Model做出修改后,View上就可以显示出变化了(不过在今天这一章里,我们暂时不讨论ViewModel/Model的修改)。现在,在Models下添加一个Contact类。

我们的Model,很简单,具体是这个样子的:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MVVM_WPF.Infrastructure;


namespace MVVM_WPF.Models
{
/// <summary>
/// Our Contact model, which stores data retrieved from data persistence layer
/// </summary>
public class Contact : ObservableObject
{
#region Fields


string _name;
string _phoneNumber;


#endregion // Fields


#region Properties


public string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged(() => Name);
}
}
}


public string PhoneNumber
{
get
{
return _phoneNumber;
}
set
{
if (_phoneNumber != value)
{
_phoneNumber = value;
RaisePropertyChanged(() => PhoneNumber);
}
}
}


#endregion // Properties
}
}

 

 


你们可能注意到了这个Contact实际上继承了ObservableObject。这是一个抽象的基类,由于我们将来会有很多类都要实现INotifyPropertyChanged接口,所以我在这里写了一个基类。在Infrastructure下添加一个ObservableObject:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Linq.Expressions;

namespace MVVM_WPF.Infrastructure
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

public virtual void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

public void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
var propertyName = PropertySupport.ExtractPropertyName(propertyExpression);
this.RaisePropertyChanged(propertyName);
}
}
}

好的,这里我要说一下,其实这个类不是我个人写的,而是“剽窃”了一些现有的开源代码,拼凑起来的。请注意PropertySupport,这是一个静态类,它其实来自于微软的Prism框架里一小段代码,我之所以这样做只是让大家看的更清楚这些MVVM的框架内部大体是怎么回事,其实都是大同小异的。请在Infrastructure下添加PropertySupport:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

namespace MVVM_WPF.Infrastructure
{
    public static class PropertySupport
    {
        public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
        {
            if (propertyExpression == null)
            {
                throw new ArgumentNullException("propertyExpression");
            }

            var memberExpression = propertyExpression.Body as MemberExpression;
            if (memberExpression == null)
            {
                throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
            }

            var property = memberExpression.Member as PropertyInfo;
            if (property == null)
            {
                throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
            }

            var getMethod = property.GetGetMethod(true);
            if (getMethod.IsStatic)
            {
                throw new ArgumentException("The referenced property is a static property.", "propertyExpression");
            }

            return memberExpression.Member.Name;
        }
    }
}

 

 

有的人可能会问“PropertySupport”到底是干嘛的?其实我们经常写这样的代码:RaisePropertyChanged("SomeProperty"),这个代码本身没有任何问题,但是我们传入的是一个string,这也就暗示了两个小问题:1.当你的Property改名字以后,你需要修改这个string;2.输入string是个稍微容易出错的过程(打字错误)。那么PropertySupport.ExtractPropertyName便在牺牲了一点点效率的前提下,通过指定一个Expression来获得其属性的名字。所以我们就可以写成:RaisePropertyChanged(() => PhoneNumber);如此一来,你只需要指定哪个属性即可,至于它的名字,不需要你操心,而且当下次你修改PhoneNumber为PrivatePhoneNumber后,你也不需要修改任何string。

Okay,既然我们说了要显示出一系列联系人,我们便需要一个View和ViewModel。在ViewModels下添加ContactMasterViewModel:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MVVM_WPF.Infrastructure;
using System.Collections.ObjectModel;
using MVVM_WPF.Models;

namespace MVVM_WPF.ViewModels
{
    public class ContactMasterViewModel : ObservableObject
    {
        #region Fields

        private bool _isLoaded;
        private ObservableCollection<Contact> _items = new ObservableCollection<Contact>();

        #endregion // Fields

        #region Properties

        public IEnumerable<Contact> Items
        {
            get
            {
                // If we load this view model in design mode (for example, in VS or Expression),
                // we add some random data so that we can preview the layout of our view
                if (DesignHelper.IsInDesignMode)
                {
                    for (int i = 0; i < 25; ++i)
                    {
                        _items.Add(new Contact().GenerateRandomData());
                    }
                }
                else if (!_isLoaded)
                {
                    Load();
                }
                return _items;
            }
        }

        #endregion // Properties


        #region Private Methods

        private void Load()
        {
            // TODO: Once we finish the implementation of data persistence layer,
            // we need to re-write this code to make this method work for real-world app.

            // We haven't implemented data persistence
            // Therefore, we temporarily load some random data in memory
            for (int i = 0; i < 25; ++i)
            {
                _items.Add(new Contact().GenerateRandomData());
            }
            _isLoaded = true;
        }

        #endregion // Private Methods
    }
}

注意这里面的DesignHelper:在Infrastructure下添加DesignHelper类:

DesignHelper内容如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

namespace MVVM_WPF.Infrastructure
{
    public static class DesignHelper
    {
        #region Fields

        private static bool? _isInDesignMode;
        private static readonly Random Rand = new Random(Guid.NewGuid().GetHashCode());

        #endregion // Fields

        #region Properties

        /// <summary>
        /// Gets a value indicating whether the control is in design mode
        /// (running in Blend or Visual Studio).
        /// </summary>
        [SuppressMessage(
            "Microsoft.Security",
            "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands",
            Justification = "The security risk here is neglectible.")]
        public static bool IsInDesignMode
        {
            get
            {
                if (!_isInDesignMode.HasValue)
                {
#if SILVERLIGHT
                    _isInDesignMode = DesignerProperties.IsInDesignTool;
#else
#if WIN8
                    _isInDesignMode = Windows.ApplicationModel.DesignMode.DesignModeEnabled;
#else
                    var prop = DesignerProperties.IsInDesignModeProperty;
                    _isInDesignMode
                        = (bool)DependencyPropertyDescriptor
                                     .FromProperty(prop, typeof(FrameworkElement))
                                     .Metadata.DefaultValue;

                    // Just to be sure
                    if (!_isInDesignMode.Value
                        && Process.GetCurrentProcess().ProcessName.StartsWith("devenv", StringComparison.Ordinal))
                    {
                        _isInDesignMode = true;
                    }
#endif
#endif
                }

                return _isInDesignMode.Value;
            }
        }

        #endregion // Properties

        #region Public Methods

        /// <summary>
        /// Gets a random string, given its minimum length and maximum length
        /// </summary>
        public static string GetRandomString(int minLen = 15, int maxLen = 50)
        {
            StringBuilder builder = new StringBuilder();
            int length = Rand.Next(minLen, maxLen);
            char ch;
            for (int i = 0; i < length; i++)
            {
                ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * Rand.NextDouble() + 65)));
                builder.Append(ch);
            }

            return builder.ToString().ToLower();
        }

        public static string GetRandomNumericString(int minLen = 15, int maxLen = 25)
        {
            StringBuilder builder = new StringBuilder();
            int length = Rand.Next(minLen, maxLen);
            for (int i = 0; i < length; i++)
            {
                builder.Append(Rand.Next(0, 10).ToString());
            }

            return builder.ToString().ToLower();
        }

        #endregion // Public Methods
    }
}

注意IsInDesignMode属性:这是我剽窃MvvmLight的代码。它的作用在于调用它,你可以知道当前你是处于开发视图下还是runtime环境下,什么意思呢,等会儿再解释。还有一个new Contact().GenerateRandomData(),这个GenerateRandomData其实来自于一个Helper类:在Models下添加ModelHelper:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MVVM_WPF.Infrastructure;

namespace MVVM_WPF.Models
{
    public static class ModelHelper
    {
        public static Contact GenerateRandomData(this Contact target)
        {
            target.Name = DesignHelper.GetRandomString(5, 15);
            target.PhoneNumber = DesignHelper.GetRandomNumericString(8, 12);
            return target;
        }
    }
}

 

这个应该很容易理解,就是给一个Contact的属性添加一些随机的数据而已,这样便于我们设计和测试。有了ContactMasterViewModel,我们必然需要ContactMasterView,于是在Views下添加ContactMasterView,其XAML代码如下:
<UserControl x:Class="MVVM_WPF.Views.ContactMasterView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
   <UserControl.Resources>
        <!--<mc:ContactMasterViewModel x:Key="DesignContext"/>-->      
    </UserControl.Resources>
    <Grid>
        <ListView ItemsSource="{Binding Items, Mode=OneWay}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Name" Width="150">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Name, Mode=OneWay}" VerticalAlignment="Center"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>

                    <GridViewColumn Header="Phone" Width="150">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding PhoneNumber, Mode=OneWay}" VerticalAlignment="Center"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</UserControl>

 


请注意d:DataContext="{Binding Source={StaticResource DesignContext}}"这里,我们给View绑上来一个DataContext,然而这只是开发视图环境下的DataContext而已。很多时候,当你做完View以后,你希望直接可以在开发视图中看到结果,这样一来你便不需要不断编译,运行,你可以很快获得修改View以后的视觉反馈(我承认我这里的中文讲话水平真的不高)。然而,还记得之前的DesignHelper.IsInDesignMode吗?它可以决定你当前是在运行程序,还是在开发视图下,如果在开发视图下,我们就给ContactMasterViewModel.Items中添加一些随机数据,如果不是开发视图,我们才真正的要从数据层返回一些真实数据(不在本章介绍数据层)。

现在还缺什么呢?ShellViewModel:在ViewModels下添加ShellViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MVVM_WPF.Infrastructure;

namespace MVVM_WPF.ViewModels
{
    public class ShellViewModel : ObservableObject
    {
        #region Fields

        ContactMasterViewModel _contactMaster;

        #endregion // Fields

        #region Properties

        public ContactMasterViewModel ContactMaster
        {
            get
            {
                if (_contactMaster == null)
                {
                    _contactMaster = new ContactMasterViewModel();
                }
                return _contactMaster;
            }
        }

        #endregion // Properties
    }
}

 

将ShellView.xaml修改如下:
<Window x:Class="MVVM_WPF.Views.ShellView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ShellView" Height="300" Width="300" xmlns:my="clr-namespace:MVVM_WPF.Views">
    <Grid>
        <!--<view:ContactMasterView DataContext="{Binding ContactMaster}"/>-->
        <my:ContactMasterView DataContext="{Binding ContactMaster}"  />        
    </Grid>
</Window>

 

这里应该很好理解,我们将ContactMasterView放到ShellView中,并为其指定了DataContext。那么ShellView的DataContext从哪里来呢?再次,为了简化教程,我们用一种比较天真直接的办法:修改该App.xaml.cs如下:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;
using MVVM_WPF.Views;
using MVVM_WPF.ViewModels;

namespace MVVM_WPF
{
    /// <summary>
    /// App.xaml 的交互逻辑
    /// </summary>
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            var shell = new ShellView();
            var shellViewModel = new ShellViewModel();
            shell.DataContext = shellViewModel;
            shell.Show();
        }
    }
}

现在按F5,运行程序,顺利的话,你应该可以看到一些随机数据显示在界面上。好了大体就先讲到这里。其实大家可以看到,我并没有什么奇思妙想,基本上就是一路“剽窃”各路高手的代码,然后自己拼凑了个小例子而已。编程总是有一些很固定的东西,而且这么多年来,全世界的编程高手,专家,都已经写出来许多拿来即用的东西,我们需要做的是掌握好基础,多读书,多关注新知识,然后“拿来主义”,让这些好用的工具为我们服务。当然,我这个例子目前来看,实在太简单了,不过别急,后面还有新东西要讲的。下回见。谢谢大家支持。

 

 

posted @ 2012-05-24 16:14  浪子の无悔  阅读(544)  评论(0编辑  收藏  举报