MVVM开发模式实例解析

此实例为本人依托网上原有资料,加以个人理解整合,在MVVM模式下写的一个基于MySQL数据库的小Demo,实现对数据库中内容的搜索显示功能。文档下载 源码下载

本实例基于VS2012+MySQL+SilverLight5实现。

MVVM开发模式

概述

MVVM(Model-View-ViewModel)是在WPF/Silverlight一种很常见的开发模式,是MVC模式的一种变种,从字面意义上便可知MVVM模式大致上可以分为三个模块即Model、View和ViewModel。Model(模型)层主要存放一些实际数据,业务逻辑代码, View(视图)就是与用户交互的界面层,存放着我们的Xaml页面,而通过Xaml页面控件的Binding技术就能够实现View和Model之间的关联,但实际编程时我们发现, Model中的属性(与方法)往往不那么容易与View中的界面控件关联起来, 比如类型不匹配界面控件所需要的类型与模型中属性提供的类型不匹配,也就是说需要额外操作,模型中的数据需要经过一些额外的处理才能传给视图,这诞生了我们的ViewModel模块,ViewModel视图模块层的作用用来连接业务逻辑和视图层的关键部分,通常我们发出的命令或者事件都是通过这层传送给业务逻辑层的。这样MVVM模式解决了,我们在开发WPF/Silverlight应用程序过程中产生的业务层、表示层比较混乱问题,使表示层和业务层完全分离。

由于我们的silverLight项目无法直接连接MySQL数据库,通常的做法是通过Web Service和WCF服务,所以在对数据库的操作将放到web项目中,然后通过silverLight中引用相应的服务便可间接时间对数据库的访问。

实例演示

新建一个silverLight应用程序MVVMDemo, 生产解决方案后会有两个项目,一个是silverLight的项目,一个是以silverLight项目名加.web的项目。

数据库层

项目主要功能:实现对数据库中的用户根据用户名的模糊查询。

数据库的设计:数据库名:db_user,表名:t_user, 详细见下图:


在silverLight项目中新建四个文件夹,分别是Model, View, ViewModel和Command,Command我们用来存放的命令,Model我们用来存放获取到的数据,View我们用来存放显示查询的UserControl,ViewModel用来实现相应数据和命令的绑定。在web项目中新建Entiyi文件夹用来存放数据库实体类(User),同时在web项目下新建一个操作数据库的类SQLHelper和一个提供给SilverLight项目执行用户信息相关操作的web服务,UserInfoWebService.asmx截图如下:


 

要使用MySQL数据库,首先需要下载安装mysql-connector-net-6.6.4,然后在web项目中引用安装目录下的MySql.Data.dll, 然后再Web.config配置连接字符串如下:

  <appSettings>

    <addkey="ip"value="localhost"/>

    <addkey="port"value="3306"/>

    <addkey="user"value="root"/>

    <addkey="pwd"value="teamwork603_04"/>

    <addkey="database"value="db_user"/>

    <addkey="encoding"value="utf8"/>

    <addkey="parseType"value="HTML"/>

</appSettings>

这样在SQLHelper类中再引用MySql.Data.MySqlClient; System.Configuration;便可实现对数据库的访问,在此就不贴上具体实现代码。

然后在UserInfoWebService.asmx中便可以通过调用SQLHelper类中的方法进一步封装成类集合供silverLight项目调用,在UserInfoWebService.asmx写入以下方法。

        [WebMethod]

        publicList<User>GetUserByName(string sqlStr)

        {

            List<User> userList = newList<User>();

            SQLHelpersqlHelper = newSQLHelper();

            DataSetuserDataSet = sqlHelper.QueryData(sqlStr);

            foreach (DataRow dr in userDataSet.Tables[0].Rows)

            {

                Useruser = newUser(Convert.ToInt32(dr[0]), Convert.ToString(dr[1]));

                userList.Add(user);

            }

            return userList;

        }

这样一个根据用户名查询用户的web服务方法便完成了,接下来再silverLight的项目中引用—>添加服务引用—>发现,然后选后刚刚的UserInfoWebService.asmx,即可,如图:


