using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using XFListView.Repositories;
using Prism.Navigation;
namespace XFListView.ViewModels
{
public class RowHeightPageViewModel : BindableBase, INavigationAware
{
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 RowHeightPageViewModel()
{
MyItemList = MyItemRepository.SampleLongViewModel();
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public void OnNavigatedTo(NavigationParameters parameters)
{
}
}
}
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 VariRowHeithtPageViewModel : BindableBase, INavigationAware
{
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 VariRowHeithtPageViewModel()
{
MyItemList = MyItemRepository.SampleLongViewModel();
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public void OnNavigatedTo(NavigationParameters parameters)
{
}
}
}
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; } } }
參考資料