其实写这篇博文的时候我是拒绝的,因为这牵扯到一个高大上的东西——"框架"。一说起这个东西,很多朋友就感觉有点蒙了,尤其是编程新手。因为它不像在代码里面定义一个变量那么显而易见,它是需要在你的整个程序架构上体现出来的,并且对于框架来说,并没有什么固定的代码格式,你可以这样写,当然也可以那样写。只要最终可以达到同样的效果,各个模块之间能够体现这种框架的思想就OK。所以当你都是用MVVM框得到两份架写的相同需求的Demo看时,发现里面的很多代码都不一样,请不要惊讶,因为你正在接触一个很抽象的东西,这种东西有的时候还真得你需要自己挖空心思去琢磨一下,光靠别人给你讲还是不行的!

--------------------------------切入正题--------------------------------

在进行搭建自己的MVVM框架的时候你需要提起掌握一下知识(至少要熟悉,如果未达标,建议先自行脑补一下,我可能不会做到面面俱到):

1、熟练掌握数据绑定;

2、熟练使用委托;

3、对MVVM框架有一定的了解;

--------------------------------在你决定继续要往下看的时候我会默认你已经对上述知识有所了解------------------------------

一:为页面绑定数据

按照规范的MVVM框架来说,一个项目中至少要有要有三个文件夹:View、ViewModel、Model;这三个文件夹分别对应该框架的三个组成部分,这一点没什么好说的。针对Model中的一些属性而言,如果想具有属性通知的功能的话就需要继承INotifyPropertyChanged接口,并需要自定义一个函数用于触发对应的PropertyChanged事件,一般情况下我们都会把这一部分封装到一个类中,供其它类来继承它。这样就避免出现代码冗余的问题。示例代码如下所示:

                public
class ObservableObject : INotifyPropertyChanged { public
event PropertyChangedEventHandler PropertyChanged; public
void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } }

接下来,我们就需要让对应的Model类来继承我们写的这个具有属性通知的类,示例代码如下所示:

                public
class User:ObservableObject { private
string _name; public
string Name { get { return _name; } set { _name = value; RaisePropertyChanged("Name"); } } private
int _age; public
int Age { get { return _age; } set { _age = value; RaisePropertyChanged("Age"); } } public User(string name,int age) { this.Name = name; this.Age = age; } ///
<summary> /// 给ViewModel提供一个获取Model中集合对象的接口 ///
</summary> ///
<returns></returns> public
static ObservableCollection<User> GetUsers() { return
new ObservableCollection<User>() { new User("hippieZhou",), new User("小明",), new User("老王",) }; } }

Model已经搭建完成,接着我们就开始搭建ViewModel,示例代码如下所示:

                public
class MainPageViewModel : ObservableObject { private ObservableCollection<User> _users; public ObservableCollection<User> Users { get { return _users; } set { _users = value; RaisePropertyChanged("Users"); } } public MainPageViewModel() { this.Users = User.GetUsers(); } }

OK,是不是很简单,接下来就是View中的数据绑定,示例代码如下所示:

                <Page.DataContext>

                <vm:MainPageViewModel/>

                </Page.DataContext>

                <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

                <ListView x:Name="lv" Grid.Row="" ItemsSource="{Binding Users}">

                <ListView.ItemTemplate>

                <DataTemplate>

                <ListViewItem>

                <Grid>

                <Grid.ColumnDefinitions>

                <ColumnDefinition Width=""/>

                <ColumnDefinition Width="*"/>

                </Grid.ColumnDefinitions>

                <TextBlock Text="{Binding Name}" Grid.Column=""/>

                <TextBlock Text="{Binding Age}" Grid.Column=""/>

                </Grid>

                </ListViewItem>

                </DataTemplate>

                </ListView.ItemTemplate>

                </ListView>

                </Grid>

二:为页面绑定Command