可以修改命名空间为UserInfoServiceReference,在silverlight的项目中你会发现多出了一个Service References的文件夹,文件夹里面就是引用的服务。

View层

到了这步,对数据库部分的工作就大致完成了,结下了就是通过MVVM模式来实现查询了。

首先在View文件夹下新建一个Xaml页面,用户提供查询的界面,如下图:


也就是一个文本框,一个按钮,和一个显示结果的DataGrid,代码在后面。

ViewModel层

我们可以看到有三个控件,为了在ViewModel层实现绑定,需要在ViewModel层分别为这三个控件定义相应的属性, 在文本框输入查询字符后文本框的文本属性就变化,查询到数据后更新到DataGrid后相应的属于也会变化, 要实现属性变化的通知就需要实现INotifyPropertyChanged 接口,INotifyPropertyChanged接口定义了一个当属性值改变时执行的事件,名称为PropertyChanged,这是继承这个借口必须要实现的事件, event PropertyChangedEventHandler PropertyChanged; PropertyChangedEventHandler委托类delegate voidPropertyChangedEventHandler(object sender, PropertyChangedEventArgs e); PropertyChangedEventArgs类型,这个类用于传递更改值的属性的名称,实现向客户端已经更改的属性发送更改通知,属性的名称为字符串类型。根据PropertyChanged事件的委托类,实现PropertyChanged事件:

PropertyChanged(this, new PropertyChangedEventArgs("PropertyName"));

综上,可在ViewModel中新建一个QueryUserViewModel类,继承INotifyPropertyChange接口,定义查询关键字和查询结果两个变量,并实现相应的属性更改事件:

        #region属性

        ///<summary>

        ///查询关键字

        ///</summary>

        publicstring SearchText

        {

            get { return _SearchText; }

            set

            {

                _SearchText = value;

                if(PropertyChanged != null)

                {

                    this.PropertyChanged(this, newPropertyChangedEventArgs("SearchText"));

                }

            }

        }

 

        ///<summary>

        ///查询结果

        ///</summary>

        publicObservableCollection<User> ResultText

        {

            get { return _ResultText; }

            set

            {

                _ResultText = value;

                if(PropertyChanged != null)

                {

                    this.PropertyChanged(this, newPropertyChangedEventArgs("ResultText"));

                }

            }

        }

接下来还有按钮事件的绑定,按钮具有Command属性,实现自定义命令需要继承ICommand接口。在该接口中定义了一个event EventHandler CanExecuteChanged事件,当出现影响是否应执行该命令的更改时发生,bool CanExecute(object para)方法,定义用于确定此命令是否可以在其当前状态下执行的方法。以及void Execute(object para)执行此命令时调用的方法。这样就需要在ICommand文件加下添加一个QueryUserCommand类,用于自定义命令,通过委托执行ViewModel中的SearchUserByUserName方法

        ///<summary>

        ///定义Action委托

        ///</summary>

        privateAction _Handler;

 

        publicQueryUserCommand(Action Handler)

        {

            this._Handler =Handler;//委托方法

        }

 

        ///<summary>

        ///当出现影响是否应执行该命令的更改时发生。

        ///</summary>

        publiceventEventHandlerCanExecuteChanged;

 

        ///<summary>

        ///定义用于确定此命令是否可以在其当前状态下执行的方法。

        ///</summary>

        ///<paramname="para"></param>

        ///<returns></returns>

        publicboolCanExecute(object para)

        {

            if (para != null)

            {

                CanExecuteChanged(para, newEventArgs());

            }

            returntrue;

        }

 

        ///<summary>

        ///定义在调用此命令时调用的方法。

        ///</summary>

        ///<paramname="para"></param>

        publicvoid Execute(object para)

        {

            _Handler(); //调用委托

        }

所以在QueryUserViewModel类中,还需要添加一个按钮的Command属性,

        ///<summary>

        ///查询命令

        ///</summary>

        publicICommand SearchCmd

        {

            get { return_SearchCmd; }

             }

以及属性执行的方法,和在构造函数关联委托执行的方法_SearchCmd = newQueryUserCommand(SearchUserByUserName);

Model层

