WindowsPhone 8开发日志:MVC设计模式入门、进阶

移动开发
博文中作者为大家呈现的是一个简单的MVC模式设计的例子,我们从中看出,这样的设计手法,可以让界面逻辑跟功能逻辑分开设计提高效率,如果程序以后要改进,从预览窗口我们也更容易知道自己需要更改哪些东西。

由于最近在等待新工作的入职,闲来无事还是完善一下我的WP8开发的博客吧。经过许久回头看一下我那半成品的WP应用,内牛满面,之前Google了N久的东西,居然也不记得了,真是不得不认老啊。

这段时间接触了一下B/S的东西,觉得那东西真不是人用的,一旦工程量上去,后续开发很大一部分时间都是在Debug中渡过,很是纠结。现在业内普遍的对移动开发的看法是——web应用是种趋势,可是个人觉得除非现阶段的移动网络环境有质的改变,否则用户体验会很有问题。然后简单的玩了一下node.js,觉得后台其实也可以不用那么复杂,想想,还是老老实实的写回自己的客户端应用好了。

废话就不多说了,先简单的介绍一下本篇的目的吧。MVC这个东西,现下看起来很流行,Model-View-Control,其实我个人并不清楚其概念所谓,其实说白了就是当年的“所见即所得”,一个意思。之前接触了一下WPF,就是给这个概念给搞死,但不管怎么样,xaml可能是MS的开发趋势,MCV又是其中一个极重要的概念,不入门不行。

MVC的更多的概念性解析请诸位百度或google吧,它的核心是数据,MVC是一种展现数据呈现方式的一种开发手段,虽然比纯代码性的呈现方式多了一些额外的工作,可是,必须要记住,现在做多一些是为了将来少做一些,其优势还是相当可观的。

一、构造数据类DataItem

WinForm的项目里,我们想在程序设计的阶段预览ListBox是怎么显示数据,那几乎是不可能的,除非我将程序跑起来,在人类审美观崛起的现在,代码与界面分离设计可以有效的通过分工实现人力资源的最大化的利用,而这也是MVC的主要任务。

写WinForm时,我会这么设计一个简单的数据结构体:

1
2
3
4
5
6
struct TestDataItem
{
    int Index;
    string Name;
    boo Flag;
}

可使用MVC的方式,编译器没办法识别这样的定义,所以必须将这简单的结构体扩展成一个特定的类:

//TestDataItem.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MvcTest
{
    /// <summary>
    /// 数据集的Item类,继承INotifyPropertyChanged,INotifyPropertyChanging接口
    /// </summary>
    public partial class TestDataItem : INotifyPropertyChanged, INotifyPropertyChanging
    {
        /// <summary>
        /// 属性更改的事件参数
        /// </summary>
        private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
        /// <summary>
        /// 数据属性变化的事件回调
        /// </summary>
        public event PropertyChangingEventHandler PropertyChanging;
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        /// Item的成员属性
        /// </summary>
        private int _index;
        private string _name;
        private bool _flag;
        /// 属性更改的响应函数
        #region
        partial void OnIndexChanging(int value);
        partial void OnIndexChanged();
        partial void OnNameChanging(string value);
        partial void OnNameChanged();
        partial void OnFlagChanging(bool value);
        partial void OnFlagChanged();
        #endregion
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
        /// 类接口的实现
        #region
        /// <summary>
        /// INotifyPropertyChanging接口的实现函数
        /// </summary>
        protected virtual void SendPropertyChanging()
        {
            if ((this.PropertyChanging != null))
            {
                this.PropertyChanging(this, emptyChangingEventArgs);
            }
        }
        /// <summary>
        /// INotifyPropertyChanged接口的实现
        /// </summary>
        /// <param name="propertyName"></param>
        protected virtual void SendPropertyChanged(String propertyName)
        {
            if ((this.PropertyChanged != null))
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion
        /// 数据的公共属性
        #region
        public int Index
        {
            get
            {
                return this._index;
            }
            set
            {
                if(this._index != value)
                {
                    this.OnIndexChanging(value);
                    this.SendPropertyChanging();
                    this._index = value;
                    this.SendPropertyChanged("Index");
                    this.OnIndexChanged();
                }
            }
        }
        public string Name
        {
            get
            {
                return this._name;
            }
            set
            {
                if(this._name != value)
                {
                    this.OnNameChanging(value);
                    this.SendPropertyChanging();
                    this._name = value;
                    this.SendPropertyChanged("Name");
                    this.OnNameChanged();
                }
            }
        }
        public bool Flag
        {
            get
            {
                return this._flag;
            }
            set
            {
                if (this._flag != value)
                {
                    this.OnFlagChanging(value);
                    this.SendPropertyChanging();
                    this._flag = value;
                    this.SendPropertyChanged("Flag");
                    this.OnFlagChanged();
                }
            }
        }
        #endregion
    }
}

此类的核心是INotifyPropertyChanged,INotifyPropertyChanging这两个接口(不懂请百度呗,我也懂不了多少)。因为数据的窗口(像Grid或LongListSelector)要感知每一条TestDataItem的属性变化,然后这个类里面就存在各种的回调或事件啦。

二、构造数据集TestDataItemCollection

这步理解就简单一点了,数据容器呈现的是整体的数据集合,这个集合也需要一个特殊的类,否则编译器不知道怎么去解析(我只能这么理解),有玩过数据绑定的童鞋应该能弄懂这点。

当然,TestDataItem的集合类也需要实现一个特殊的接口INotifyPropertyChanged——没错,跟上面TestDataItem实现的接口一样,可以这么理解,这个接口是MVC的核心所在。

//TestDataItemCollection.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MvcTest
{
    /// <summary>
    /// 数据集的类
    /// </summary>
    public class TestDataCollection : INotifyPropertyChanged
    {
        /// <summary>
        /// 数据集合
        /// </summary>
        public ObservableCollection<TestDataItem> TestDataItems { get; private set; }
        /// <summary>
        /// 属性更改事件回调
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        /// 构造函数
        /// </summary>
        public TestDataCollection()
        {
            this.TestDataItems = new ObservableCollection<TestDataItem>();
        }
        /// <summary>
        /// INotifyPropertyChanged接口的实现
        /// </summary>
        private void NotifyPropertyChanged(String propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (null != handler)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

三、构造数据集示例ViewModel

这一步设计要呈现的数据例子,其用到的xmal的文件,可是在Visual Studio新建项的时候,并没有这一选项,可是我们会发现WP的页面文件格式就xaml的,所以不妨新建一个页面,将其重命名为TestDataViewModel.xaml,然后,删掉页面正面的cs等文件,将下面这段xaml代码粘上去——当然,你直接手敲上去也是没有问题的。

//TestDataViewModel.xaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 这对应的是数据集的类名,表示一组数据-->
<vm:TestDataCollection
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vm="clr-namespace:MvcTest">
    <!--clr-namespace:MvcTest这是数据集的命名空间,带文件夹的要注意啦-->
    <!--简单的数据集例子-->
    <vm:TestDataCollection.TestDataItems>
        <vm:TestDataItem Index="0" Name="name1" Flag="False"></vm:TestDataItem>
        <vm:TestDataItem Index="1" Name="name2" Flag="True"></vm:TestDataItem>
        <vm:TestDataItem Index="2" Name="name3" Flag="True"></vm:TestDataItem>
        <vm:TestDataItem Index="3" Name="name4" Flag="False"></vm:TestDataItem>
        <vm:TestDataItem Index="4" Name="name5" Flag="True"></vm:TestDataItem>
        <vm:TestDataItem Index="5" Name="name6" Flag="True"></vm:TestDataItem>
    </vm:TestDataCollection.TestDataItems>
</vm:TestDataCollection>

注意看xaml的注释内容,但看起来不难,难的一步是如何呈现上面的数据集于PhonePage上。

四、呈现ViewModel数据集

首先,我们要选一个数据容器,个人比较喜欢LongListSelector,但似乎没有其分的选择,将上面的数据集例子绑定上去;然后,设计一下单条数据呈现的样式,要用到DataItemTemplate,这也许我将来会详讲。就这两条,直接上代码:

//MainPage.xaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<phone:PhoneApplicationPage
    x:Class="MvcTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DataContext="{d:DesignData TestDataViewModel.xaml}"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">
                                                                                                                                                             
    <!--单条数据显示的样式-->
    <phone:PhoneApplicationPage.Resources>
        <DataTemplate x:Key="TestDataItemTemplate">
            <StackPanel Margin="0,0,0,0">
                <TextBlock Text="{Binding Index}"/>
                <TextBlock Text="{Binding Name}" />
                <!--TextBlock Text="{Binding Flag}" /-->
                <StackPanel Height="1" Background="AliceBlue"></StackPanel>
            </StackPanel>
        </DataTemplate>
    </phone:PhoneApplicationPage.Resources>
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="我的应用程序" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
            <TextBlock Text="页面名称" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <!--用一个LongListSelector来显示数据集-->
            <StackPanel>
                <phone:LongListSelector Margin="0,0,0,0" ItemTemplate="{StaticResource ResourceKey=TestDataItemTemplate}" ItemsSource="{Binding TestDataItems}"/>
            </StackPanel>
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

 

注意以上代码的两行注释跟第10行的内容,这是其关键所在,新建一个默认的WP工程,就多了总共三处地方,于是,你就能看到以下的效果啦:

wKiom1NFXd_g5H9vAAEtg6tneWY609.jpg

 

于是,通过MVC的设计手段,我们就能看到在WP设计页面上看到数据集合是如何呈现的啦。至于实际运行如何加载数据集就不须要多说吧?只要给LongListSelector的ItemSource属性赋值就好了,一句话的功夫。

小结:

以上的是一个简单的MVC模式设计的例子,其实这工作一个人做看不出什么优势,可是,我们从中看出,这样的设计手法,可以让界面逻辑跟功能逻辑分开设计提高效率,还有,如果程序以后要改进,从预览窗口我们也更容易知道自己需要更改哪些东西,总之,优势不少就是啦。

自己还有一个问题,如何在同一个页面呈现两个数据集呢?嗯,这是个问题。

进阶:

讲到在一个PhonePage里绑定一个数据集,用的是如下的方法:

1
d:DataContext="{d:DesignData TestDataViewModel.xaml}"

一个xmal文件表示一个数据集,在上面代码里的d:DataContenxt里研究了半天,实在没有办法让一个DataContenxt绑定第二个xaml的数据集,玩过Linq的人都知道,一个DataContenxt下面却可以定义多个集合的,接下来的思路就是怎么在一个xmal里加入两个数据集。

自己设计了两个除了类名外其他都一样的数据类(TestDataItem.cs及TestDataItem2.cs),如下图的文件目录所示,为了显示方便将其放到两个不同的文件夹里,这时就要注意namespace的问题啦。

wKioL1NGdpKwRsn5AAEpixsnML4213.jpg

 

上面的TestDataCollection.cs、TestDataCollection2、TestDataViewModel.xaml、TestDataViewModel2.xaml是个人作单独显示时用的,想将两个数据集放到同一个DataContext里不需要动用这几个文件。而TotalTestDataCollection.cs是关键的所在,在此定义了一个含有两个数据集合的大集合,其代码如下所示:

//TotalTestDataCollection.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using MvTest.TestDataViewModel1;
using MvTest.TestDataViewModel2;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MvcTest.TotalDataViewModel
{
    public class TotalTestDataCollection : INotifyPropertyChanged
    {
        /// <summary>
        /// 数据集合1
        /// </summary>
        public ObservableCollection<TestDataItem> TestDataItems1 { get; private set; }
        /// <summary>
        /// 数据集合2
        /// </summary>
        public ObservableCollection<TestDataItem2> TestDataItems2 { get; private set; }
        /// <summary>
        /// 属性更改事件回调
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        /// 构造函数
        /// </summary>
        public TotalTestDataCollection()
        {
            this.TestDataItems1 = new ObservableCollection<TestDataItem>();
            this.TestDataItems2 = new ObservableCollection<TestDataItem2>();
        }
        /// <summary>
        /// INotifyPropertyChanged接口的实现
        /// </summary>
        private void NotifyPropertyChanged(String propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (null != handler)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

如前一篇笔记强调的,要想让VS认出你的ViewModel,必须要继承INotifyPropertyChanged的接口。然后再新建一个xaml来设计两个数据集的例子(方法见上一篇博文吧),其代码如下所示:

//TotalDataViewModel.xaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 这对应的是数据集的类名,表示一组数据-->
<vm:TotalTestDataCollection
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vm="clr-namespace:MvcTest.TotalDataViewModel"
    xmlns:c1="clr-namespace:MvTest.TestDataViewModel1"
    xmlns:c2="clr-namespace:MvTest.TestDataViewModel2">
    <!--clr-namespace:MvcTest这是数据集的命名空间,带文件夹的要注意啦-->
                                                                                                                                                                                                           
    <vm:TotalTestDataCollection.TestDataItems1>
        <c1:TestDataItem Index="0" Name="name0" Flag="True"></c1:TestDataItem>
        <c1:TestDataItem Index="1" Name="name1" Flag="True"></c1:TestDataItem>
        <c1:TestDataItem Index="2" Name="name2" Flag="True"></c1:TestDataItem>
    </vm:TotalTestDataCollection.TestDataItems1>
    <vm:TotalTestDataCollection.TestDataItems2>
        <c2:TestDataItem2 Index="3" Name="name3" Flag="True"></c2:TestDataItem2>
        <c2:TestDataItem2 Index="4" Name="name4" Flag="True"></c2:TestDataItem2>
        <c2:TestDataItem2 Index="5" Name="name5" Flag="True"></c2:TestDataItem2>
    </vm:TotalTestDataCollection.TestDataItems2>
</vm:TotalTestDataCollection>

注意代码里的c1、c2的命名空间,否则会无法识别TestDataItem,当然,省事的方法就是将所有的文件放到一个目录下面,好吧,个人是有强迫症的。就这样将两个数据集拼在一个xaml里面了,下面就是在MainPage里呈现出这些数据,为此我设计了两个LongListSelector,分别显示不同的数据,为了区分起见,我设计了两个DataItemTemplate,如下面代码所示:

//MainPage.xaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<phone:PhoneApplicationPage
    x:Class="MvcTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DataContext="{d:DesignData TotalDataViewModel/TotalDataViewModel.xaml}"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">
                                                                                                                         
    <!--单条数据显示的样式-->
    <phone:PhoneApplicationPage.Resources>
        <DataTemplate x:Key="TestDataItemTemplate">
            <StackPanel Margin="0,0,0,0">
                <TextBlock Text="{Binding Index}"/>
                <TextBlock Text="{Binding Name}" />
                <!--TextBlock Text="{Binding Flag}" /-->
                <StackPanel Height="1" Background="AliceBlue"></StackPanel>
            </StackPanel>
        </DataTemplate>
        <DataTemplate x:Key="TestDataItemTemplate2">
            <StackPanel Margin="0,0,0,0">
                <TextBlock Foreground="Red" Text="{Binding Index}"/>
                <TextBlock Foreground="Yellow" Text="{Binding Name}" />
                <TextBlock Foreground="Green" Text="{Binding Flag}" />
                <StackPanel Height="1" Background="RoyalBlue"></StackPanel>
            </StackPanel>
        </DataTemplate>
                                                                                                                             
    </phone:PhoneApplicationPage.Resources>
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="我的应用程序" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
            <TextBlock Text="页面名称" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <!--用一个LongListSelector来显示数据集-->
            <StackPanel>
                <phone:LongListSelector Margin="0,0,0,0" ItemTemplate="{StaticResource ResourceKey=TestDataItemTemplate}" ItemsSource="{Binding TestDataItems1}"/>
                <phone:LongListSelector Margin="0,0,0,0" ItemTemplate="{StaticResource ResourceKey=TestDataItemTemplate2}" ItemsSource="{Binding TestDataItems2}"/>
            </StackPanel>
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

只要d:DataContext关联到TotalDataViewModel.xaml里,编译器倒可以自动感知内容,只要在LongListSelector里绑定好指定的数据集就可以啦,其显示效果如下图所示:

wKioL1NGe92AAWhWAAFhfx3b81s468.jpg

 

总结

(1)先设计好你要显示的数据类(TestDataItem.cs);

(2)设计好数据集的集合类(TotalTestDataCollection.cs);

(3)设计好数据集的内容(TestDataViewModel.xaml);

(4)在PhonePage里呈现你的数据。

步骤不多,四步而已,虽说如此,但要弄出这个效果可是花了我一个下午的时间,我真是Low爆啦。

原文及源码下载:http://joeyliu.blog.51cto.com/3647812/1393105

责任编辑:闫佳明 来源: 51CTO博客
相关推荐

2013-04-09 12:41:09

WindowsPhon

2012-05-09 10:09:18

JavaMEJava

2013-04-09 11:36:38

WindowsPhon

2013-04-16 16:23:25

WindowsPhonWindowsPhon

2009-07-10 16:14:29

MVC设计模式Swing

2011-06-02 18:02:50

iPhone MVC

2013-04-09 11:26:55

WindowsPhon

2009-06-11 10:37:58

netbeans spMVC基础

2013-11-26 15:44:25

2009-07-30 13:45:40

ASP.NET开发模式MVC模式

2011-04-22 09:26:57

MVC设计

2012-12-18 10:03:22

JavaScriptWebJS

2020-05-14 14:48:15

架构模式单库

2011-02-28 13:34:51

SpringMVC

2009-06-11 17:24:46

J2EE的MVC体系结J2EE设计模式

2021-11-04 08:00:04

模式开发设计

2013-06-17 15:26:35

WP开发WindowsPhonWindows8兼容性

2014-03-12 10:11:57

Python设计模式

2017-02-13 13:10:35

JavaScript设计模式

2020-07-07 10:34:57

设计师工作互联网设计
点赞
收藏

51CTO技术栈公众号