写到这算是完成了1/3,界面是展示了数据,但是我们不能对它进行任何的操作,因此我们还需要让数据能够动态的增加和删除,接下来我们需要使用有关Command的相关知识了,首先,Command属于ButtonBase的一个字段,如果我们想为对应的Command进行绑定的话,那就需要这个绑定源对应的委托继承自ICommand接口,需要重写ICommand对应的两个接口函数。其次,由于ICommand提供了两个接口函数CanExecute和Execute,因此当CanExecute为false时候Execute是不被执行,此时绑定的Command是失效的,那么对应的控件应该自动处于禁用状态的,但是在WindowsStore类型的应用不再像WPF那样具有CommandManager的功能,不能自动触发CanExecuteChanged,这样就导致控件的颜色仍然不是禁用状态的颜色(尽管没有执行对应的函数),因此我们需要手动触发这个事件,来保证前台的控件的显示状态在指定的条件下发生改变。

在此,我们一般会采取封装的思想来处理这种情况,因此我选择封装一个类DelegateCommand,继承至ICommand,示例代码如下所示:

                public sealed class DelegateCommand : ICommand

                    {

                        public event EventHandler CanExecuteChanged;

          /// <summary>

                        /// 需要手动触发属性改变事件

          /// </summary>

                        public void RaiseCanExecuteChanged()

                        {

                            if (CanExecuteChanged != null)

                            {

                                CanExecuteChanged(this, EventArgs.Empty);

                            }

                        }

         /// <summary>

                        /// 决定当前绑定的Command能否被执行

                        /// true:可以被执行

                        /// false:不能被执行

         /// </summary>

         /// <param name="parameter">不是必须的,可以依据情况来决定,或者重写一个对应的无参函数</param>

         /// <returns></returns>

                        public bool CanExecute(object parameter)

                        {

                            return this.MyCanExecute == null ? true : this.MyCanExecute(parameter);

                        }

         /// <summary>

                        /// 用于执行对应的命令,只有在CanExecute可以返回true的情况下才可以被执行

         /// </summary>

         /// <param name="parameter"></param>

                        public void Execute(object parameter)

                        {

                            try

                            {

                                this.MyExecute(parameter);

                            }

                            catch (Exception ex)

                            {

                                #if DEBUG

                                Debug.WriteLine(ex.Message);

                                #endif

                            }

                        }

         /// <summary>

                        /// 

         /// </summary>

         public Action<Object> MyExecute { get; set; }

         public Func<Object, bool> MyCanExecute { get; set; }

         /// <summary>

                        /// 构造函数,用于初始化

         /// </summary>

         /// <param name="execute"></param>

         /// <param name="canExecute"></param>

         public DelegateCommand(Action<Object> execute, Func<Object, bool> canExecute)

                        {

                            this.MyExecute = execute;

                            this.MyCanExecute = canExecute;

                        }

     }

然后在我们的ViewModel中创建对应的Command就可以了,我们可以将ViewModel改造成下面这个样子:

                public class MainPageViewModel : ObservableObject

                    {

          private ObservableCollection<User> _users;

          public ObservableCollection<User> Users

                        {

                            get { return _users; }

                            set

                            {

                                _users = value;

                                RaisePropertyChanged("Users");

                            }

                        }

                        private DelegateCommand _addCommand;

         /// <summary>

                        /// 当当前集合项的个数小于5时允许用户继续添加,否则就不允许用户添加

         /// </summary>

                        public DelegateCommand AddCommand

                        {

                            get

                            {

                                return _addCommand ?? (_addCommand = new DelegateCommand

                                  ((Object obj) =>

                                  {

                                      //添加一条记录

                                      this.Users.Add(new User(DateTime.Now.ToString(),DateTime.Now.Hour));

                                      //手动触发CanExecuteChanged事件来改变对应控件的显示状态

                                      this._addCommand.RaiseCanExecuteChanged();

                                      this._delCommand.RaiseCanExecuteChanged();

                                  },

                   (Object obj) => this.Users.Count < ));

                            }

                        }

                        /// <summary>

                        /// 当当前集合项的个数大于1时允许用户继续删除,否则就不允许用户删除

         /// </summary>

                        private DelegateCommand _delCommand;

                        public DelegateCommand DelCommand

                        {

                            get

                            {

                                return _delCommand ?? (_delCommand =

                                  new DelegateCommand((Object obj) =>

                                  {

                                      //删除一条记录

                                      this.Users.RemoveAt();

                                      //手动触发CanExecuteChanged事件来改变对应控件的显示状态

                                      this._addCommand.RaiseCanExecuteChanged();

                                      this._delCommand.RaiseCanExecuteChanged();

                                  },

                                  (Object obj) => this.Users.Count > ));

                            }

                        }

                        public MainPageViewModel()

                        {

                            this.Users = User.GetUsers();

                        }

     }

