65.9K
CodeProject 正在变化。 阅读更多。
Home

一个使用 MVVM 为 Windows 8 Metro 风格应用制作的通讯录应用程序 第二部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.40/5 (5投票s)

2012年5月28日

CPOL

6分钟阅读

viewsIcon

43393

downloadIcon

1314

这是第二部分,使用 XML 文件作为 Windows 8 的数据源。

这是关于使用 XML 文件作为 Windows 8 数据源的系列文章的第二部分,请查看上一篇文章 此处

在上一个系列中,我们学习了如何使用 MVVM 创建应用程序的基本布局。我们将“视图”中的按钮与“视图模型”连接起来。现在我们需要将“视图模型”与“模型”连接起来。

“模型”是对您数据的表示。它是一个简单的“类”,代表单个数据实体。在我们的应用程序中,我们将使用我们在上一篇文章中构建的“Contact.cs”类。

using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Linq;
  using System.Text;
  using System.Threading.Tasks;
  namespace Mvvm3_Basic.Model
  {
  public class Contact : INotifyPropertyChanged
  {
  private string _guid;
  public string CGuid { get { return _guid; } set { _guid = value; CP("CGuid"); } }
  private string _name;
  public string Name { get { return _name; } set { _name = value; CP("Name"); } }
  private string _email;
  public string Email { get { return _email; } set { _email = value; CP("Email"); } } private void CP(string s)
  {
  if (PropertyChanged != null)
  PropertyChanged(this, new PropertyChangedEventArgs(s));
  }
  public event PropertyChangedEventHandler PropertyChanged;
  }
  }

在开始更新我们的视图模型之前,我们需要确保我们拥有有效的数据源。我们的数据源将是一个简单的 XML 文件。

在 Metro 中读写文件可能很棘手。您需要 阅读 MSDN 上的文档

我们首先创建我们的 XML 文件。右键单击项目名称,添加 -> 新建项 -> 数据 -> XML 文件。将 XML 文件命名为“ContactXML.xml”。点击确定。

文件的结构将是这样的

<?xml version="1.0" encoding="utf-8">
  <Contacts>
  <Contact>
  <CGuid></CGuid>
  <Name></Name
  <Email></Email>
  </Contact>
  </Contacts>

注意:我们将保持此结构。但由于我们没有任何数据,并且将在运行时添加数据,因此请删除 <Contact> 节点及其子节点。所以您只剩下 <Contacts></Contacts>(注意是复数)。

将此 XML 文件放在与我们的 EXE 相同的文件夹中非常重要。此 XML 文件的当前位置**不**合适。保存 XML 文件后,请将其移至应用程序的可执行目录。我们正在构建的应用程序非常简单,不使用其自身目录以外的任何其他目录。如果您不复制 XML,应用程序将无法运行。通常在 {project}/bin/debug/appx/

在我们的 XML 就绪并放置好之后,第一步是编写一个类来处理将联系人读写到 XML 文件中。右键单击“ViewModel”文件夹,添加 -> 类。将类命名为“DB.cs”。

