2016/10/05
企業之差旅費用 跨平台應用程式開發 (2) 建立專案
在這裡,您已經準備好要來進行開發跨平台 ( Android, iOS, UWP ) 的行動應用程式開發,並且在行動應用程式中,將會呼叫企業後端所提供的 Web API 服務,以便讓使用者可以透過行動裝置,進行差旅費用的申請;這是企業內部很典型的一個應用程式,接下來就是需要開始建立專案與進行相關設定。
在這個階段的練習,您將會學會底下的 Xamarin.Forms 的開發技術:
- 使用 Visual Studio 2015 增加 Prism 專案樣版
- 建立 Prism for Xamarin.Forms 的專案
- 升級與安裝必要的套件(NuGet Package)或者插件(Plug-ins)
- 建立專案資料夾
這篇章節的練習專案的原始程式碼將會存放在 GitHubhttps://github.com/vulcanlee/XFAppSample/tree/master/XFDoggy/1.BuildProject 內,在這個目錄下,會有兩個目錄:
Starter這個目錄將會存放當您要進行練習開發這個專案之前,Visual Studio 專案的原始檔案。 Completed這個目錄將會存放當您要進行練習開發這個專案完成之後,Visual Studio 專案的原始檔案。
使用 Visual Studio 2015 增加 Prism 專案樣版
若您的 Visual Studio 開發環境中,尚未安裝
Prism Template Pack
擴充功能,請參考底下步驟,進行安裝這個擴充功能。安裝 Prism Template Pack
- 首先,開啟 Visual Studio 2015
- 點選功能表
工具
>擴充功能與更新
選項 - 在
擴充功能與更新
對話窗內,點選線上
>Visual Studio 組件庫
- 在右上方文字輸入盒內,輸入
Prism Template Pack
,並且按下Enter
按鍵 - 點選搜尋出來的
Prism Template Pack
項目,點選下載
按鈕,安裝Prism Template Pack
組件到您的Visual Studio 2015 內;安裝完成後,您需要點選立即重新啟動
按鈕,讓您的 Visual Studio 2915 重新啟動之後,這次的安裝才會生效。
建立 Prism for Xamarin.Forms 的專案
- 當 Visual Studio 重新啟動之後,點選功能表
檔案
>新增
>專案
- 在
新增專案
對話窗內,點選範本
>Visual C#
>Prism
>Prism Unity App (Forms)
,並且在底下名稱欄位中,輸入XFDoggy
最後,點選確定
按鈕。 - 接著,會出現
Prism for Xamarin.Forms - Project Wizard
這個對話窗,請在這個對話窗中,勾選Android
/iOS
/UWP
這三個檢查盒,最後點選Create
按鈕,以便產生這三個類型的專案。 - 當專案建立到一半,若您的開發環境還沒有建置與設定完成 Mac 電腦與 Xamarin Studio for Mac 系統,此時會看到
Xamarin Mac Agent Instructions
對話窗出現,這個對話窗是要提醒您進行與 Mac 電腦連線設定,這是因為,您無法在 Windows 作業系統進行 iOS 相關應用程式的建立與設計工作,而是需要透過 Mac 電腦安裝的 XCode 來協助您完成這些 iOS 應用程式的建立工作。不過,這不影響您繼續開發 Xamarin.Forms 的應用程式,只不過此時,您無法編譯與執行 iOS 的應用程式。 - 接著會看到
新的通用Windows專案
對話視窗,此時,您只需要按下確定
按鈕即可,此時,專案精靈會繼續完成相關平台的專案建立工作。 - 最後,整個新的 Xamarin.Forms 專案就建立完成了。
升級與安裝必要的套件(NuGet Package)或者插件(Plug-ins)
在這個專案內,將會使用到許多不同的 NuGet 套件 (Package),因此,您需要先在這裡把這些套件安裝到所有的專案內,方便日後開發作業可以更加順利。
- 滑鼠右擊方案
XFDoggy
,選擇管理方案的 NuGet 套件
,在NuGet - 解決方案
頁面中,切換到瀏覽
標籤頁次內,請在文字輸入盒中輸入,Behaviors.Forms
並且開始搜尋這個套件,並且安裝到所有的專案內。使用這個套件,可以讓您在宣告 XAML 定義的時候,使用 XAML 提供的行為特性功能,例如,可以綁定當某個事件發生的時候,將會執行某個指定的 ICommand。這項功能對於有在開發 WPF 或者 Windows UWP 的開發者而言,必定不陌生,因為,在這些環境之下,可以使用 Blend SDK 所提供的各種不同行為(Behaviors)功能,來擴增或者豐富您的 XAML 宣告。 - 滑鼠右擊方案
XFDoggy
,選擇管理方案的 NuGet 套件
,在NuGet - 解決方案
頁面中,切換到瀏覽
標籤頁次內,請在文字輸入盒中輸入,Newtonsoft.Json
並且開始搜尋這個套件,並且安裝到所有的專案內。'Newtonsoft.Json' 這個套件,可以讓您進行 JSON 物件的 序列化 (Serialization) 或者 反序列化 (Deserialization)) 的操作;這項功能將會提供當從網路取得開發資料的 JSON 定義資料之後,接著,便可以將這些 JSON 定義文字轉換成為 .NET 的物件。 - 滑鼠右擊核心PCL專案
XFDoggy
,選擇管理 NuGet 套件
,在NuGet - 解決方案
頁面中,切換到瀏覽
標籤頁次內,請在文字輸入盒中輸入,Microsoft.Net.Http
並且開始搜尋這個套件,並且安裝到核心PCL專案內;在安裝過程中,若看到接受授權
對話窗,請點選我接受
按鈕由於在撰寫這份文件的時候 (2016.09.19),本機電腦 Visual Studio 的 Xamarin Toolkit 已經升級到 Xamarin 4.2 版,而透過這個Xamarin版本再產生 核心PCL專案 的時候,其 PCL TargetFrameworkProfile 已經採用了Profile259
(前一個 Xamarin 版本,使用的Profile111
);而在Profile259
的 PCL專案內,是沒有這個System.Net.Http
命名空間。因此,需要額外安裝這個Microsoft.Net.Http
NuGet 套件,以便可以使用HttpClient
這樣的類別。想要查看您的 PCL專案的TargetFrameworkProfile
是哪個版本,可以先使用滑鼠右擊 核心PCL XFDoggy 專案,接著選取卸載專案
,再度使用滑鼠右擊 核心PCL XFDoggy 專案,選取編輯 XFDoggy.csproj
這個選項。在下面截圖中,將會看到紅色框線處標示的,就是這個 PCL 的 Profile 版本有關更多不同 PCL Profile 資訊,可以參考這個網址http://portablelibraryprofiles.apps.stephencleary.com/
建立專案資料夾
核心 PCL 專案
請在 核心 PCL 專案內依序建立底下的資料夾
- 滑鼠右擊
XFDoggy
核心PCL 專案節點,選擇加入
>新增資料夾
,在新產生的資料夾內,填入資料夾名稱Helps
- 滑鼠右擊
XFDoggy
核心PCL 專案節點,選擇加入
>新增資料夾
,在新產生的資料夾內,填入資料夾名稱Infrastructure
- 滑鼠右擊
XFDoggy
核心PCL 專案節點,選擇加入
>新增資料夾
,在新產生的資料夾內,填入資料夾名稱Models
- 滑鼠右擊
XFDoggy
核心PCL 專案節點,選擇加入
>新增資料夾
,在新產生的資料夾內,填入資料夾名稱Services
平台專案
請在每個平台的專案下,依序建立底下資料夾
- 滑鼠右擊
XFDoggy.Droid
專案節點,選擇加入
>新增資料夾
,在新產生的資料夾內,填入資料夾名稱Infrastructure
- 滑鼠右擊
XFDoggy.iOS
專案節點,選擇加入
>新增資料夾
,在新產生的資料夾內,填入資料夾名稱Infrastructure
- 滑鼠右擊
XFDoggy.UWP
專案節點,選擇加入
>新增資料夾
,在新產生的資料夾內,填入資料夾名稱Infrastructure
- 滑鼠右擊
XFDoggy.UWP
專案節點內的Assets
資料夾,選擇加入
>新增資料夾
,在新產生的資料夾內,填入資料夾名稱Images
複製圖片檔案
- 請下載底下連結中的圖片檔案到本機檔案總管內的某個資料夾內
- 請拖拉這些圖片檔案到
XFDoggy.Droid
專案 >Resources
>drawable
目錄下 - 請拖拉這些圖片檔案到
XFDoggy.iOS
專案 >Resources
目錄下 - 請拖拉這些圖片檔案到
XFDoggy.UWP
專案 >Assets
>Images
目錄下
第一次啟動專案測試
- 滑鼠右擊
XFDoggy.Droid
專案節點,選擇設定為起始專案
- 按下
F5
按鍵,該使執行這個 Android 專案,看看是否可以正常執行。 - 若可以正常編譯,但是執行的時候出現了錯誤訊息:
java.lang.OutOfMemoryError
- 此時請滑鼠雙擊
XFDoggy.Droid
專案Properties
節點 - 在
XFDoggy.Droid
頁面,點選Android Options
>Advanced
- 在
Java Max Heap Size
欄位內輸入1G
- 重新執行一次
2016/10/04
企業之差旅費用 跨平台應用程式開發 (1) 專案需求說明
這是一個企業內部行動應用程式的開發範例,透過已經開發好的 Web API,開發出一套跨平台的行動裝置應用程式,可以分別在 Android / iOS / UWP 裝置上執行,讓企業內部使用者透過這個應用程式進行差旅費用的申請。
若要瞭解這些 Web API 如何使用,可以下載這個 PostMan 定義檔案,查看相關範例
2016/10/03
如何啟用 Xamarin.Forms 的 XAML 編輯有 IntelliSense效果
當您在使用 Visual Studio 2105 編輯 .xaml 檔案內的 XAML 標籤宣告語言,Visual Studio 2015 提供了 IntelliSense 的輔助輸入提示功能;不過,若您的 Visual Studio 2015 對於正在編輯的 XAML 檔案,無法正常使用 IntelliSense 的功能,可以參考底下步驟。
- 關閉 Visual Studio 2015 所有正在編輯的標籤頁次
- 離開 Visual Studio
- 重新使用 Visual Studio 開啟這個專案
- 使用滑鼠右擊 XAML 檔案,選擇
開啟方式
>原始程式碼(文字)編輯器
,接著點選設定為預設值
按鈕,最後點選確定
按鈕
在下圖中,您可以看到使用了
原始程式碼(文字)編輯器
方式來開啟 XAML 檔案與沒有的差異,那就是最前面的 <?xml version="1.0" encoding="utf-8" ?>
文字,顯示為藍色的,說明這樣開啟 XAML 檔案的方式,具有 IntelliSense 的效果。
標籤:
Xamarin
2016/10/02
Xamarin.Forms 使用 Grial UI Kit 在Android可以執行,在iOS卻會閃退
使用 Grial UI Kit 的專案,發現到 Android 專案可以正常執行,可是,在 iOS 專案下執行,卻會發現到有
System.IO.FileNotFoundException: Could not load file or assembly 'UXDivers.Artina.Shared' or one of its dependencies
例外異常錯誤訊息。
當您使用 Xamarin.Forms 搭配 XAML 開發方式,執行 iOS 專案的時候,可能會發生某些組件無法正常被載入的情況。
假使您僅僅在 XAML 中參考這些組件,就會發生這樣的例外異常錯誤。
您可以在
AppDelegate.cs
檔案中,加入底下程式碼,強迫要將這個組件讀入到記憶體中var workaround = typeof(UXDivers.Artina.Shared.CircleImage);
標籤:
Xamarin
訂閱:
文章 (Atom)
ListView 是在進行開發行動裝置應用程式中最基本,也是最複雜的控制項,在這份筆記中,將會說明如何使用底下各類功能。
ListView 基本應用 不可修改紀錄 (BasicPage)
ItemsSource="{Binding MyItemList, Mode=TwoWay}"
ItemsSource
這個屬性,使用 XAML 延伸標記功能,進行資料綁定;例如ItemsSource="{Binding MyItemList, Mode=TwoWay}"
。而在 ViewModel 中,您需要使用ObservableCollection<MyItem>
來宣告 ListView 控制項所要顯示的資料來源,使用ObservableCollection
的目的,是您可以透過訂閱內建的CollectionChanged
事件,便可以得知當時資料新增、修改、刪除等狀態。ListView.ItemTemplate
>DataTemplate
>ViewCell
來進行定義,在這頁範例中,使用 Grid 版面配置,定義了三個欄位(Column),並且使用了雙向資料繫結綁定了每筆紀錄的欄位;而每筆資料的紀錄來為型態為MyItem
,這個類別定義有列在底下程式碼清單中。<ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="36" /> </Grid.ColumnDefinitions> <Label Grid.Column="0" VerticalOptions="Center" TextColor="Teal" FontSize="20" Text="{Binding FirstName, Mode=TwoWay}" /> <Label Grid.Column="1" VerticalOptions="Center" TextColor="Teal" FontSize="20" Text="{Binding LastName, Mode=TwoWay}" /> <Label Grid.Column="2" VerticalOptions="Center" TextColor="Blue" FontSize="30" Text="{Binding Age, Mode=TwoWay}" /> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate>
SelectedItem
屬性中,因此,透過了雙向資料繫結SelectedItem="{Binding MyItemListSelected, Mode=TwoWay}"
就可以在檢視模型 (ViewModel) 內,取得當時使用者點選的那筆資料。而在 ViewModel 內的 MyItemListSelected 屬性,可以使用強型別的方式來宣告,這樣就可以在 ViewModel 內簡單的存取相關屬性。MyItemList
物件,進行新增一筆物件,此時,在 ListView 上,就會同步更新了這筆資料。要了要能夠呈現在 ViewModel 中,新增一筆紀錄,而在 View 中也能夠動態更新,您需要使用ObservableCollection<T>
來宣告使用紀錄列之物件。private ObservableCollection<MyItem> myItemList; public ObservableCollection<MyItem> MyItemList { get { return myItemList; } set { SetProperty(ref myItemList, value); } }
MyItemList
物件,進行一筆紀錄的刪除動作,此時,在 ListView 上,就會同步刪除了這筆資料。myItemListSelected
來進行選定的紀錄,修改其內容,但由於每筆 ListView 內的資料類別為MyItem
,在這個類別內的每個屬性,並沒有實作出INotifyPropertyChanged
介面,也就是說,就算當使用者點選了 修改 按鈕,試圖變更了 ViewModel 內的這筆紀錄內容,這些內容也不會顯示在 ListView 上,在下一個範例中,將會說明如何解決此一問題。public class MyItem { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } }
修改()
方法中,有註解兩段程式碼,這兩段程式碼也是可以做到修改紀錄的能力,只不過是透過了重新設定這筆紀錄或者將原紀錄刪除、接著在加入到原先位置上,達成可以顯示出修改紀錄在 ListView 上的效果,這樣的做法比較不太建議使用。BasicPage.xaml
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" prism:ViewModelLocator.AutowireViewModel="True" x:Class="XFListView.Views.BasicPage" Title="ListView 基本應用 不可修改紀錄" > <StackLayout> <ListView ItemsSource="{Binding MyItemList, Mode=TwoWay}" SelectedItem="{Binding MyItemListSelected, Mode=TwoWay}" Margin="20,0" > <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="36" /> </Grid.ColumnDefinitions> <Label Grid.Column="0" VerticalOptions="Center" TextColor="Teal" FontSize="20" Text="{Binding FirstName, Mode=TwoWay}" /> <Label Grid.Column="1" VerticalOptions="Center" TextColor="Teal" FontSize="20" Text="{Binding LastName, Mode=TwoWay}" /> <Label Grid.Column="2" VerticalOptions="Center" TextColor="Blue" FontSize="30" Text="{Binding Age, Mode=TwoWay}" /> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> <StackLayout Orientation="Vertical"> <StackLayout Orientation="Horizontal" HorizontalOptions="Center"> <Button Text="新增" Command="{Binding 新增Command}" /> <Button Text="修改" Command="{Binding 修改Command}" /> <Button Text="刪除" Command="{Binding 刪除Command}" /> </StackLayout> </StackLayout> </StackLayout> </ContentPage>
BasicPageViewModel.cs
using Prism.Commands; using Prism.Mvvm; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using XFListView.Models; using XFListView.Repositories; namespace XFListView.ViewModels { public class BasicPageViewModel : BindableBase { private ObservableCollection<MyItem> myItemList; public ObservableCollection<MyItem> MyItemList { get { return myItemList; } set { SetProperty(ref myItemList, value); } } #region PropertyName private string propertyName; /// <summary> /// PropertyDescription /// </summary> public string PropertyName { get { return this.propertyName; } set { this.SetProperty(ref this.propertyName, value); } } #endregion private MyItem myItemListSelected; public MyItem MyItemListSelected { get { return myItemListSelected; } set { SetProperty(ref myItemListSelected, value); } } public DelegateCommand 新增Command { get; private set; } public DelegateCommand 修改Command { get; private set; } public DelegateCommand 刪除Command { get; private set; } public BasicPageViewModel() { MyItemList = MyItemRepository.Sample(); 新增Command = new DelegateCommand(新增); 修改Command = new DelegateCommand(修改); 刪除Command = new DelegateCommand(刪除); } private void 刪除() { var fooItem = MyItemList.FirstOrDefault(x => x.FirstName == myItemListSelected.FirstName && x.LastName == myItemListSelected.LastName && x.Age == myItemListSelected.Age); if (fooItem != null) { MyItemList.Remove(fooItem); } } private void 修改() { if (myItemListSelected != null) { // 底下程式碼,無法讓 UI 同步更新已經修正的內容 #region 直接修改綁定的物件 myItemListSelected.FirstName = "艾嬡"; myItemListSelected.LastName = "林"; myItemListSelected.Age = 33; #endregion // 若每筆紀錄的每個欄位沒有實作 INotifyPropertyChanged 介面 // 則當修改某一紀錄的欄位時候,就無法立即反應、顯示更新結果 // 您需要變通,重新設定這筆紀錄,或者刪除這筆紀錄再加入 // 請參考底下兩個方法(這樣的方法比較複雜) #region 先找到、修改、再回去設定 (沒有動畫) //var fooItem = MyItemList.FirstOrDefault(x => x.FirstName == myItemListSelected.FirstName && //x.LastName == myItemListSelected.LastName && x.Age == myItemListSelected.Age); //if (fooItem != null) //{ // var fooIdx = myItemList.IndexOf(fooItem); // fooItem.FirstName = "艾嬡"; // fooItem.LastName = "林"; // fooItem.Age = 33; // myItemList[fooIdx]= fooItem; //} #endregion #region 先找到、移除、再加入 (會有動畫) //var fooItem = MyItemList.FirstOrDefault(x => x.FirstName == myItemListSelected.FirstName && //x.LastName == myItemListSelected.LastName && x.Age == myItemListSelected.Age); //if (fooItem != null) //{ // var fooIdx = myItemList.IndexOf(fooItem); // myItemList.Remove(fooItem); // fooItem.FirstName = "艾嬡"; // fooItem.LastName = "林"; // fooItem.Age = 33; // myItemList.Insert(fooIdx, fooItem); //} #endregion } } private void 新增() { MyItemList.Add(new MyItem { FirstName = "文君", LastName = "簡", Age = 19 }); } } } ## MyItem.cs ```cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace XFListView.Models { public class MyItem { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } } }
ListView 基本應用 可修改紀錄 (Basic1Page)
public ObservableCollection<MyItemViewModel> MyItemList
,也就是,在 ListView 中的每筆紀錄,都會修改成為 MyItemViewModel 類別,而這個類別則有實作了BindableBase
,請參考底下程式碼。因此,原先當使用者按下了修改按鈕所要做的事情,就會立即在 ListView 上顯示出這些更新的欄位。private ObservableCollection<MyItemViewModel> myItemList; public ObservableCollection<MyItemViewModel> MyItemList { get { return myItemList; } set { SetProperty(ref myItemList, value); } }
public class MyItemViewModel : BindableBase { #region FirstName private string firstName; /// <summary> /// PropertyDescription /// </summary> public string FirstName { get { return this.firstName; } set { this.SetProperty(ref this.firstName, value); } } #endregion #region LastName private string lastName; /// <summary> /// PropertyDescription /// </summary> public string LastName { get { return this.lastName; } set { this.SetProperty(ref this.lastName, value); } } #endregion #region Age private int age; /// <summary> /// PropertyDescription /// </summary> public int Age { get { return this.age; } set { this.SetProperty(ref this.age, value); } } #endregion public MyItemViewModel() { } }
Basic1Page.xaml
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" prism:ViewModelLocator.AutowireViewModel="True" x:Class="XFListView.Views.Basic1Page" Title="ListView 基本應用 可修改紀錄"> <StackLayout> <ListView ItemsSource="{Binding MyItemList, Mode=TwoWay}" SelectedItem="{Binding MyItemListSelected, Mode=TwoWay}" Margin="20,0" > <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="36" /> </Grid.ColumnDefinitions> <Label Grid.Column="0" VerticalOptions="Center" TextColor="Teal" FontSize="20" Text="{Binding FirstName, Mode=TwoWay}" /> <Label Grid.Column="1" VerticalOptions="Center" TextColor="Teal" FontSize="20" Text="{Binding LastName, Mode=TwoWay}" /> <Label Grid.Column="2" VerticalOptions="Center" TextColor="Blue" FontSize="30" Text="{Binding Age, Mode=TwoWay}" /> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> <StackLayout Orientation="Vertical"> <StackLayout Orientation="Horizontal" HorizontalOptions="Center"> <Button Text="新增" Command="{Binding 新增Command}" /> <Button Text="修改" Command="{Binding 修改Command}" /> <Button Text="刪除" Command="{Binding 刪除Command}" /> </StackLayout> </StackLayout> </StackLayout> </ContentPage>
Basic1PageViewModel.cs
using Prism.Commands; using Prism.Mvvm; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using XFListView.Repositories; namespace XFListView.ViewModels { public class Basic1PageViewModel : BindableBase { private ObservableCollection<MyItemViewModel> myItemList; public ObservableCollection<MyItemViewModel> MyItemList { get { return myItemList; } set { SetProperty(ref myItemList, value); } } private MyItemViewModel myItemListSelected; public MyItemViewModel MyItemListSelected { get { return myItemListSelected; } set { SetProperty(ref myItemListSelected, value); } } public DelegateCommand 新增Command { get; private set; } public DelegateCommand 修改Command { get; private set; } public DelegateCommand 刪除Command { get; private set; } public Basic1PageViewModel() { MyItemList = MyItemRepository.SampleViewModel(); 新增Command = new DelegateCommand(新增); 修改Command = new DelegateCommand(修改); 刪除Command = new DelegateCommand(刪除); } private void 刪除() { var fooItem = MyItemList.FirstOrDefault(x => x.FirstName == myItemListSelected.FirstName && x.LastName == myItemListSelected.LastName && x.Age == myItemListSelected.Age); if (fooItem != null) { MyItemList.Remove(fooItem); } } private void 修改() { if (myItemListSelected != null) { #region 直接修改綁定的物件 myItemListSelected.FirstName = "艾嬡"; myItemListSelected.LastName = "林"; myItemListSelected.Age = 33; #endregion } } private void 新增() { MyItemList.Add(new MyItemViewModel { FirstName = "文君", LastName = "簡", Age = 19 }); } } }
MyItemViewModel.cs
using Prism.Commands; using Prism.Mvvm; using System; using System.Collections.Generic; using System.Linq; namespace XFListView.ViewModels { public class MyItemViewModel : BindableBase { #region FirstName private string firstName; /// <summary> /// PropertyDescription /// </summary> public string FirstName { get { return this.firstName; } set { this.SetProperty(ref this.firstName, value); } } #endregion #region LastName private string lastName; /// <summary> /// PropertyDescription /// </summary> public string LastName { get { return this.lastName; } set { this.SetProperty(ref this.lastName, value); } } #endregion #region Age private int age; /// <summary> /// PropertyDescription /// </summary> public int Age { get { return this.age; } set { this.SetProperty(ref this.age, value); } } #endregion public MyItemViewModel() { } } }
ListView 固定紀錄列高 (RowHeightPage)
RowHeight
來指定,例如:RowHeight="60"
就是指定每筆紀錄要顯示的高度是60。RowHeightPage.xaml
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" prism:ViewModelLocator.AutowireViewModel="True" x:Class="XFListView.Views.RowHeightPage" Title="固定紀錄列高" > <StackLayout> <ListView x:Name="lv" ItemsSource="{Binding MyItemList, Mode=TwoWay}" Margin="20,0" RowHeight="60" SeparatorColor="Red" > <ListView.ItemTemplate> <DataTemplate> <ViewCell> <StackLayout> <BoxView Color="Transparent" HeightRequest="10" /> <Label TextColor="Teal" FontSize="30" Text="{Binding FirstName}" /> <BoxView Color="Transparent" HeightRequest="10" /> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage>
RowHeightPageViewModel.cs
ListView 可變紀錄列高 (VariRowHeithtPage)
HasUnevenRows
來指定,例如:RowHeight="60"
就是指定每筆紀錄要顯示高度會依當時資料的高度而定。VariRowHeithtPage.xaml
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" prism:ViewModelLocator.AutowireViewModel="True" Title="可變紀錄列高" x:Class="XFListView.Views.VariRowHeithtPage"> <StackLayout> <ListView x:Name="lv" ItemsSource="{Binding MyItemList, Mode=TwoWay}" Margin="20,0" HasUnevenRows="True" SeparatorColor="Red" > <ListView.ItemTemplate> <DataTemplate> <ViewCell> <StackLayout> <BoxView Color="Transparent" HeightRequest="10" /> <Label TextColor="Teal" FontSize="30" Text="{Binding FirstName}" /> <BoxView Color="Transparent" HeightRequest="10" /> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage>
VariRowHeithtPageViewModel.cs
ListView 客製化資料列與資料按鈕會失效 (CustomButtonPage)
<Button Grid.Column="2" Text="進行互動" Command="{Binding 進行互動Command}" CommandParameter="{Binding .}" />
進行互動Command
,和其觸發後要執行的動作。可是,為什麼沒有觸發這個 ICommand 物件呢?public CustomButtonPageViewModel(IPageDialogService dialogService) { _dialogService = dialogService; MyData = PersonRepository.SampleViewModel(); 進行互動Command = new DelegateCommand<PersonViewModel>(進行互動); } private async void 進行互動(PersonViewModel obj) { await _dialogService.DisplayAlertAsync("請確定", $"您選取了 {obj.Name}", "確定"); }
進行互動Command
ICommand 物件,並不存在於每筆 ListView 的紀錄內,每筆紀錄要顯示的資料是定義在PersonViewModel
這個類別上。雖然其有繼承了BindableBase
類別與實作了INotifyPropertyChanged
介面,可以,在這個類別內,卻沒有定義任何 ICommand 物件,這將導致 ListView 內的每筆紀錄,沒有可用的 ICommand 物件可用於綁定繫結之用。public class PersonViewModel : BindableBase { #region Name private string name; /// <summary> /// Name /// </summary> public string Name { get { return this.name; } set { this.SetProperty(ref this.name, value); } } #endregion #region Birthday private DateTime birthday; /// <summary> /// Birthday /// </summary> public DateTime Birthday { get { return this.birthday; } set { this.SetProperty(ref this.birthday, value); } } #endregion #region FavoriteColor private Color favoriteColor; /// <summary> /// FavoriteColor /// </summary> public Color FavoriteColor { get { return this.favoriteColor; } set { this.SetProperty(ref this.favoriteColor, value); } } #endregion public PersonViewModel() { } }
CustomButtonPage.xaml
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" prism:ViewModelLocator.AutowireViewModel="True" x:Class="XFListView.Views.CustomButtonPage" Title="客製化資料列與資料按鈕會失效" > <StackLayout> <ListView ItemsSource="{Binding MyData}" SelectedItem="{Binding MyDataSelected, Mode=TwoWay}" Margin="20,0" > <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width="70" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="120" /> </Grid.ColumnDefinitions> <BoxView Grid.Column="0" BackgroundColor="{Binding FavoriteColor}" VerticalOptions="Center" HorizontalOptions="Center" HeightRequest="40" WidthRequest="40"/> <StackLayout Grid.Column="1" Orientation="Vertical"> <Label TextColor="Teal" FontSize="14" Text="{Binding Name}" /> <Label TextColor="Teal" FontSize="14" Text="{Binding Birthday, StringFormat='{0:yyyy-MM-dd}'}" /> </StackLayout> <Button Grid.Column="2" Text="進行互動" Command="{Binding 進行互動Command}" CommandParameter="{Binding .}" /> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage>
CustomButtonPageViewModel.cs
using Prism.Commands; using Prism.Mvvm; using System; using System.Collections.Generic; using System.Linq; using System.Collections.ObjectModel; using XFListView.Repositories; using Prism.Services; namespace XFListView.ViewModels { public class CustomButtonPageViewModel : BindableBase { #region MyData private ObservableCollection<PersonViewModel> myData; /// <summary> /// MyData /// </summary> public ObservableCollection<PersonViewModel> MyData { get { return this.myData; } set { this.SetProperty(ref this.myData, value); } } #endregion #region MyDataSelected private PersonViewModel myDataSelected; /// <summary> /// MyDataSelected /// </summary> public PersonViewModel MyDataSelected { get { return this.myDataSelected; } set { this.SetProperty(ref this.myDataSelected, value); } } #endregion public readonly IPageDialogService _dialogService; public DelegateCommand<PersonViewModel> 進行互動Command { get; private set; } public CustomButtonPageViewModel(IPageDialogService dialogService) { _dialogService = dialogService; MyData = PersonRepository.SampleViewModel(); 進行互動Command = new DelegateCommand<PersonViewModel>(進行互動); } private async void 進行互動(PersonViewModel obj) { await _dialogService.DisplayAlertAsync("請確定", $"您選取了 {obj.Name}", "確定"); } } }
ListView 客製化資料列與資料按鈕 (CustomButton1Page)
PersonViewModel
這個類別。PersonViewModel
類別,使用這個PersonWithButtonViewModel
類別來取代,在這個類別中,經會定義了 ICommand 的物件,如下所示,這樣,就解決了使用者可以在每筆 ListView 紀錄上按下按鈕,並且有回應的功能。using Microsoft.Practices.Unity; using Prism.Commands; using Prism.Mvvm; using Prism.Services; using System; using System.Collections.Generic; using System.Linq; using Xamarin.Forms; namespace XFListView.ViewModels { public class PersonWithButtonViewModel : BindableBase { #region Name private string name; /// <summary> /// Name /// </summary> public string Name { get { return this.name; } set { this.SetProperty(ref this.name, value); } } #endregion #region Birthday private DateTime birthday; /// <summary> /// Birthday /// </summary> public DateTime Birthday { get { return this.birthday; } set { this.SetProperty(ref this.birthday, value); } } #endregion #region FavoriteColor private Color favoriteColor; /// <summary> /// FavoriteColor /// </summary> public Color FavoriteColor { get { return this.favoriteColor; } set { this.SetProperty(ref this.favoriteColor, value); } } #endregion public readonly IPageDialogService _dialogService; public DelegateCommand<PersonWithButtonViewModel> 進行互動Command { get; private set; } public DelegateCommand<PersonWithButtonViewModel> 立即產生Command { get; private set; } public DelegateCommand<PersonWithButtonViewModel> 刪除Command { get; private set; } public PersonWithButtonViewModel() { #region 使用 Prism 的容器 IUnityContainer,解析 IPageDialogService 物件 // 在這裡不使用建構式注入,是因為,PersonRepository.SampleWithButtonViewModel() 要產生的清單物件,不是透過 Unity 所建立的 // 所以,手動進行物件解析,取得實作的物件 var fooApp = App.Current as App; IUnityContainer fooIUnityContainer = fooApp.Container; _dialogService = fooApp.Container.Resolve<IPageDialogService>(); #endregion 進行互動Command = new DelegateCommand<PersonWithButtonViewModel>(進行互動); 立即產生Command = new DelegateCommand<PersonWithButtonViewModel>(立即產生); 刪除Command = new DelegateCommand<PersonWithButtonViewModel>(刪除); } private async void 進行互動(PersonWithButtonViewModel obj) { await _dialogService.DisplayAlertAsync("請確定", $"您選取了 {obj.Name}", "確定"); } private async void 刪除(PersonWithButtonViewModel obj) { await _dialogService.DisplayAlertAsync("請確定", $"準備刪除 {obj.Name}", "確定"); } private async void 立即產生(PersonWithButtonViewModel obj) { await _dialogService.DisplayAlertAsync("請確定", $"立即執行 {obj.Name}", "確定"); } } }
CustomButton1Page.xaml
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" prism:ViewModelLocator.AutowireViewModel="True" x:Class="XFListView.Views.CustomButton1Page" Title="客製化資料列與資料按鈕" > <StackLayout> <ListView ItemsSource="{Binding MyData}" SelectedItem="{Binding MyDataSelected, Mode=TwoWay}" Margin="20,0" > <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width="70" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="120" /> </Grid.ColumnDefinitions> <BoxView Grid.Column="0" BackgroundColor="{Binding FavoriteColor}" VerticalOptions="Center" HorizontalOptions="Center" HeightRequest="40" WidthRequest="40"/> <StackLayout Grid.Column="1" Orientation="Vertical"> <Label TextColor="Teal" FontSize="14" Text="{Binding Name}" /> <Label TextColor="Teal" FontSize="14" Text="{Binding Birthday, StringFormat='{0:yyyy-MM-dd}'}" /> </StackLayout> <Button Grid.Column="2" Text="進行互動" Command="{Binding 進行互動Command}" CommandParameter="{Binding .}" /> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage>
CustomButton1PageViewModel.cs
using Prism.Commands; using Prism.Mvvm; using System; using System.Collections.Generic; using System.Linq; using System.Collections.ObjectModel; using XFListView.Repositories; using Prism.Services; namespace XFListView.ViewModels { public class CustomButton1PageViewModel : BindableBase { #region MyData private ObservableCollection<PersonWithButtonViewModel> myData; /// <summary> /// MyData /// </summary> public ObservableCollection<PersonWithButtonViewModel> MyData { get { return this.myData; } set { this.SetProperty(ref this.myData, value); } } #endregion #region MyDataSelected private PersonWithButtonViewModel myDataSelected; /// <summary> /// MyDataSelected /// </summary> public PersonWithButtonViewModel MyDataSelected { get { return this.myDataSelected; } set { this.SetProperty(ref this.myDataSelected, value); } } #endregion public readonly IPageDialogService _dialogService; public DelegateCommand<PersonViewModel> 進行互動Command { get; private set; } public CustomButton1PageViewModel(IPageDialogService dialogService) { _dialogService = dialogService; MyData = PersonRepository.SampleWithButtonViewModel(); 進行互動Command = new DelegateCommand<PersonViewModel>(進行互動); } private async void 進行互動(PersonViewModel obj) { await _dialogService.DisplayAlertAsync("請確定", $"您選取了 {obj.Name}", "確定"); } } }
ListView 頁首頁尾、自動捲動、互動功能表 (HeaderFooterPage)
ListView.Header
&ListView.Footer
來宣告 ListView 的頭與尾要出先甚麼樣貌。ViewCell
裡面,使用ViewCell.ContextActions
來宣告互動功能表的定義,如下所示<ViewCell.ContextActions> <MenuItem Text="立即產生" Command="{Binding 立即產生Command}" CommandParameter="{Binding .}" /> <MenuItem Text="刪除" Command="{Binding 刪除Command}" CommandParameter="{Binding .}" IsDestructive="True" /> </ViewCell.ContextActions>
HeaderFooterPage.xaml
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" prism:ViewModelLocator.AutowireViewModel="True" x:Class="XFListView.Views.HeaderFooterPage" Title="頁首頁尾、自動捲動、互動功能表" > <StackLayout> <ListView ItemsSource="{Binding MyData}" SelectedItem="{Binding MyDataSelected, Mode=TwoWay}" Margin="20,0" > <ListView.Header> <StackLayout Orientation="Horizontal"> <Label Text="頁首標題" TextColor="Green" FontSize="40" BackgroundColor="Yellow" /> </StackLayout> </ListView.Header> <ListView.Footer> <StackLayout Orientation="Horizontal"> <Label Text="頁尾標題" TextColor="White" FontSize="40" BackgroundColor="Gray" /> </StackLayout> </ListView.Footer> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <ViewCell.ContextActions> <MenuItem Text="立即產生" Command="{Binding 立即產生Command}" CommandParameter="{Binding .}" /> <MenuItem Text="刪除" Command="{Binding 刪除Command}" CommandParameter="{Binding .}" IsDestructive="True" /> </ViewCell.ContextActions> <ViewCell.View> <Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width="70" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="120" /> </Grid.ColumnDefinitions> <BoxView Grid.Column="0" BackgroundColor="{Binding FavoriteColor}" VerticalOptions="Center" HorizontalOptions="Center" HeightRequest="40" WidthRequest="40"/> <StackLayout Grid.Column="1" Orientation="Vertical"> <Label TextColor="Teal" FontSize="14" Text="{Binding Name}" /> <Label TextColor="Teal" FontSize="14" Text="{Binding Birthday, StringFormat='{0:yyyy-MM-dd}'}" /> </StackLayout> <Button Grid.Column="2" Text="進行互動" Command="{Binding 進行互動Command}" CommandParameter="{Binding .}" /> </Grid> </ViewCell.View> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage>
HeaderFooterPageViewModel.cs
using Microsoft.Practices.Unity; using Prism.Commands; using Prism.Mvvm; using Prism.Services; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Xamarin.Forms; using XFListView.Repositories; namespace XFListView.ViewModels { public class HeaderFooterPageViewModel : BindableBase { #region MyData private ObservableCollection<PersonWithButtonViewModel> myData; /// <summary> /// MyData /// </summary> public ObservableCollection<PersonWithButtonViewModel> MyData { get { return this.myData; } set { this.SetProperty(ref this.myData, value); } } #endregion #region MyDataSelected private PersonWithButtonViewModel myDataSelected; /// <summary> /// MyDataSelected /// </summary> public PersonWithButtonViewModel MyDataSelected { get { return this.myDataSelected; } set { this.SetProperty(ref this.myDataSelected, value); } } #endregion public HeaderFooterPageViewModel() { myData = PersonRepository.SampleWithButtonViewModel(); } } }
ListView 自動捲動到指定紀錄 (ScrollToPage)
listview.ScrollTo(personWithButtonViewModel, ScrollToPosition.Start, true);
來做到這樣的需求。ScrollToPage.xaml
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" prism:ViewModelLocator.AutowireViewModel="True" x:Class="XFListView.Views.ScrollToPage" Title="自動捲動到指定紀錄" > <StackLayout> <ListView x:Name="listview" ItemsSource="{Binding MyData}" SelectedItem="{Binding MyDataSelected, Mode=TwoWay}" Margin="20,0" > <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width="70" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <BoxView Grid.Column="0" BackgroundColor="{Binding FavoriteColor}" VerticalOptions="Center" HorizontalOptions="Center" HeightRequest="40" WidthRequest="40"/> <StackLayout Grid.Column="1" Orientation="Vertical"> <Label TextColor="Teal" FontSize="14" Text="{Binding Name}" /> <Label TextColor="Teal" FontSize="14" Text="{Binding Birthday, StringFormat='{0:yyyy-MM-dd}'}" /> </StackLayout> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> <StackLayout Orientation="Horizontal"> <Button Text="捲到2Zachary" Command="{Binding 捲到2ZacharyCommand}" /> <Button Text="捲到Bob" Command="{Binding 捲到BobCommand}" /> </StackLayout> </StackLayout> </ContentPage>
ScrollToPage.xaml.cs
using Xamarin.Forms; using XFListView.ViewModels; namespace XFListView.Views { public partial class ScrollToPage : ContentPage { public ScrollToPageViewModel fooScrollToPageViewModel; public ScrollToPage() { InitializeComponent(); fooScrollToPageViewModel = this.BindingContext as ScrollToPageViewModel; fooScrollToPageViewModel.自動捲動到 = this.自動捲動到; } public void 自動捲動到(PersonWithButtonViewModel personWithButtonViewModel) { listview.ScrollTo(personWithButtonViewModel, ScrollToPosition.Start, true); } } }
ScrollToPageViewModel.cs
using Prism.Commands; using Prism.Mvvm; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using XFListView.Repositories; namespace XFListView.ViewModels { public class ScrollToPageViewModel : BindableBase { #region MyData private ObservableCollection<PersonWithButtonViewModel> myData; /// <summary> /// MyData /// </summary> public ObservableCollection<PersonWithButtonViewModel> MyData { get { return this.myData; } set { this.SetProperty(ref this.myData, value); } } #endregion #region MyDataSelected private PersonWithButtonViewModel myDataSelected; /// <summary> /// MyDataSelected /// </summary> public PersonWithButtonViewModel MyDataSelected { get { return this.myDataSelected; } set { this.SetProperty(ref this.myDataSelected, value); } } #endregion public DelegateCommand 捲到2ZacharyCommand { get; private set; } public DelegateCommand 捲到BobCommand { get; private set; } public Action<PersonWithButtonViewModel> 自動捲動到; public ScrollToPageViewModel() { MyData = PersonRepository.SampleWithButtonViewModel(); 捲到2ZacharyCommand = new DelegateCommand(捲到2Zachary); 捲到BobCommand = new DelegateCommand(捲到Bob); } private void 捲到Bob() { if (自動捲動到 != null) { var fooItem = MyData.FirstOrDefault(x => x.Name == "Bob"); if (fooItem != null) { 自動捲動到(fooItem); } } } private void 捲到2Zachary() { if (自動捲動到 != null) { var fooItem = MyData.FirstOrDefault(x => x.Name == "2Zachary"); if (fooItem != null) { 自動捲動到(fooItem); } } } } }
ListView 下拉更新 (PullToRefreshPage)
IsPullToRefreshEnabled="True"
。RefreshCommand="{Binding 更新資料Command}"
命令屬性與IsRefreshing="{Binding IsBusy, Mode=TwoWay}"
屬性;也就是說,當進入到更新模式,更新資料Command
命令就會被呼叫,而 ViewModel 的 IsBusy 屬性會變成 True。更新資料Command
命令要呼叫的方法,透過非同步的方式取得最新的資料,完成之後,設定IsBusy
屬性為 True,讓 ListView 控制項不會顯示正在忙碌的視覺。private async void 更新資料() { await 從網路取得最新資料(); IsBusy = false; } public async Task 從網路取得最新資料() { await Task.Delay(3000); var fooMyData = PersonRepository.SampleWithButtonViewModel(); foreach (var item in fooMyData) { MyData.Add(item); } }
PullToRefreshPage.xaml
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" prism:ViewModelLocator.AutowireViewModel="True" x:Class="XFListView.Views.PullToRefreshPage" Title="下拉更新" > <StackLayout> <ListView x:Name="listview" ItemsSource="{Binding MyData}" SelectedItem="{Binding MyDataSelected, Mode=TwoWay}" Margin="20,0" IsPullToRefreshEnabled="True" RefreshCommand="{Binding 更新資料Command}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}" > <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width="70" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <BoxView Grid.Column="0" BackgroundColor="{Binding FavoriteColor}" VerticalOptions="Center" HorizontalOptions="Center" HeightRequest="40" WidthRequest="40"/> <StackLayout Grid.Column="1" Orientation="Vertical"> <Label TextColor="Teal" FontSize="14" Text="{Binding Name}" /> <Label TextColor="Teal" FontSize="14" Text="{Binding Birthday, StringFormat='{0:yyyy-MM-dd}'}" /> </StackLayout> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage>
PullToRefreshPageViewModel.cs
using Prism.Commands; using Prism.Mvvm; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using XFListView.Repositories; namespace XFListView.ViewModels { public class PullToRefreshPageViewModel : BindableBase { #region MyData private ObservableCollection<PersonWithButtonViewModel> myData; /// <summary> /// MyData /// </summary> public ObservableCollection<PersonWithButtonViewModel> MyData { get { return this.myData; } set { this.SetProperty(ref this.myData, value); } } #endregion #region IsBusy private bool isBusy; /// <summary> /// IsBusy /// </summary> public bool IsBusy { get { return this.isBusy; } set { this.SetProperty(ref this.isBusy, value); } } #endregion public DelegateCommand 更新資料Command { get; private set; } public PullToRefreshPageViewModel() { MyData = PersonRepository.SampleWithButtonViewModel(); 更新資料Command = new DelegateCommand(更新資料); } private async void 更新資料() { await 從網路取得最新資料(); IsBusy = false; } public async Task 從網路取得最新資料() { await Task.Delay(3000); var fooMyData = PersonRepository.SampleWithButtonViewModel(); foreach (var item in fooMyData) { MyData.Add(item); } } } }
ListView 點選與取消選取 (ItemClickPage)
xmlns:behaviors="clr-namespace:Behaviors;assembly=Behaviors"
這個命名空間,並且要在專案內安裝Behaviors.Forms
NuGet 套件。ItemTapped
事件發生的時候,就會執行綁定的 ICommandMyDataClickedCommand
方法;在這裡,請您測試看看,若將EventName="ItemTapped"
改成EventName="ItemSelected"
,整個執行過程中會有甚麼不同呢?<ListView.Behaviors> <behaviors:EventHandlerBehavior EventName="ItemTapped"> <behaviors:InvokeCommandAction Command="{Binding MyDataClickedCommand}" /> </behaviors:EventHandlerBehavior> </ListView.Behaviors>
ItemClickPage.xaml
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" prism:ViewModelLocator.AutowireViewModel="True" xmlns:behaviors="clr-namespace:Behaviors;assembly=Behaviors" x:Class="XFListView.Views.ItemClickPage" Title="ListView 點選與取消選取"> <StackLayout> <ListView x:Name="listview" ItemsSource="{Binding MyData}" SelectedItem="{Binding MyDataSelected, Mode=TwoWay}" Margin="20,0" IsPullToRefreshEnabled="True" RefreshCommand="{Binding 更新資料Command}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}" > <ListView.Behaviors> <behaviors:EventHandlerBehavior EventName="ItemTapped"> <behaviors:InvokeCommandAction Command="{Binding MyDataClickedCommand}" /> </behaviors:EventHandlerBehavior> </ListView.Behaviors> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width="70" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <BoxView Grid.Column="0" BackgroundColor="{Binding FavoriteColor}" VerticalOptions="Center" HorizontalOptions="Center" HeightRequest="40" WidthRequest="40"/> <StackLayout Grid.Column="1" Orientation="Vertical"> <Label TextColor="Teal" FontSize="14" Text="{Binding Name}" /> <Label TextColor="Teal" FontSize="14" Text="{Binding Birthday, StringFormat='{0:yyyy-MM-dd}'}" /> </StackLayout> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage>
ItemClickPageViewModel.cs
using Prism.Commands; using Prism.Mvvm; using Prism.Navigation; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using XFListView.Repositories; namespace XFListView.ViewModels { public class ItemClickPageViewModel : BindableBase { private readonly INavigationService _navigationService; #region MyData private ObservableCollection<PersonWithButtonViewModel> myData; /// <summary> /// MyData /// </summary> public ObservableCollection<PersonWithButtonViewModel> MyData { get { return this.myData; } set { this.SetProperty(ref this.myData, value); } } #endregion #region MyDataSelected private PersonWithButtonViewModel myDataSelected; /// <summary> /// MyDataSelected /// </summary> public PersonWithButtonViewModel MyDataSelected { get { return this.myDataSelected; } set { this.SetProperty(ref this.myDataSelected, value); } } #endregion public DelegateCommand MyDataClickedCommand { get; private set; } public ItemClickPageViewModel(INavigationService navigationService) { _navigationService = navigationService; MyData = PersonRepository.SampleWithButtonViewModel(); MyDataClickedCommand = new DelegateCommand(MyDataClicked); } private async void MyDataClicked() { await _navigationService.NavigateAsync("Basic1Page"); // 若沒有加入底下這行,則當回到這個頁面的時候,這筆一樣有被選取 MyDataSelected = null; } } }
參考資料