并将对应的View改造成下面这个样子:

                <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

                <Grid.RowDefinitions>

                <RowDefinition Height="Auto"/>

                <RowDefinition Height="*"/>

                </Grid.RowDefinitions>

                <StackPanel Grid.Row="" HorizontalAlignment="Center" Width="">

                <Button Content="Add" Command="{Binding AddCommand}" HorizontalAlignment="Stretch" Margin=""/>

                <Button Content="Del" Command="{Binding DelCommand}" HorizontalAlignment="Stretch" Margin=""/>

                </StackPanel>

                <ListView x:Name="lv" Grid.Row="" ItemsSource="{Binding Users}">

                <ListView.ItemTemplate>

                <DataTemplate>

                <ListViewItem>

                <Grid>

                <Grid.ColumnDefinitions>

                <ColumnDefinition Width=""/>

                <ColumnDefinition Width="*"/>

                </Grid.ColumnDefinitions>

                <TextBlock Text="{Binding Name}" Grid.Column=""/>

                <TextBlock Text="{Binding Age}" Grid.Column=""/>

                </Grid>

                </ListViewItem>

                </DataTemplate>

                </ListView.ItemTemplate>

                </ListView>

                </Grid>
                    

这个地方提醒新手朋友要注意的一个问题,如果你希望你的控件在指定条件下显示的状态不一样就需要手动触发CanExecuteChanged事件。

推荐链接:Implement ICommand.CanExecuteChanged in portable class library (PCL)

Re-enabling the CommandManager feature with RelayCommand in MVVM Light V5

三:Event To Command

接下来算是一个重点内容吧,如何将一个事件绑定到Command上? 这个问题很现实,并不是所有的控件都有Command属性,当一个控件只有Event而没有Command我们该怎么办?

我们现在需求是选中一项后弹出一个对话框,显示你选中项的相关信息(通过EventToCommand来实现)

这个微软为我们提供了一个解决方案,如果你安装了Blend工具,你可以把 目录C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1\ExtensionSDKs\BehaviorsXamlSDKManaged\12.0打开,你会发现有两个动态库很有用:Microsoft.Xaml.Interactions.dll和Microsoft.Xaml.Interactivity.dll;没错,就是它俩可以达成你的心愿,迅速将这两个动态库加入到工程中,然后你可以在你的XAML页面中进行绑定的,我把完整的代码罗列出来供大家参考:

 1
