Windows Store apps开发[7]视图模型与数据绑定

注:本系列学习帖子我在DevDiv.com移动开发社区原创首发

        转载请注明出处:BeyondVincent(破船)@DevDiv.com

如果你有什么问题也可以前往交流

下面是首发地址:

[DevDiv原创]Windows 8 Metro App开发Step by Step---(13个学习帖子)



    在程序中使用视图模型(ViewModel),可以带来很多好处,在开发中值得采纳,视图模型的使用对Metro App开发非常有帮助,通过学习MVC和MVVC你可以了解到试图模型。在这里我将介绍如何定义和使用试图模型,其中包括数据的绑定等内容。

    本次程序我以DevDiv论坛的板块为参考数据,写一个视图模型,有一个主画面,画面的左边有一个列表,列表中的数据来自试图模型中板块项列表,同时右边会显示选中板块的介绍。下面是程序的运行图,我们可以先来看看最终效果:



    本次学习内容主要包括一下几部分:

1、视图模型(ViewModel)的创建

2、添加页面

3、编写页面相关代码

4、添加资源字典

5、编写XAML

6、程序运行效果图与Demo程序


更多内容请查看下面的帖子


Windows 8 Metro App开发Step by Step

1、视图模型(ViewModel)的创建

   对于创建具有可持续性和和可维护性的应用程序,试图模型是必须具有的一个基础部分,它可以让我将应用程序数据与呈现数据给用户的方法相分离。如果不使用视图模型的话,你会发现你的程序越来越难以维护和开发。

   在本小节,我首先使用Blank App模版创建了一个名为DevDiv_DataBinding的工程。并在其中创建一个Data目录,然后再Data目录下创建两个新的类文件。分别是ForumItem.cs和ViewModel.cs文件。


ForumItem类的代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;

namespace DevDiv_DataBinding.Data
{
    class ForumItem : INotifyPropertyChanged
    {
        private string name, link, info;// 论坛板块的名称,链接和描述信息
        private int topicCount; // 主题数
        public string Name
        {
            get { return name; }
            set { name = value; NotifyPropertyChanged("Name"); }
        }
        public string Link
        {
            get { return link; }
            set { link = value; NotifyPropertyChanged("Link"); }
        }
        public string Info
        {
            get { return info; }
            set { info = value; NotifyPropertyChanged("info"); }
        }
        public int TopicCount
        {
            get { return topicCount; }
            set { topicCount = value; NotifyPropertyChanged("TopicCount"); }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
            }
        }
    }
}


ForumItem类是论坛中每个板块的信息:板块名称、链 接、描述信息和主题数。
该类中最重要的部分是实现了INotifyPropertyChanged接口,即让该类的属性可观测,Metro UI控件一个很好的特性是它们支持数据绑定,这意味着当他们所显示的可观测数据发生变化时控件将会自动更新。所以在这里让 ForumItem支持可观测,那么方便UI上自动的进行更新。

ViewModel类的代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace DevDiv_DataBinding.Data
{
    class ViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<ForumItem> forumItemList;
        private int selectedItemIndex;
        private string itemDetail;
        public ViewModel()
        {
            forumItemList = new ObservableCollection<ForumItem>();
            selectedItemIndex = -1;
        }

        public int SelectedItemIndex
        {
            get { return selectedItemIndex; }
            set
            {
                selectedItemIndex = value; NotifyPropertyChanged("SelectedItemIndex");
            }
        }

        public string ItemDetail
        {
            get { return itemDetail; }
            set
            {
                itemDetail = value; NotifyPropertyChanged("ItemDetail");
            }
        }

        public ObservableCollection<ForumItem> ForumItemList
        {
            get
            {
                return forumItemList;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
            }
        }
    }

}

    ViewModel中最重要的是 forumItemLi st对象集合(ObservableCollection)。ObservableCollection类实现了一个集合的基本特性,并在向列表中的项有被添加、删除或替换时发送事件通知。注意ObservableCollection类本身并不会发送一个事件通知,当它包含的某个对象的数据值被修改时,只有通过创建一个可观测的forumItemList 对象的可观测集合,才能确保对ForumItem所做的任何改变都将导致UI控件的更新。
     ViewModel也实现了 INotifyPropertyChanged接口,因为在这里有用到了可观测对象 itemDetail。

好吧,上面就是一个简单的视图模型,下面我会介绍视图模型如何跟UI进行交互以实现数据改变后UI能够自动更新。


2、添加页面
    我在工程中添加了Pages目录,并添加了一个空白页 ListPage.xaml。在这里我并没有使用工程默认创建的MainPage.xaml。
    为了让程序启动的时候默认加载 ListPage界面,需要 更新App.xaml.cs,如下语句,修改为Pages.ListPage
var rootFrame = new Frame();
if (!rootFrame.Navigate(typeof(Pages.ListPage)))
{
    throw new Exception("Failed to create initial page");
}