我们将在该类中添加大量代码,因此请密切关注。

  1. 创建一个 Contact 类的列表,如 ObservableCollection<Contact>:(对于 observablecollection,您需要 System.Collections.ObjectModel;对于“Contact”,您需要 using Mvvm3_Basic.Model => 在我的例子中是这样,您的可能不同)
  2. 您还需要添加许多其他命名空间。请参阅屏幕截图。
    using System;
      using System.Collections.Generic;
      using System.Collections.ObjectModel;
      using System.Linq;
      using System.Text;
      using System.Threading.Tasks;using Mvvm3_Basic.Model;
        using System.Xml;
        using System.Xml.Linq;
        using Windows.ApplicationModel;
        using Windows.Data.Xml.Dom;
        using Windows.Storage;
        using System.IO;namespace Mvvm3_Basic.ViewModel
        {
        class DB
        {
        public ObservableCollection<Contact> contactlist { get; set; }}}
  3. 出于测试目的,这次我们只会在 XML 中插入记录。所以请在您的 XML 文件中插入一条记录。示例
    <Contact>
        <CGuid>c3d471aa-2f38-4861-ae1e-8614a21d0734</CGuid>
        <Name>Harry</Name>
        <Email>harry@hogwarts.com</Email>
    </Contact>
     inside <Contacts></Contacts>
  4. 接下来我们添加读取 XML 的代码。该方法称为:GetContactList()
    namespace Mvvm3_Basic.ViewModel
      {
    class DB
    {
    public ObservableCollection<Contact> contactlist { get; set; } public async void GetContactList()
        {
        var sf = await Package.Current.InstalledLocation.GetFileAsync(@"ContactXML.xml");
        var file = await sf.OpenAsync(FileAccessMode.Read);
        Stream inStream = file.AsStreamForRead()XDocument xdoc = XDocument.Load(inStream);
        var contacts = (from contact in xdoc.Descendants("Contact")
        select new
        {
        CGuid = contact.Element("CGuid").Value,
        Name = contact.Element("Name").Value,
        Email = contact.Element("Email").Value
        });if (contactlist == null) contactlist = new ObservableCollection<Contact>();
        contactlist.Clear(); foreach (var c in contacts)
        {
        contactlist.Add(new Contact { CGuid = c.CGuid, Name = c.Name, Email = c.Email });
        }
        }
        }
        }
  5. 该类使用 XDocument “异步”准备记录,从我们的 XML 文件中读取。然后,通过一些 LINQ 和一个 for 循环,我们创建一个“Contact”对象的列表,并将其放入我们的 ObservableCollection 中。MSDN 包含有关异步读取的大量文档。
  6. 接下来我们添加一个方法,用于将“Contact”插入到我们的 XML 文件中。
    public async void Save(Contact c)
    {
    var sf = await Package.Current.InstalledLocation.GetFileAsync(@"ContactXML.xml");
    XmlDocument xmlDoc;
    using (var stream = await sf.OpenAsync(FileAccessMode.ReadWrite))
    {
    xmlDoc = await XmlDocument.LoadFromFileAsync(sf);
    XmlElement root = xmlDoc.DocumentElement;
    XmlElement xe = xmlDoc.CreateElement("Contact");
    XmlElement cguid = xmlDoc.CreateElement("CGuid");
    cguid.InnerText = Guid.NewGuid().ToString();
    XmlElement name = xmlDoc.CreateElement("Name");
    name.InnerText = c.Name;
    XmlElement email = xmlDoc.CreateElement("Email");
    email.InnerText = c.Email;
    xe.AppendChild(cguid);
    xe.AppendChild(name);
    xe.AppendChild(email);
    root.AppendChild(xe);
    }
    if (xmlDoc != null)
    await xmlDoc.SaveToFileAsync(sf);
    }
    }
    }
  7. 插入、更新和删除操作将**不**使用 XDocument。相反,我们将使用 XmlDocument 并处理各种 XmlElement
  8. 接下来我们添加一个 Update 方法。我们的联系人将根据“CGuid”进行更新。它在此处充当我们的主键。
     public async void Update(Contact c)
    {
    var sf = await Package.Current.InstalledLocation.GetFileAsync(@"ContactXML.xml");
    XmlDocument xmlDoc;
    using (var stream = await sf.OpenAsync(FileAccessMode.ReadWrite))
    {
    xmlDoc = await XmlDocument.LoadFromFileAsync(sf);
    XmlElement root = xmlDoc.DocumentElement;
    IXmlNode xee = root.SelectSingleNode("//Contact/CGuid[.='" + c.CGuid + "']");
    xee.NextSibling.NextSibling.InnerText = c.Email;
    xee.NextSibling.InnerText = c.Name;
    }
    if (xmlDoc != null)
    await xmlDoc.SaveToFileAsync(sf);
    }
    }
    }
  9. 我们使用 XPath 来查找和更新我们的联系人。您需要复习您的 XPath!
  10. 接下来我们添加一个“Delete”方法。与更新一样,删除将根据 CGuid 进行。
    public async void Delete(Contact c)
    {
    var sf = await Package.Current.InstalledLocation.GetFileAsync(@"ContactXML.xml");
    XmlDocument xmlDoc;
    using (var stream = await sf.OpenAsync(FileAccessMode.ReadWrite))
    {
    xmlDoc = await XmlDocument.LoadFromFileAsync(sf);
    XmlElement root = xmlDoc.DocumentElement;
    //IXmlNode xee = root.SelectSingleNode("//Contact/Name[.='" + c.Name + "'] | //Contact/Email[.='" + c.Email + "']");
    IXmlNode xee = root.SelectSingleNode("//Contact/CGuid[.='" + c.CGuid + "']");
    xee.ParentNode.RemoveChild(xee.NextSibling.NextSibling);
    xee.ParentNode.RemoveChild(xee.NextSibling);
    xee.ParentNode.RemoveChild(xee); IXmlNode clup = root.SelectSingleNode("//*[not(node())]");
      clup.ParentNode.RemoveChild(clup);
      }
      if (xmlDoc != null)
      await xmlDoc.SaveToFileAsync(sf);
      }
      }
      }
    
  11. 好了,这就是我们的 DB.cs 类的大部分内容。我们现在将设置我们的 ViewModel 类。

