XAML in Xamarin.Forms 基礎篇 電子書

XAML in Xamarin.Forms 基礎篇 電子書
XAML in Xamarin.Forms 基礎篇 電子書

Xamarin.Forms 快速入門 電子書

Xamarin.Forms 快速入門 電子書
Xamarin.Forms 快速入門 電子書

2016/10/05

Xamarin.Forms ListView 控制項的功能與特色

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式

ListView 是在進行開發行動裝置應用程式中最基本,也是最複雜的控制項,在這份筆記中,將會說明如何使用底下各類功能。
  • ListView 的資本操作 (資料綁定)
  • 紀錄異動時,ListView同步更新顯示(ObservableCollection)
  • 取得剛剛點選的項目資料內容
  • 設定每列固定高度
  • 不均勻(Uneven) 的每筆紀錄高度
  • 每筆紀錄的分隔線與顏色設定
  • 紀錄列上可以有按鈕
  • 頁首與頁尾
  • 紀錄的互動功能表 (ViewCell.ContextActions)
  • 手勢更新(Pull to Refresh)資料
  • 指定捲動到某筆資料上
  • 點選項目之後,會取消剛剛的選取
ListView功能與特色
這份筆記的專案原始碼將可從底下網址取得:

ListView 基本應用 不可修改紀錄 (BasicPage)

在這個範例頁面中,展示了如何使用 ListView 的基本功能
不可修改紀錄 不可修改紀錄
  • 如何透過 MVVM 方式,將要顯示的資料列綁定到 ListView 控制項上
    ItemsSource="{Binding MyItemList, Mode=TwoWay}"
    可以透過 ItemsSource 這個屬性,使用 XAML 延伸標記功能,進行資料綁定;例如 ItemsSource="{Binding MyItemList, Mode=TwoWay}"。而在 ViewModel 中,您需要使用ObservableCollection<MyItem> 來宣告 ListView 控制項所要顯示的資料來源,使用ObservableCollection 的目的,是您可以透過訂閱內建的 CollectionChanged 事件,便可以得知當時資料新增、修改、刪除等狀態。
    在 ListView 宣告標記中,每筆資料的內容,可以透過 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>
    
  • 如何取得點選的資料紀錄內容
    當使用者點選 ListView 上的任何一筆紀錄,其所點選的資料會呈現在 SelectedItem 屬性中,因此,透過了雙向資料繫結 SelectedItem="{Binding MyItemListSelected, Mode=TwoWay}" 就可以在檢視模型 (ViewModel) 內,取得當時使用者點選的那筆資料。而在 ViewModel 內的 MyItemListSelected 屬性,可以使用強型別的方式來宣告,這樣就可以在 ViewModel 內簡單的存取相關屬性。
  • 如何動態新增一筆資料到 ListView 內
    只要針對 ListView 的資料來源,也就是 MyItemList 物件,進行新增一筆物件,此時,在 ListView 上,就會同步更新了這筆資料。要了要能夠呈現在 ViewModel 中,新增一筆紀錄,而在 View 中也能夠動態更新,您需要使用 ObservableCollection<T> 來宣告使用紀錄列之物件。
          private ObservableCollection<MyItem> myItemList;
          public ObservableCollection<MyItem> MyItemList
          {
              get { return myItemList; }
              set { SetProperty(ref myItemList, value); }
          }
    
  • 如何動態刪除一筆資料到 ListView 內
    只要針對 ListView 的資料來源,也就是 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)

在這個範例頁面中,展示了如何使用修改 ListView 來源物件的定義,使其具有資料繫結能力,進而可以做到修改紀錄的能力。
可修改紀錄 可修改紀錄
在 XAML 頁面定義與上面的頁面定義一樣,不過,在 ViewModel 的 ListView 要綁定的資料來源定義上,修改為 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)

在這個範例頁面中,展示了如何使用指定 ListView 內每筆紀錄要顯示的高度。
  • 要設定每筆紀錄要顯示的高度,可以使用 ListView 的屬性 RowHeight 來指定,例如:RowHeight="60" 就是指定每筆紀錄要顯示的高度是60。
  • 在這個範例中,ViewModel 的每筆紀錄內容都很多,而且有設定高度,因此,多餘的部分,會被裁剪掉。
    固定紀錄列高 

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

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)
        {
        }
    }
}

ListView 可變紀錄列高 (VariRowHeithtPage)

在這個範例頁面中,展示了如何根據當時資料顯示的高度,自行調整每列的高度。
  • 要設定每筆紀錄要顯示的高度,可以使用 ListView 的屬性 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

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 客製化資料列與資料按鈕會失效 (CustomButtonPage)

在這個範例頁面中,展示了開發者可以在 ListView 的每筆紀錄上,放置按鈕 View,讓使用者點選,觸發這個按鈕命令或者事件;可是,當您使用底下的方式來宣告 XAML & 定義 ViewModel 的程式碼,卻發現到無法觸發每筆紀錄的按鈕事件。
客製化資料列與資料按鈕會失效 客製化資料列與資料按鈕會失效
在 ListView 的 ItemTemplate 內,確實有宣告一個按鈕與設定Command屬性並資料繫結到 ViewModel 上
<Button Grid.Column="2"
                      Text="進行互動"
                      Command="{Binding 進行互動Command}"
                      CommandParameter="{Binding .}"
                      />
在 ViewModel 內,也確實有宣告 ICommand 物件,即 進行互動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}", "確定");
        }
這是一般在剛開始接觸 MVVM 開發方法的開發者常犯的錯誤,要知道,在這個 ViewModel 內所定義的 進行互動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 這個類別。
客製化資料列與資料按鈕 客製化資料列與資料按鈕iOS
在這個範例中,將會把 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)

在這個範例頁面中,展示了如何使用頁首與頁尾的UI內容,並且可以使用 ListView 的互動功能表。
頁首頁尾自動捲動互動功能表 頁首頁尾自動捲動互動功能表
您可以使用 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)

在這個範例頁面中,展示了如何在 ViewModel 來執行 ListView 的自動捲動功能;要做到這樣的功能,需要使用 code-behind 來協助做到。
在 code behind 裡面,使用 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)

在這個範例頁面中,展示了如何使用 ListView 的下拉更新的手勢操作,也就是說,當在 ListView 的最上面顯示的是第一筆紀錄,此時,若使用手勢由上往下滑動,此時,就會自動通知 ListView 可以開始進入到資料更新模式,不過,若想要啟動這個功能,您的 ListView 需要設定屬性 IsPullToRefreshEnabled="True"
在 ListView 內,也綁定了 RefreshCommand="{Binding 更新資料Command}" 命令屬性與 IsRefreshing="{Binding IsBusy, Mode=TwoWay}" 屬性;也就是說,當進入到更新模式,更新資料Command 命令就會被呼叫,而 ViewModel 的 IsBusy 屬性會變成 True。
底下為 ViewModel 的 更新資料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)

在這個範例頁面中,展示了如何設計當使用者點選了某個項目的時候,就會執行相對應的 ICommand 所定義的方法;要達成不使用 Code Behind 來寫程式碼,而透過 ViewModel 來完成這樣的需求,您需要在 XAML 命名空間中引用 xmlns:behaviors="clr-namespace:Behaviors;assembly=Behaviors" 這個命名空間,並且要在專案內安裝 Behaviors.Forms NuGet 套件。
因此,您可以在 ListView 內,使用底下宣告標記,宣告當 ListView 的 ItemTapped 事件發生的時候,就會執行綁定的 ICommand MyDataClickedCommand 方法;在這裡,請您測試看看,若將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;
        }
    }
}

參考資料

沒有留言:

張貼留言