这样我们的页面就添加好了。

下面我们就来对该页面 ListPage.xaml.cs进行编写

3、编写页面相关代码
这里我直接贴出 ListPage.xaml.cs文件的代码,以方便你直接阅读
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using DevDiv_DataBinding.Data;

// The Blank Page item template is documented at <a href="\"http://go.microsoft.com/fwlink/?LinkId=234238\"" target="\"_blank\"">http://go.microsoft.com/fwlink/?LinkId=234238</a>

namespace DevDiv_DataBinding.Pages
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class ListPage : Page
    {
        ViewModel viewModel;

        public ListPage()
        {

            viewModel = new ViewModel();
            viewModel.SelectedItemIndex = -1;
            viewModel.ForumItemList.Add(new ForumItem { Name = "Android开发论坛", TopicCount = 4, Link = "http://www.devdiv.com/forum-110-1.html",
                                                        Info = "Android开发论坛、Android开发者论坛、环境搭建、应用开发、驱动开发、系统移植、文档"});
            viewModel.ForumItemList.Add(new ForumItem { Name = "Android开发资料", TopicCount = 8, Link = "http://www.devdiv.com/forum-102-1.html" });
            viewModel.ForumItemList.Add(new ForumItem { Name = "iOS开发论坛/iPhone开发论坛", TopicCount = 8, Link = "http://www.devdiv.com/forum-102-1.html" });
            viewModel.ForumItemList.Add(new ForumItem { Name = "iOS开发资料/iPhone开发资料", TopicCount = 8, Link = "http://www.devdiv.com/forum-102-1.html" });
            viewModel.ForumItemList.Add(new ForumItem { Name = "微软/诺基亚 Windows Phone开发论坛 ", TopicCount = 8, Link = "http://www.devdiv.com/forum-102-1.html" });
            viewModel.ForumItemList.Add(new ForumItem { Name = "Windows Phone开发资料", TopicCount = 8, Link = "http://www.devdiv.com/forum-102-1.html" });
            viewModel.ForumItemList.Add(new ForumItem { Name = "Windows 8 开发论坛 ", TopicCount = 8, Link = "http://www.devdiv.com/forum-102-1.html",
                                                        Info = "Windows 8 代码、教程、入门、文档、视频"});
            viewModel.ForumItemList.Add(new ForumItem { Name = "Symbian开发论坛", TopicCount = 8, Link = "http://www.devdiv.com/forum-102-1.html" });
            viewModel.ForumItemList.Add(new ForumItem { Name = "Symbian开发论坛", TopicCount = 8, Link = "http://www.devdiv.com/forum-102-1.html" });
            viewModel.ForumItemList.Add(new ForumItem { Name = "Symbian开发论坛", TopicCount = 8, Link = "http://www.devdiv.com/forum-102-1.html" });
            viewModel.ForumItemList.Add(new ForumItem { Name = "Symbian开发论坛", TopicCount = 8, Link = "http://www.devdiv.com/forum-102-1.html" });
            viewModel.ForumItemList.Add(new ForumItem { Name = "Symbian开发论坛", TopicCount = 8, Link = "http://www.devdiv.com/forum-102-1.html" });
            viewModel.ForumItemList.Add(new ForumItem { Name = "Symbian开发论坛", TopicCount = 8, Link = "http://www.devdiv.com/forum-102-1.html" });

            this.InitializeComponent();

            this.DataContext = viewModel;
        }

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }

        private void ListSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            viewModel.ItemDetail = ((ForumItem)e.AddedItems[0]).Info;
            if (viewModel.ItemDetail == null || viewModel.ItemDetail.Length == 0)
            {
                viewModel.ItemDetail = ((ForumItem)e.AddedItems[0]).Name;
            }
        }
    }
}

上面的代码看着让我有点头疼,在这里我创建了一个viewModel并对其进行了初始化。你需要重点关注的下面的代码:
this.DataContext = viewModel;
DataContext 属性指定绑定到一个UI控件及其所有子控件的数据的来源。也就是说这里控件需要用到的数据来源是 viewModel。 使用this关键字来为整个布局设置DataContext。

代码最后定义了一个方法ListSelectionChanged,该方法处理当ListView中选择项改变后会触发的事件。在xaml文件中有使用到。

4、添加资源字典
     为了方便样式的统一,在这里我自定义了ListView使用到的一些样式,这样做的好处是UI控件的字体,颜色等属性我只需要在一个地方修改就可以应用到所有使用到的地方,非常的方便。为此我在工程中创建了Resources目录,并用Resource Dictionary(资源字典)项模板创建了一个新的ForumResourceDictionary.xaml文件。