现在让我们来处理 ViewModel。

在我们的 ViewModel 中添加一个名为 SelectedContact 的属性,如下所示。此属性将维护状态。简单来说,无论您在 UI 中处理哪个“Contact”,此属性都将在此 ViewModel 中维护有关该联系人的详细信息。同时,创建一个 DB.cs 对象。我们需要此对象来处理我们的数据源。

DB db = new DB();    //at class level private Contact _ctc = new Contact();
  public Contact SelectedContact
  {
  get { return _ctc; }
  set
  {
  _ctc = value;
  if (PropertyChanged != null)
  PropertyChanged(this, new PropertyChangedEventArgs("SelectedContact")); }
  }

现在让我们修改我们的方法以包含新的数据源。

  1. 首先,我们将修改 OnAdd()。此方法将返回一个空的联系人。这很有用,因为在我们的“视图”(UI)中,有一个“添加新”按钮。单击该按钮时,我们需要清除我们的文本框,以便用户可以输入数据。由于我们在“视图”(UI)中的文本框将绑定到 SelectedContact 属性,因此返回一个空字符串将自动清除文本框中的任何值。(很巧妙吧!)
    void OnAdd(object obj)
    {
    Contact c = new Contact { Name = string.Empty, Email = string.Empty };
    SelectedContact = c;
    PropertyChanged(this, new PropertyChangedEventArgs("SelectedContact")); Debug.WriteLine("Add Called!");
      }
  2. 接下来我们将修改保存方法。
     void OnSave(object obj)
    {
    db.Save(SelectedContact);
    if (PropertyChanged != null)
    PropertyChanged(this, new PropertyChangedEventArgs("SelectedContact")); Debug.WriteLine("Save Called!");
        }
  3. 接下来是更新方法。
    void OnUpdate(object obj)
    {
    db.Update(SelectedContact);
    if (PropertyChanged != null)
    PropertyChanged(this, new PropertyChangedEventArgs("Contacts"));Debug.WriteLine("Update Called!");
        }
  4. 接下来是删除方法。
    void OnDelete(object obj)
    {
    db.Delete(SelectedContact);
    if (PropertyChanged != null)
    PropertyChanged(this, new PropertyChangedEventArgs("SelectedContact")); Debug.WriteLine("Delete Called!");
        }
  5. 最后是重要的 GetContact() 方法。
    void GetContact(object obj)
    {
    if (Contacts == null) Contacts = new ObservableCollection<Contact>();
    Contacts.Clear();
    db.GetContactList();
    Contacts = db.contactlist;
    if (PropertyChanged != null)
    PropertyChanged(this, new PropertyChangedEventArgs("Contacts")); Debug.WriteLine("Refresh called!");
        }

