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






4.40/5 (5投票s)
这是第二部分,使用 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”。
我们将在该类中添加大量代码,因此请密切关注。
- 创建一个 Contact 类的列表,如
ObservableCollection<Contact>
:(对于 observablecollection,您需要System.Collections.ObjectModel
;对于“Contact”,您需要 using Mvvm3_Basic.Model => 在我的例子中是这样,您的可能不同) - 您还需要添加许多其他命名空间。请参阅屏幕截图。
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; }}}
- 出于测试目的,这次我们只会在 XML 中插入记录。所以请在您的 XML 文件中插入一条记录。示例
<Contact> <CGuid>c3d471aa-2f38-4861-ae1e-8614a21d0734</CGuid> <Name>Harry</Name> <Email>harry@hogwarts.com</Email> </Contact> inside <Contacts></Contacts>
- 接下来我们添加读取 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 }); } } } }
- 该类使用 XDocument “异步”准备记录,从我们的 XML 文件中读取。然后,通过一些 LINQ 和一个 for 循环,我们创建一个“Contact”对象的列表,并将其放入我们的 ObservableCollection 中。MSDN 包含有关异步读取的大量文档。
- 接下来我们添加一个方法,用于将“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); } } }
- 插入、更新和删除操作将**不**使用
XDocument
。相反,我们将使用XmlDocument
并处理各种XmlElement
。 - 接下来我们添加一个
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); } } }
- 我们使用 XPath 来查找和更新我们的联系人。您需要复习您的 XPath!
- 接下来我们添加一个“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); } } }
- 好了,这就是我们的 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")); } }
现在让我们修改我们的方法以包含新的数据源。
- 首先,我们将修改
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!"); }
- 接下来我们将修改保存方法。
void OnSave(object obj) { db.Save(SelectedContact); if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("SelectedContact")); Debug.WriteLine("Save Called!"); }
- 接下来是更新方法。
void OnUpdate(object obj) { db.Update(SelectedContact); if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Contacts"));Debug.WriteLine("Update Called!"); }
- 接下来是删除方法。
void OnDelete(object obj) { db.Delete(SelectedContact); if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("SelectedContact")); Debug.WriteLine("Delete Called!"); }
- 最后是重要的
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
接口的一些帮助来实现的。
- 我们将现有的 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>
- 接下来我们将 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
属性显示联系人的名称。 - 最后我们使用 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 中,我们有一个包含姓名和电子邮件文本框的堆栈面板。两者都设置为相应的属性。再次,绑定是双向的。 - 所以这里视图中发生的是,当我们点击刷新按钮时,联系人列表会填充到 ListBox 中。当我们从 ListBox 中选择任何联系人时,会设置“SelectedContact”属性,进而导致文本框显示“
SelectedContact
”的姓名和电子邮件。 - 如果文本框中的任何内容发生更改,并且我们点击保存/更新/删除,那么我们的“
SelectedContact
”属性将再次使用新值进行设置,然后触发相应的命令。
就是这样!我们的应用程序终于完成了。运行它。尝试添加新值、更新现有值和删除一些值。这应该为那些在 MVVM 和 Metro 的广阔世界中盲目徘徊的人提供一个良好的开端。
文件已附在此帖中。下载并尽情享受。
祝好!