该文件的代码如下:
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DevDiv_DataBinding.Resources">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/Common/StandardStyles.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <SolidColorBrush x:Key="AppBackgroundColor" Color="#3E790A"/>
    <Style x:Key="ForumListItem" TargetType="TextBlock" 
           BasedOn="{StaticResource BasicTextStyle}" >
        <Setter Property="FontSize" Value="38"/>
        <Setter Property="FontWeight" Value="Light"/>
        <Setter Property="Margin" Value="10, 0"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
    </Style>

    <DataTemplate x:Key="ForumListItemTemplate">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Name}" 
                       Style="{StaticResource ForumListItem}"/>
        </StackPanel>
    </DataTemplate>

</ResourceDictionary>

    看上面的代码,我定义了ForumListItem样式,以及一个数据模版ForumListItemTemplate。 ForumListItem定义了item的一些字体、布局等属性。 ForumListItemTemplate这定义了该item具有的控件内容以及绑定到的数据。 通过在.cs文件中设置DataContext属性,使视图模型作为绑定的数据源,Binding 关键字则让指定细节(“显示这个特定属性的值”)。

最后把该资源字典添加到App.xaml中即可,如下代码
<ResourceDictionary Source="Resources/ForumResourceDictionary.xaml"/>

5、编写XAML
我把ListPage.xaml文件的编写放到最后,这并不影响程序的开发。
同样我先把文件代码贴出来,然后对关键部分进行分析
<Page
    x:Class="DevDiv_DataBinding.Pages.ListPage"
    IsTabStop="false"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DevDiv_DataBinding.Pages"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{StaticResource AppBackgroundColor}">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="885*"/>
            <ColumnDefinition Width="481*"/>
        </Grid.ColumnDefinitions>
        <StackPanel Grid.RowSpan="2">
            <TextBlock Style="{StaticResource HeaderTextStyle}" Margin="10" Foreground="Red"
                       Text="论坛首页-DevDiv.com"/>
            <ListView x:Name="ForumList" Grid.RowSpan="2" 
                ItemsSource="{Binding ForumItemList}"
                ItemTemplate="{StaticResource ForumListItemTemplate}" 
                SelectionChanged="ListSelectionChanged" />
        </StackPanel>
        <StackPanel Orientation="Vertical" Grid.Column="1">
            <TextBlock Style="{StaticResource HeaderTextStyle}" Margin="10" Foreground="Red"
                       Text="板块详情"/>
            <TextBlock Style="{StaticResource HeaderTextStyle}" Margin="10" FontSize="30"
                       Text="{Binding ItemDetail}"/>
        </StackPanel>
        <StackPanel Orientation="Vertical" Grid.Column="1" Grid.Row="1" Background="White">
    <TextBlock Height="80"></TextBlock>
            <Image HorizontalAlignment="Center" Source="../Assets/icon.png"/>
            <TextBlock Height="135" FontSize="18" Foreground="Red">        
                <Run Text="大家好!我是破船"/>
                    <Run Text="欢迎跟我一起学习"/>  
                <LineBreak/>
                    <Run Text="Window 8 Metro App开发"/>
            </TextBlock>
        </StackPanel>
    </Grid>
</Page>

首先,来看看VS设计器显示的内容是什么,如下图:



可以看到,Grid控件的Background 特性为我在资源字典中指定的颜色。

我们来看上面的关键代码:
<ListView x:Name="ForumList" Grid.RowSpan="2" 
    ItemsSource="{Binding ForumItemList}"
    ItemTemplate="{StaticResource ForumListItemTemplate}" 
    SelectionChanged="ListSelectionChanged" />

该代码片段是使用了ListView ,并指定了它的数据源ItemsSource和数据模板ItemTemplate。
Binding 关键字告诉ListView控件应该显示我在.cs代码文件中设置的DataContext对象的ForumItemList 属性的内容。而ItemTemplate则告诉ListView 控件应该如何显示ItemSource 中的每一数据项。StaticResource关键字和ForumListItemTemplate 值表示将使用我在资源字典中指定的数据模板。
最后就是指定SelectionChanged事件的处理方法。

再来看看下面的代码: 该代码是显示在画面右上角的,其中第二个TextBlock 中将数据源绑定到视图模型的ItemDetail上。当用户改变选择项时,视图模型中的ItemDetail 内容也会改变,这样UI也会自动的更新ItemDetail 内容。
<StackPanel Orientation="Vertical" Grid.Column="1">
    <TextBlock Style="{StaticResource HeaderTextStyle}" Margin="10" Foreground="Red"
                Text="板块详情"/>
    <TextBlock Style="{StaticResource HeaderTextStyle}" Margin="10" FontSize="30"
                Text="{Binding ItemDetail}"/>
</StackPanel>

至此,我们的代码关键部分就编写完毕了。在设计器中你是看不到ListView中的数据的,因为设计器不能显示动态生成的内容。下面就让我们运行起来看看吧。

6、程序运行效果图与Demo程序

未选中效果



选中效果



最后送上代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值