<Page 2
x:Class="MVVM.MainPage" 3
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5
xmlns:local="using:MVVM" 6
xmlns:vm="using:MVVM.ViewModel" 7
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 8
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 9
mc:Ignorable="d" 10 11
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity" 12
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"> 13
<!-- 14
C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1\ExtensionSDKs\BehaviorsXamlSDKManaged\12.0 15
--> 16
<Page.DataContext> 17
<vm:MainPageViewModel/> 18
</Page.DataContext> 19 20
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> 21
<Grid.RowDefinitions> 22
<RowDefinition Height="Auto"/> 23
<RowDefinition Height="*"/> 24
</Grid.RowDefinitions> 25
<StackPanel Grid.Row="0" HorizontalAlignment="Center" Width="100"> 26
<Button Content="Add" Command="{Binding AddCommand}" HorizontalAlignment="Stretch" Margin="6"/> 27
<Button Content="Del" Command="{Binding DelCommand}" HorizontalAlignment="Stretch" Margin="6"/> 28
</StackPanel> 29
<ListView x:Name="lv" Grid.Row="1" ItemsSource="{Binding Users}"> 30
<ListView.ItemTemplate> 31
<DataTemplate> 32
<ListViewItem> 33
<Grid> 34
<Grid.ColumnDefinitions> 35
<ColumnDefinition Width="200"/> 36
<ColumnDefinition Width="*"/> 37
</Grid.ColumnDefinitions> 38
<TextBlock Text="{Binding Name}" Grid.Column="0"/> 39
<TextBlock Text="{Binding Age}" Grid.Column="1"/> 40
</Grid> 41
</ListViewItem> 42
</DataTemplate> 43
</ListView.ItemTemplate> 44
<Interactivity:Interaction.Behaviors> 45
<Core:EventTriggerBehavior EventName="SelectionChanged"> 46
<Core:InvokeCommandAction Command="{Binding ShowDialog}" CommandParameter="{Binding ElementName=lv,Path=SelectedItem,Converter={StaticResource converter}}"/> 47
</Core:EventTriggerBehavior> 48
</Interactivity:Interaction.Behaviors> 49
</ListView> 50
</Grid> 51
</Page>

这里面用到了一个非MVVM的知识:值转换器(只是为了弹出框能够显示我想要的数据而已,没什么其他的作用),示例代码如下所示:

                ///
<summary> /// 定义一个值转换器,用于将绑定的数据格式化为指定的格式 ///
</summary> public
class ItemConverter : IValueConverter { public
object Convert(object value, Type targetType, object parameter, string language) { User user = value as User; if (user != null) { return user.Name; } else { return
"you have not select!"; } } public
object ConvertBack(object value, Type targetType, object parameter, string language) { throw
new NotImplementedException(); } }

然后对应的命令写法和之前的是一样的,如下所示:

                private DelegateCommand _showDialog;

                public DelegateCommand ShowDialog

                        {

                get

                            {

                return _showDialog ?? (_showDialog= new DelegateCommand(

                async (Object obj) =>

                                    {

                await
new Windows.UI.Popups.MessageDialog(obj.ToString()).ShowAsync(); }, (Object obj) => true)); } }

写到这,我们的MVVM框架已经搭建的差不多了,还算满意,我运行的效果是这样的(你的也是这样的吗?):

我不知道我用我的这种方式理解和设计应用程序的MVVM框架在诸位眼中是否规范,合法,还请高手不吝赐教呀:)!!!!

四:写在最后

 如果你能够熟练理解并能够将MVVM运用到自己的项目中,并计划使用第三方MVVM框架的话,我建议你使用MVVMLight,简单易用上手快,并且它已经支持UWP的项目模板了。我真的很佩服作者(官网地址)的编码能力,我的很多思路都是从他的博客中获得灵感的,希望你也是如此!

GitHub  :https://github.com/hippieZhou/MVVM

