學習目標
- PCL 實作 FileHelper
- 檔案的寫入,讀取,刪除
自古以來,檔案存取 是程式最基本的功能,
不過手機取得檔案的方式和桌機有些不一樣,
在桌機上,使用者有權限去存取整個硬碟或瀏覽整個目錄,
在手機上,有幾個基本的資料夾專門拿來放圖片或音樂,甚至應用程式要存取的資料會放在 特定的資料夾 內。
有寫過 .NET 的程式人員一定會知道 System.IO 類別庫,支援檔案的輸入輸出,
其中最好用的就是 File 這個類別,
例如,
要寫入文字時可以直接使用 File.WriteAllText 方法,
或是讀取檔案時可以使用 File.ReadAllText 方法。
為了防止檔案存取去影響到 UI Thread,建議撰寫時都加上 非同步,教學見上一小節。
好消息與壞消息
好消息是,在 Android 和 iOS 專案內,只要 using System.IO,都能使用完整的 File 類別與功能,
壞消息是,PCL 專案內並沒有 File 類別的實作 (聽說因為萬惡的Windows Phone 和 Android、iOS 存取方式不同),所以,乾脆就你自己來實作...
更好的消息是本篇不會介紹 Windows Phone 的實作(誤
實作跨平台 file I/O
總之有點無奈的,先在 PCL 建立介面
namespace TextFileTryout { public interface IFileHelper { bool Exists(string filename); void WriteText(string filename, string text); string ReadText(string filename); IEnumerableGetFiles(); void Delete(string filename); } }
然後,在 iOS 專案內實作:
using System; using System.Collections.Generic; using System.IO; using Xamarin.Forms; [assembly: Dependency(typeof(TextFileTryout.iOS.FileHelper))] namespace TextFileTryout.iOS { class FileHelper : IFileHelper { public bool Exists(string filename) { string filepath = GetFilePath(filename); return File.Exists(filepath); } public void WriteText(string filename, string text) { string filepath = GetFilePath(filename); File.WriteAllText(filepath, text); } public string ReadText(string filename) { string filepath = GetFilePath(filename); return File.ReadAllText(filepath); } public IEnumerableGetFiles() { return Directory.GetFiles(GetDocsPath()); } public void Delete(string filename) { File.Delete(GetFilePath(filename)); } // 取得資料夾檔案的路徑 string GetFilePath(string filename) { return Path.Combine(GetDocsPath(), filename); } string GetDocsPath() { return Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); } } }
很蠢的是,Android 實作一模一樣,Copy & Past 到 Android 專案內,記得改 Dependency 和 namespace
至於 WP 我就不想介紹了…
最後最後,
為了讓使用上更方便,
我們回到 PCL 實作 IFileHelper 介面,並多一個取得所有檔案的方法:
namespace TextFileTryout { class FileHelper : IFileHelper { IFileHelper fileHelper = DependencyService.Get< IFilehelper>(); public bool Exists(string filename) { return fileHelper.Exists(filename); } public void WriteText(string filename, string text) { fileHelper.WriteText(filename, text); } public string ReadText(string filename) { return fileHelper.ReadText(filename); } //多一個取得所有檔案的方法,等等的範例要用 public IEnumerableGetFiles() { IEnumerable< string> filepaths = fileHelper.GetFiles(); List< string> filenames = new List< string>(); foreach (string filepath in filepaths) { filenames.Add(Path.GetFileName(filepath)); } return filenames; } public void Delete(string filename) { fileHelper.Delete(filename); } } }
OK,準備好以上有點蠢的東西後 (感覺就是可以在 PCL 內建好給我們用的東西啊),我們來寫一個比較實際的東西
實際應用 File I/O
畫面上會有一個 Entry 和 ListView,
Entry 輸入值後可以存文字檔到手機內,並用 ListView 將資料夾內所有檔案叫出來,
當然我們也可以從 ListView 將這檔案刪除
XAML
*OnPlatform 寫法已更改,請參考此篇文章
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="TextFileTryout.TextFileTryoutPage"> <ContentPage.Padding> <OnPlatform x:TypeArguments="Thickness" iOS="0, 20, 0, 0" /> </ContentPage.Padding> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <!--檔案名稱--> <Entry x:Name="filenameEntry" Grid.Row="0" Placeholder="filename" /> <!--要存的資料--> <Editor x:Name="fileEditor" Grid.Row="1"> <Editor.BackgroundColor> <OnPlatform x:TypeArguments="Color" WinPhone="#D0D0D0" /> </Editor.BackgroundColor> </Editor> <!--存檔按鈕--> <Button x:Name="saveButton" Text="Save" Grid.Row="2" HorizontalOptions="Center" Clicked="OnSaveButtonClicked" /> <!--抓資料出來顯示的 ListView--> <ListView x:Name="fileListView" Grid.Row="3" ItemSelected="OnFileListViewItemSelected"> <ListView.ItemTemplate> <DataTemplate> <TextCell Text="{Binding}"> <TextCell.ContextActions> <MenuItem Text="Delete" IsDestructive="True" Clicked="OnDeleteMenuItemClicked" /> </TextCell.ContextActions> </TextCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </Grid> </ContentPage>
.cs
為了讓事情 單純點,這邊沒有寫 MVVM,主要寫法為非同步事件
public partial class TextFileTryoutPage : ContentPage { FileHelper fileHelper = new FileHelper(); public TextFileTryoutPage() { InitializeComponent(); //先刷新一下畫面 RefreshListView(); } //存檔 async void OnSaveButtonClicked(object sender, EventArgs args) { string filename = filenameEntry.Text; if (fileHelper.Exists(filename)) { bool okResponse = await DisplayAlert("TextFileTryout", "File " + filename + " already exists. Replace it?", "Yes", "No"); if (!okResponse) return; } string errorMessage = null; try { //檔案寫入 fileHelper.WriteText(filenameEntry.Text, fileEditor.Text); } catch (Exception exc) { errorMessage = exc.Message; } //寫入後清空 if (errorMessage == null) { filenameEntry.Text = ""; fileEditor.Text = ""; RefreshListView(); } else { await DisplayAlert("TextFileTryout", errorMessage, "OK"); } } //點選 ListView 時 async void OnFileListViewItemSelected(object sender, SelectedItemChangedEventArgs args) { if (args.SelectedItem == null) return; string filename = (string)args.SelectedItem; string errorMessage = null; try { //將資料讀出 fileEditor.Text = fileHelper.ReadText((string)args.SelectedItem); filenameEntry.Text = filename; } catch (Exception exc) { errorMessage = exc.Message; } if (errorMessage != null) { await DisplayAlert("TextFileTryout", errorMessage, "OK"); } } //刪除檔案 void OnDeleteMenuItemClicked(object sender, EventArgs args) { string filename = (string)((MenuItem)sender).BindingContext; fileHelper.Delete(filename); RefreshListView(); } void RefreshListView() { fileListView.ItemsSource = fileHelper.GetFiles(); fileListView.SelectedItem = null; } }
執行結果:
沒有留言:
張貼留言
注意:只有此網誌的成員可以留言。