最后一步将是构建视图,即我们的 UI。所以我们开始吧。您需要修改现有的 UI。

在现有的 UI 中,我们只有一个包含按钮的堆栈面板。现在我们将添加一个 ListBox(用于容纳我们的联系人列表)和一个 Grid(这是我们的联系人处理表单)。当我们从 ListBox 中选择任何联系人时,详细信息会加载到表单中。同时,“ViewModel”的“SelectedContact”属性也将被设置为从 ListBox 中选中的联系人。这是通过 TwoWay Binding 和 INotifyPropertyChanged 接口的一些帮助来实现的。

  1. 我们将现有的 Grid 分割成一个合适的布局。
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"> <Grid Background="{StaticResource ApplicationPageBackgroundBrush}">
      <Grid.RowDefinitions>
      <RowDefinition Height="25*"/>
      <RowDefinition Height="231*"/>
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
      <ColumnDefinition Width="205*"/>
      <ColumnDefinition Width="478*"/>
      </Grid.ColumnDefinitions>
  2. 接下来我们将 ListBox 添加到 View。
     <Grid Background="{StaticResource ApplicationPageBackgroundBrush}">
    <Grid.RowDefinitions>
    <RowDefinition Height="25*"/>
    <RowDefinition Height="231*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
    <ColumnDefinition Width="205*"/>
    <ColumnDefinition Width="478*"/>
    </Grid.ColumnDefinitions> <ListBox x:Name="ListPerson" Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Contacts}" 
        SelectedItem="{Binding SelectedContact, Mode=TwoWay}" DisplayMemberPath="Name" FontSize="24" >
      </ListBox>

    请注意,ItemsSource 设置为 Contacts (ObservableCollection),SelectedItem 设置为我们的 SelectedItem 属性。这是一个双向绑定,我们只使用 DisplayMemberPath 属性显示联系人的名称。

  3. 最后我们使用 GridView 添加表单。
    <ListBox x:Name="ListPerson" Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Contacts}" 
    SelectedItem="{Binding SelectedContact, Mode=TwoWay}" DisplayMemberPath="Name" FontSize="24" >
    </ListBox> <Grid x:Name="ContactLst" DataContext="{Binding SelectedContact}" Grid.Row="1" Grid.Column="1">
      <StackPanel Orientation="Vertical">
      <StackPanel>
      <TextBlock Text="Name: " FontSize="24"/>
      <TextBox Text="{Binding Name, Mode=TwoWay}" FontSize="24" Padding="10" Margin="10"/>
      </StackPanel>
      <StackPanel>
      <TextBlock Text="Email: " FontSize="24"/>
      <TextBox Text="{Binding Email, Mode=TwoWay}" FontSize="24" Padding="10" Margin="10"/>
      </StackPanel>
      </StackPanel>
      </Grid>

    我们已将 Grid 的 DataContext 设置为“SelectedContact”。在 Grid 中,我们有一个包含姓名和电子邮件文本框的堆栈面板。两者都设置为相应的属性。再次,绑定是双向的。

  4. 所以这里视图中发生的是,当我们点击刷新按钮时,联系人列表会填充到 ListBox 中。当我们从 ListBox 中选择任何联系人时,会设置“SelectedContact”属性,进而导致文本框显示“SelectedContact”的姓名和电子邮件。
  5. 如果文本框中的任何内容发生更改,并且我们点击保存/更新/删除,那么我们的“SelectedContact”属性将再次使用新值进行设置,然后触发相应的命令。

就是这样!我们的应用程序终于完成了。运行它。尝试添加新值、更新现有值和删除一些值。这应该为那些在 MVVM 和 Metro 的广阔世界中盲目徘徊的人提供一个良好的开端。

文件已附在此帖中。下载并尽情享受。

祝好!

© . All rights reserved.