我們使用 Xamarin.Forms 進行對於 ListView 的使用,很多人都會覺得有些好奇,究竟相關 ListView 上的不同操作與應用,該如何撰寫 View / ViewModel 的 XAML / C# 程式碼呢?
我撰寫的一個範例專案(原始碼位於 https://github.com/vulcanlee/xamarin-forms-develop-notes-example/tree/master/XFListShowImg ),用來說明底下需求:
- 在Listview新增判斷邏輯,假如是值True,呈現某張圖,若是False,則不秀圖出來
- 當使用者點選某個紀錄上的 BoxView 控制項的時候,若該圖片沒有顯示出來,則需要修正,經圖片顯示出來。
這是一個上過課的學員提問的問題,我使用底下範例專案做出說明。
ListView 的 XAML
由於,我們需要將集合資料使用 ListView 顯示出來,當然,我們需要孰悉 ListView 的各種屬性的操作;在這裡,我們需要使用 ListView 的這些屬性
- ItemsSource : 將會綁訂到 ListView 上的
ObservableCollection<T>
的 C# 屬性,只要我們有增/修/刪 這個ObservableCollection<T>
的物件內容,ListView 則會自動進行更新。 - SelectedItem : 當使用者點選某個紀錄項目的時候,當時點選的紀錄項目,將會透過資料綁定方法,把當時點選的紀錄物件值,綁定到 ViewModel 上的指定屬性物件上。
- HasUnevenRows : 這個屬性用來指定,每個紀錄可以不用具有相同的高度。
每筆紀錄的顯示內容 ViewCell
要設計每筆紀錄在 ListView 上要呈現甚麼樣貌,我們就需要使用 ViewCell 來宣告;在底下,列出了這個 ViewCell 的完整定義。
我們使用了 Grid 將 ViewCell 切割成為 2 Rows X 2 Columns,並且將 Label / BoxView / Image 這三個控制項,使用Grid附加屬性,指定這些控制項是要位於 Grid 的哪個格子上。
在這些控制項上,要顯示的內容或者顏色,將會透過資料綁定的方式,指定到當時紀錄的ViewModel 中的屬性上,在這裡要特別注意到,此時,
ViewCell
的 BindingContext
物件,不是整個 ListView 要顯示的集合物件,而是,要顯示的那筆紀錄的物件。
關於圖片在某些情境(在這裡是依據 ViewModel 內的某個屬性值),不需要顯示出來;這樣的需求有很多種作法,也需要是開發環境的各種條件而定,在這裡,我們使用了
IsVisible="{Binding ShowImage}"
這樣的方式來處理,只要該記錄在 ViewModel 內的 ShowImage 的值為 False 的布林值,他就不會顯示在螢幕上,反之,就會顯示在螢幕上。 <ViewCell>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Label
Grid.Row="0" Grid.Column="0"
Text="{Binding Number}"/>
<BoxView
Grid.Row="1" Grid.Column="0"
Color="{Binding BoxColor}">
</BoxView>
<Image
Grid.Row="0" Grid.Column="1"
Grid.RowSpan="2"
HorizontalOptions="Fill" VerticalOptions="Fill"
Aspect="AspectFill"
Source="{Binding ImageUrl}"
IsVisible="{Binding ShowImage}"/>
</Grid>
</ViewCell>
如何指定 ViewCell 內的命令,要綁定到 ViewModel 中的命令物件
我們這裡有個需求,那就是,使用者可以點選 BoxView 這個控制項,接著,我們需要判斷這個紀錄的圖片是否沒有顯示出來,若沒有,我們須將這個圖片切換成為可以顯示的模式。
我們想要將這個手勢操作命令定義在頁面用的 ViewModel 上,而不是在每筆紀錄的 ViewModel 上。
為了解決這個問題,我們需要在設定綁定命令的時候,變更當時的 BindingContext 的來源,在這裡,我們使用了底下方法:
我們設定了 BoxView.GestureRecognizers 的屬性,加入了
TapGestureRecognizer
這個物件;不過,當要使用 TapGestureRecognizer
的 Command 屬性的時候,我們使用了 Source
這個屬性,指定此次綁定的 BindingContext 的來源,是這個頁面的 ViewModel,接著,我們使用了 Path
這個屬性,指定了要綁定的命令路徑。
其中,
x:Reference ThisPage
的 ThisPage 指的就是這個頁面,因為,我們在頁面上,設定了 x:Name="ThisPage"
這個延伸標記屬性設定。 <BoxView
Grid.Row="1" Grid.Column="0"
Color="{Binding BoxColor}">
<BoxView.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Path=BindingContext.TapBoxCommand, Source={x:Reference ThisPage}}"
CommandParameter="{Binding}"/>
</BoxView.GestureRecognizers>
</BoxView>
ViewModel 內的程式碼
在這裡,我們使用了
PropertyChanged.Fody
這個套件,因此,簡化了整個 ViewModel 的程式碼數量,關於要綁定到 View 中的各個屬性,其定義如下。
在這裡,要綁定到 ListView 上的集合資料,需要使用
ObservableCollection<MyItem>
這個型別,而命令的部分,我們使用了 Prism 提供的 DelegateCommand<MyItem>
的命令型別,這個泛型命令型別,可以讓我們接收到,來自 XAML 的 CommandParameter 屬性所綁定的內容,在這個應用範例中,CommandParameter 將會綁定當時所顯示的紀錄物件。
若您不想使用
DelegateCommand<MyItem>
泛型命令型別,您也可以使用 DelegateCommand
這個型別,不過,要取得使用者當時所點選的物件,您可以使用 SelectedMyItem
這個物件來取得當時點選的紀錄物件。 public MyItem SelectedMyItem { get; set; }
public ObservableCollection<MyItem> MyItems { get; set; } = new ObservableCollection<MyItem>();
public DelegateCommand<MyItem> TapBoxCommand { get; set; }
當使用點選某筆紀錄後,將會執行底下程式碼:
TapBoxCommand = new DelegateCommand<MyItem>(x =>
{
if(x.ShowImage == false)
{
x.ShowImage = true;
x.BoxColor = Color.BlueViolet;
}
});