在UWP中实现自己的MVVM设计模式的更多相关文章

  1. MVVM设计模式和WPF中的实现(四)事件绑定

    MVVM设计模式和在WPF中的实现(四) 事件绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  2. 浅谈 MVVM 设计模式在 Unity3D 中的设计与实施

    初识 MVVM 谈起 MVVM 设计模式,可能第一映像你会想到 WPF/Sliverlight,他们提供了的数据绑定(Data Binding),命令(Command)等功能,这让 MVVM 模式得到 ...

  3. UWP中新加的数据绑定方式x:Bind分析总结

    UWP中新加的数据绑定方式x:Bind分析总结 0x00 UWP中的x:Bind 由之前有过WPF开发经验,所以在学习UWP的时候直接省略了XAML.数据绑定等几个看着十分眼熟的主题.学习过程中倒是也 ...

  4. 设计模式笔记之三:Android DataBinding库(MVVM设计模式)

    本博客转自郭霖公众号:http://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650236908&idx=1&sn=9e53 ...

  5. 通过TodoList案例对比Vue.js的MVVM设计模式与JQuery的MVP设计模式

    Vue MVVM设计模式: 在使用vue进行编程时,不会再涉及到DOM的操作,取而代之的是修改数据层,当把数据进行变更的时候,vue之中它的底层会自动的根据数据的不同帮助我们去重新渲染页面. 编码时不 ...

  6. 使用MVVM设计模式构建WPF应用程序

    使用MVVM设计模式构建WPF应用程序 本文是翻译大牛Josh Smith的文章,WPF Apps With The Model-View-ViewModel Design Pattern,译者水平有 ...

  7. Android DataBinding库(MVVM设计模式)

    什么是MVVM 说到DataBinding,就有必要先提起MVVM设计模式.Model–View–ViewModel(MVVM) 是一个软件架构设计模式,相比MVVM,大家对MVC或MVP可能会更加熟 ...

  8. (一)mvc与mvvm设计模式

    前沿:了解设计模式对我们而言,具有很大意义,对语言没有限制,它适用于任何语言,是一种变成思想.设计模式最初有四人帮提出,有兴趣的同学可以去了解下,今天给大家主要分析mvc与mvvm设计模式 一.mvc ...

  9. WPF系列教程——(二)使用Prism实现MVVM设计模式 - 简书

    原文:WPF系列教程--(二)使用Prism实现MVVM设计模式 - 简书 本文假设你已经知道MVVM设计模式是什么,所以直接进入正题,今天我们就用Prism来实现WPF的MVVM设计模式,百度上关于 ...

随机推荐

  1. android-glsurfaceview Activity框架程序

    两个基本的类让我们使用OpenGL ES API来创建和操纵图形:GLSurfaceView和 GLSurfaceView.Renderer. 1. GLSurfaceView: 这是一个视图类,你可 ...

  2. HTML <frameset>

    好久不用 <frameset>确实有点手生了,直接上代码看效果吧,简单易懂 <!DOCTYPE html> <html> <head> <meta ...

  3. Pycharm的激活码,亲测可用(20181223)

    K03CHKJCFT-eyJsaWNlbnNlSWQiOiJLMDNDSEtKQ0ZUIiwibGljZW5zZWVOYW1lIjoibnNzIDEwMDEiLCJhc3NpZ25lZU5hbWUiO ...

  4. [UWP]在UWP平台中使用Lottie动画

    最近QQ影音久违的更新了,因为记得QQ影音之前体验还算不错(FFmepg的事另说),我也第一时间去官网下载体验了一下,结果发现一些有趣的事情. 是的,你没看错,QQ影音主界面上这个动画效果是使用Lot ...

  5. 2018/7/26号碰到了个奇怪的问题(http有问题,但是ftp没毛病)

    过程大概是这样的 本来测试服务器中发ajax没问题,突然暴毙了,服务器又通过ajax发了另外一个请求(与之前不一样). nginx  reload 没毛病  ,ftp 也使用正常. 出了什么问题呢?  ...

  6. Android开发工程师文集-1 小时学会SQLite

    前言 大家好,给大家带来Android开发工程师文集-1 小时学会SQLite的概述,希望你们喜欢 内容 什么是Sqlite: 效率高,开源,小型,程序驱动,支持事务操作,无数据类型,可嵌入的关系型数 ...

  7. Android精通教程V

    前言 大家好,给大家带来Android精通教程V的概述,希望你们喜欢 前言 如果你想学习Android开发,那你就要了解Java编程,这是基础,也是重点,如果没学Java语法就先学习,再来学Andro ...

  8. spring boot、cloud v2.1.0.RELEASE 使用及技术整理

    2018年10月30日 springboot v2.1.0.RELEASE 发布: https://github.com/spring-projects/spring-boot/releases/ta ...

  9. JS 数据类型和数据分析

    栈区:(stack)-由编译器自动分配释放,存放函数的参数值,局部变量的值等. 特点是存放体积小,使用频率高的数据.可以类比内存. 堆区:(heap)-一般由程序员分配释放,若开发者不释放,程序结束时 ...

  10. 从零开始学 Spring Boot

    1.下载 spring-tool-suite https://spring.io/tools3/sts/legacy 2.解压运行 sts-bundle\sts-3.9.7.RELEASE\STS.e ...