接下来就可以通过调用Model的数据来数据和命令的关联了,Model层主要是通过对Web项目中数据的提取来获得数据,而在UserInfoWebService.asmx类中提供了一个web方法GetUserByName,可以通过此方法的相应事件在Model层实现调用,所以接下来在Model层中新建一个UserHandle类,一下函数实现对Web方法的调用:

        ///<summary>

        ///获取用户

        ///</summary>

        ///<paramname="userName"></param>

        publicvoidgetUserList(string userName)

        {

            userName = "%" + userName+ "%";

            string sqlStr = "select* from t_user where user_name like '" + userName + "'";

            UserInfoWebServiceSoapClientuserInfoClient = newUserInfoWebServiceSoapClient();

           userInfoClient.GetUserByNameCompleted += newEventHandler<GetUserByNameCompletedEventArgs>(userInfoClient_GetUserByNameCompleted);

           userInfoClient.GetUserByNameAsync(sqlStr);       

        }

可以看到,编译器自动给我们的Web方法添加了一耳光Completed结尾的事件,和一个执行函数以Async结尾,我们在这里需要为Completed事件添加一个函数,用来传递执行查询完成后数据的回传到客户端,名字为userInfoClient_GetUserByNameCompleted的函数,为了实现自动回传到ViewModel层,需要定义一个委托和相应的事件,

        ///<summary>

        ///完成获取用户

        ///</summary>

        ///<paramname="sender"></param>

        ///<paramname="e"></param>

        publicvoiduserInfoClient_GetUserByNameCompleted(object sender, GetUserByNameCompletedEventArgs e)

        {

            userList = e.Result;

            getUserListEvent(userList);//触发事件,传递获取到的用户

        }

        ///<summary>

        ///定义处理获取用户完成后的委托

        ///</summary>

        ///<paramname="userList"></param>

        publicdelegatevoidGetUserListEventHandle(ObservableCollection<User>userList);

        ///<summary>

        ///将委托和事件关联

        ///</summary>

        publiceventGetUserListEventHandlegetUserListEvent;

然后再ViewModel层的QueryUserViewModel类中的构造函数中添加相应的事件执行函数实现数据的收和反馈到界面:

userHandle.getUserListEvent += new UserHandle.GetUserListEventHandle(SetResult);

这样整个机制就大体完成了,接下来就是界面的代码:

<UserControl

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

    xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" x:Class="MVVMDemo.View.QueryUser"

    xmlns:vm="clr-namespace:MVVMDemo.ViewModel"

    mc:Ignorable="d"

    d:DesignHeight="300" d:DesignWidth="400">

 

    <UserControl.Resources>

        <vm:QueryUserViewModel x:Key="QUVM"/>

    </UserControl.Resources>

   

    <Grid x:Name="LayoutRoot"Background="White" DataContext="{StaticResource QUVM}" >

        <Button Content="查询"HorizontalAlignment="Left" Margin="191,36,0,0"VerticalAlignment="Top" Width="75" Command="{Binding SearchCmd}"/>

        <TextBox HorizontalAlignment="Left" Height="23" Margin="37,35,0,0" TextWrapping="Wrap" Text="{Binding SearchText, Mode=TwoWay}" VerticalAlignment="Top" Width="120"/>

 

        <sdk:DataGrid HorizontalAlignment="Left" ItemsSource="{Binding ResultText}" Height="108" Margin="37,97,0,0"VerticalAlignment="Top" Width="229" AutoGenerateColumns="False"AlternatingRowBackground="BlanchedAlmond">

            <sdk:DataGrid.Columns>

                <sdk:DataGridTextColumn Header="ID" Binding="{Binding ID}" Width="50" ></sdk:DataGridTextColumn>

                <sdk:DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="100"></sdk:DataGridTextColumn>

            </sdk:DataGrid.Columns>

        </sdk:DataGrid>

    </Grid>

</UserControl>

运行界面:

个人观点

自认为对此开发模式最好部分就是通过绑定实现了前台和后台的分离,在实现上多采用事件和委托机制,对于不理解事件和委托的同学来说会理解整个架构的流程会比较困难,个人觉得整个系统框架写得比较复杂,其中还有很多不规范和不完善以及冗余的地方以后完善。


  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值