本文共 17248 字,大约阅读时间需要 57 分钟。
这一节开始我们将陆续看到Windows App是怎样操作文件的。
首先我们在XAML中定义一个Button和TextBlock,将读取文件/文件夹名的过程写在前者的click事件中,后者则用来显示文件信息。
以下这段代码,首先通过StorageFolder类读取图片库,然后使用异步的方式将图片库的文件和文件夹信息载入相应的List中。新建一个StringBuilder用以保存这些文件的信息,在这里只是使用了文件/文件夹的Name属性,但属性还有很多,比如Path属性。最后再将这些获取到的信息赋值给TextBlock即可。
private async void btnGetName_Click(object sender, RoutedEventArgs e) { StorageFolder pictureFolder = KnownFolders.PicturesLibrary; IReadOnlyListpictureFileList = await pictureFolder.GetFilesAsync(); IReadOnlyList pictureFolderList = await pictureFolder.GetFoldersAsync(); StringBuilder picutreFolderInfo = new StringBuilder(); foreach(StorageFile f in pictureFileList) { picutreFolderInfo.Append(f.Name+"\n"); } foreach(StorageFolder f in pictureFolderList) { picutreFolderInfo.Append(f.Name+"\n"); } textBlockFileName.Text = picutreFolderInfo.ToString(); }
注意要在方法名前面加上async哦。还有要在清单文件中声明我们的应用要使用图片库哦,一会在Windows Phone中也一样。
后台代码不用做任何修改,只需把XAML代码修改修改以适应屏幕即可~
private async void btnGetName_Click(object sender, RoutedEventArgs e) { StorageFolder picutureFolder = KnownFolders.PicturesLibrary; StringBuilder pictureFolderInfo = new StringBuilder(); IReadOnlyListpictureFileItem = await picutureFolder.GetItemsAsync(); foreach(var i in pictureFileItem) { if (i is StorageFolder) pictureFolderInfo.Append(i.Name + "\n"); else pictureFolderInfo.Append(i.Name + "\n"); } textBlockFileName.Text = pictureFolderInfo.ToString(); }
就我个人而言,还是非常喜欢使用文件选取器的,因为能够用自己的代码来调用系统的各种弹框。
在这个示例中,首先在XAML中添加一个Button和一个TextBlock,分别命名为btnSaveFile和tBlockSaveInfo。对于这个保存文件这个操作在后台的Click事件中就可以轻易完成了。
private async void btnSaveFile_Click(object sender, RoutedEventArgs e){ FileSavePicker saveFile = new FileSavePicker(); saveFile.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; // 显示在下拉列表的文件类型 saveFile.FileTypeChoices.Add("批处理文件", new List() { ".bat" }); // 默认的文件名 saveFile.SuggestedFileName = "SaveFile"; StorageFile file = await saveFile.PickSaveFileAsync(); if(file!=null) { // 在用户完成更改并调用CompleteUpdatesAsync之前,阻止对文件的更新 CachedFileManager.DeferUpdates(file); string fileContent = "@echo off \n dir/s \n pause"; await FileIO.WriteTextAsync(file, fileContent); // 当完成更改时,其他应用程序才可以对该文件进行更改。 FileUpdateStatus updateStatus = await CachedFileManager.CompleteUpdatesAsync(file); if(updateStatus==FileUpdateStatus.Complete) { tBlockSaveInfo.Text = file.Name + " 已经保存好了。"; } else { tBlockSaveInfo.Text = file.Name + " 保存失败了。"; } } else { tBlockSaveInfo.Text = "保存操作被取消。"; }}
代码中的下拉列表的文件类型就是如下所示这个样子哟。
大部分的内容我都已经通过注释的方式添加到代码中了,至于fileContent的那段代码到底是什么意思,大家试试就知道了,我感觉蛮有意思的。
如果大家试过打开这个bat文件,有没有觉得有趣呢?
更厉害的是,我们刚才所写的代码可以在Windows Phone上不经修改而直接使用。我的Lumia 638已经刷上了Windows 10预览版,大家可以瞧瞧,全新的资源管理器。
和用文件选取器保存文件相类似,打开文件的逻辑都差不多。这个示例中同样在XAML中定义一个名为btnOpenFile的Button和一个名为tBlockOpenInfo的TextBlock。
private async void btnOpenFile_Click(object sender, RoutedEventArgs e){ FileOpenPicker openFile = new FileOpenPicker(); openFile.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; openFile.ViewMode = PickerViewMode.List; openFile.FileTypeFilter.Add(".txt"); openFile.FileTypeFilter.Add(".docx"); openFile.FileTypeFilter.Add(".pptx"); // 选取单个文件 StorageFile file = await openFile.PickSingleFileAsync(); if (file != null) { tBlockOpenInfo.Text = "你所选择的文件是: " + file.Name; } else { tBlockOpenInfo.Text = "打开文件操作被取消。"; } // 选择多个文件 //IReadOnlyListfileList = await openFile.PickMultipleFilesAsync(); //StringBuilder fileOpenInfo = new StringBuilder(); //if(fileList!=null) //{ // foreach( StorageFile f in fileList) // { // fileOpenInfo.Append(f.Name + "\n"); // } // tBlockOpenInfo.Text = "你所选择的文件是: "+"\n"+ fileOpenInfo.ToString(); //} //else //{ // tBlockOpenInfo.Text = "打开文件操作被取消。"; //}}
我已经将选取多个文件的代码也列了出来,只需要取消注释即可。像ViewMode和FileTypeFilter这种属性,看看名字应该都知道了吧。重在实践。
在手机上也是通用的,刚才我试过了,成功进入了资源管理器,不过没能打开文件。应该是因为预览版的原因,这个预览版连Office都被移除了,估计会在下一版中添加通用版的Office应用。
在XAML中添加一个TextBlock用于显示相关信息,添加一个Button来使用它的Click事件,当然了,最后分别创建2个。
1.实例化StorageFolder类
我们的文件不可能让其随意保存在计算机/手机中的任何一个地方,应该先确定它的文件夹,对吧?
在新的Windows 8中,微软开启了Windows上的App时代,下载的软件再也不能随意安装到任何地方了,而是由操作系统统一放到一块叫做“独立存储”的地方。这也是出于安全的考虑。用过Windows Phone 8的朋友应该更加清楚了。
那么下面这行代码的LocalFolder究竟在哪里呢?
StorageFolder folder = Windows.Storage.ApplicationData.Current.LocalFolder;
下图中的文件,就是我当前所写的App。(补充一条哦,一开始我装了Win8后,下载了一个游戏,模拟类的,有金币呀什么的,后来我找到这个App的文件,将数据改了之后金币就哗哗的啦。当然了,对于其他单机而言这个完全不值一提,但App的数据,相信还有很多人没有改过吧。)
那么这张图中的红方框的文件夹就是LocalFolder啦,下面还有一个存储漫游文件的文件夹。
不论是读取文件还是写入文件,都得先确定一个文件夹哦。
2.实例化StorageFile
确定了文件夹,就得确定文件咯。对于创建文件而言,执行以下代码。既然用到了异步,在函数上加上async是必不可少的咯,这一点我们在前面讲到过。后面的ReplaceExisting属性是指的,如果该文件(名)已经存在了,则替换它。
StorageFile file = await folder.CreateFileAsync("New Document.txt", CreationCollisionOption.ReplaceExisting);
那么对于读取文件呢,就直接读取好啦。
StorageFile file = await folder.GetFileAsync("sample.txt");
3.创建和读取文件
将文本写入文件按照如下代码,将文件名和文本内容(字符串)。
await FileIO.WriteTextAsync(file, "Write text to file.");
读取文件也是类似的。
string text = await FileIO.ReadTextAsync(file);
我们还可以将这个读取的字符串传递给前面定义的TextBlock来加以调试。以下是完整的代码。
// 创建文件StorageFolder folder = Windows.Storage.ApplicationData.Current.LocalFolder;StorageFile file = await folder.CreateFileAsync("New Document.txt", CreationCollisionOption.ReplaceExisting);await FileIO.WriteTextAsync(file, "Write text to file.");
// 2 从文本读取文件StorageFolder folder = Windows.Storage.ApplicationData.Current.LocalFolder;StorageFile file = await folder.GetFileAsync("sample.txt");string text = await Windows.Storage.FileIO.ReadTextAsync(file);tBlockReadInfo.Text = text;
1.实例化StorageFolder类
同上。2.实例化StorageFile
同上。3.将字节写入到文件
a.建立缓冲区
var buffer = Windows.Security.Cryptography.CryptographicBuffer.ConvertStringToBinary("There's buffer ...... ", Windows.Security.Cryptography.BinaryStringEncoding.Utf8);
b.将缓冲区中的字节写入到文件
await Windows.Storage.FileIO.WriteBufferAsync(file, buffer);
4.从文件读取字节
a.将文件加载到缓冲区
var buffer = await Windows.Storage.FileIO.ReadBufferAsync(file);
b.实例化DataReader,读取缓冲区
DataReader dataReader = Windows.Storage.Streams.DataReader.FromBuffer(buffer);
c.从DataReader对象中读取字符串
string text = dataReader.ReadString(buffer.Length);
1.实例化StorageFolder类
同上。2.实例化StorageFile
同上。3.新建流,并异步地将file打开,使用可读写的方式
var stream = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);
4.将文本写入到文件
a.使用using
using (var writeStream= stream.GetOutputStreamAt(0)){ ......}
b.(在using语句的花括号内)创建DataWriter对象,并调用DataWriter.WriteString方法,将文本写入到writeStream中
DataWriter dataWriter = new DataWriter(writeStream);dataWriter.WriteString("Stream is a good thing.");
c.将文本保存到文件中,并通过StoreAsync和FlushAsync方法存储和关闭流
await dataWriter.StoreAsync();await writeStream.FlushAsync();
5.从文件读取文本
a.获取该流的size
var size = stream.Size;
b.使用using
using (var readStream = stream.GetOutputStreamAt(0)){ ......}
c.(在using语句的花括号内)创建DataWriter对象,并调用LoadAsync方法,最后调用ReadString即可。最后还可以将信息输出到TextBlock中。
DataReader dataReader = new DataReader(readStream);uint uintBytes = await dataReader.LoadAsync((uint)size);string text = dataReader.ReadString(uintBytes);tBlockReadInfo.Text = text;
这一节来看看获取文件属性吧,可以获取到文件名、类型、最近访问时间等等属性。
下面这段代码呢,都很简单。
在Click事件中,先获取到图片库,当然了,你可以获取其他地方,我电脑上的库文件中,就只有文档库和图片库有文件了。然后创建一个文件查询,最后将这些文件都赋给files。这里的var可谓是非常的强大啊。实例化一个StringBuilder对象来辅助输出信息那真是太完美不过了,我们在前面也常用到它。
var folder = KnownFolders.PicturesLibrary;var fileQuery = folder.CreateFileQuery();var files = await fileQuery.GetFilesAsync();StringBuilder fileProperties = new StringBuilder();for (int i = 0; i < files.Count; i++){ StorageFile file = files[i]; fileProperties.AppendLine("File name: " + file.Name); fileProperties.AppendLine("File type: " + file.FileType); BasicProperties basicProperties = await file.GetBasicPropertiesAsync(); string fileSize = string.Format("{0:n0}", basicProperties.Size); fileProperties.AppendLine("File size: " + fileSize + " bytes"); fileProperties.AppendLine("Date modified: " + basicProperties.DateModified); fileProperties.AppendLine(" ");}tBlockProp.Text = fileProperties.ToString();
这样一来就完成对Name、FileType、Size和DateModified属性的获取,但还有一类属性,则比较难以获取,它们就是“扩展属性”。
ListpropertiesName = new List ();propertiesName.Add("System.DateAccessed");propertiesName.Add("System.FileOwner");IDictionary extraProperties = await file.Properties.RetrievePropertiesAsync(propertiesName);var propValue = extraProperties[dateAccessedProperty];if (propValue != null) fileProperties.AppendLine("Date accessed: " + propValue);propValue = extraProperties[fileOwnerProperty];if (propValue != null) fileProperties.AppendLine("File owner: " + propValue);
最后将fileProperties传递给TextBlock即可。
tBlockProp.Text = fileProperties.ToString();
最后调试App,就会像下图一样了。
但是如你所见,文件名太长了却无法自动换行,而且数据也显示不完整。改改XAML中的TextBlock即可。TextWrapping属性设置为Wrap,则可以换行;将TextBlock添加到ScrollViewer内则会显示出一个滚动条。
在前面的几节中,都是关于数据的,这方面的内容其实还有很多很多,省略掉一部分后,也还是有很多。这一节是很重要的一部分,它关于如何保存和读取数据。
先来看看一个大概的背景吧,我这里写的很简单啦。
保存的内容就是这四个框框里填写的数据咯。先上XAML代码。
先来看看单个设置呗,下面就是代码咯。
Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;public MainPage(){ this.InitializeComponent();}private void btnSaveAppData_Click(object sender, RoutedEventArgs e){ localSettings.Values["RectangleRedHeight"] = tBoxRedHeight.Text; localSettings.Values["RectangleRedWidth"] = tBoxRedWidth.Text; localSettings.Values["RectangleGreenHeight"] = tBoxGreenHeight.Text; localSettings.Values["RectangleGreenWidth"] = tBoxGreenWidth.Text;}private void btnReadAppData_Click(object sender, RoutedEventArgs e){ Object objRectangleRedHeight = localSettings.Values["RectangleRedHeight"]; Object objRectangleRedWidth = localSettings.Values["RectangleRedWidth"]; Object objRectangleGreenHeight = localSettings.Values["RectangleGreenHeight"]; Object objRectangleGreenWidth = localSettings.Values["RectangleGreenWidth"]; recRed.Height = double.Parse(objRectangleRedHeight.ToString()); recRed.Width = double.Parse(objRectangleRedWidth.ToString()); recGreen.Height = double.Parse(objRectangleGreenHeight.ToString()); recGreen.Width = double.Parse(objRectangleGreenWidth.ToString()); }
首先定义了两个全局变量,如果看过前面几篇文章,这个应该就非常清楚了。顾名思义,第一个是用来保存本地设置的,第二个则是用来访问本地文件夹的。这里是单个设置地进行保存的,后面还有2种方式。那么就来调试吧,注意在点击了保存数据按钮之后把App关掉哦,关掉之后再加载,这样才算是保存了应用数据嘛,你说对不对呢?
以下就是我的测试结果了。
我们的设计都不用变,后台代码修改如下。
Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings; Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder; public MainPage() { this.InitializeComponent(); } private void btnSaveAppData_Click(object sender, RoutedEventArgs e) { Windows.Storage.ApplicationDataCompositeValue compositeSettings = new ApplicationDataCompositeValue(); compositeSettings["RectangleRedHeight"] = tBoxRedHeight.Text; compositeSettings["RectangleRedWidth"] = tBoxRedWidth.Text; compositeSettings["RectangleGreenHeight"] = tBoxGreenHeight.Text; compositeSettings["RectangleGreenWidth"] = tBoxGreenWidth.Text; localSettings.Values["RectangleSettings"] = compositeSettings; } private async void btnReadAppData_Click(object sender, RoutedEventArgs e) { Windows.Storage.ApplicationDataCompositeValue compositeSettings = (Windows.Storage.ApplicationDataCompositeValue)localSettings.Values["RectangleSettings"]; if (compositeSettings == null) { Windows.UI.Popups.MessageDialog messageDialog = new Windows.UI.Popups.MessageDialog("你好像没有保存任何应用数据哦!"); await messageDialog.ShowAsync(); } else { recRed.Height = double.Parse(compositeSettings["RectangleRedHeight"].ToString()); recRed.Width = double.Parse(compositeSettings["RectangleRedWidth"].ToString()); recGreen.Height = double.Parse(compositeSettings["RectangleGreenHeight"].ToString()); recGreen.Width = double.Parse(compositeSettings["RectangleGreenWidth"].ToString()); } }
使用ApplicationDataCompositeValue 会创建一个复合设置,通过代码所示方式即可添加数据,最后会将其添加到localSettings中。
读取数据的时候,同样是先在localSettings中通过键值对的方式来取出这个复合设置。如果该设置为空,就会调用MessageDialog控件弹窗通知没有保存数据。对于这个控件,可以访问这里:。如果复合设置存在则将他们分别进行类型转换后复制给相应的矩形的属性。
在容器存放数据其实也就这么回事啦,无非就是先创建一个容器,然后如果创建成功了,就在其中添加相应的数据即可。
至于加载数据,在这里我使用了一个bool变量来检查容器是不是已经创建好了,如果创建好了就可以将相应的数据取出然后赋值了,如果没有的话则一样挑出弹窗。
Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings; Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder; public MainPage() { this.InitializeComponent(); } private void btnSaveAppData_Click(object sender, RoutedEventArgs e) { Windows.Storage.ApplicationDataContainer containerSettings = localSettings.CreateContainer("RecSettingsContainer", Windows.Storage.ApplicationDataCreateDisposition.Always); if (localSettings.Containers.ContainsKey("RecSettingsContainer")) { localSettings.Containers["RecSettingsContainer"].Values["RectangleRedHeight"] = tBoxRedHeight.Text; localSettings.Containers["RecSettingsContainer"].Values["RectangleRedWidth"] = tBoxRedWidth.Text; localSettings.Containers["RecSettingsContainer"].Values["RectangleGreenHeight"] = tBoxGreenHeight.Text; localSettings.Containers["RecSettingsContainer"].Values["RectangleGreenWidth"] = tBoxGreenWidth.Text; } } private async void btnReadAppData_Click(object sender, RoutedEventArgs e) { bool hasContainerSettings = localSettings.Containers.ContainsKey("RecSettingsContainer"); if(hasContainerSettings) { recRed.Height = double.Parse(localSettings.Containers["RecSettingsContainer"].Values["RectangleRedHeight"].ToString()); recRed.Width = double.Parse(localSettings.Containers["RecSettingsContainer"].Values["RectangleRedWidth"].ToString()); recGreen.Height = double.Parse(localSettings.Containers["RecSettingsContainer"].Values["RectangleGreenHeight"].ToString()); recGreen.Width = double.Parse(localSettings.Containers["RecSettingsContainer"].Values["RectangleGreenWidth"].ToString()); } else { Windows.UI.Popups.MessageDialog messageDialog = new Windows.UI.Popups.MessageDialog("你好像没有保存任何应用数据哦!"); await messageDialog.ShowAsync(); } }
接下来就来个运行的截图咯,还有弹框的截图^_^
1.对于单个设置和复合设置
localSettings.Values.Remove("compositeSettings");
2.对于复合数据
localSettings.DeleteContainer("containerSettings");
删除并不难,或者说,这一节都不难。有了这些,我们在做游戏的时候,就可以将用户对游戏的设置都保存下来啦。