這份影片將會帶領大家,從無到有的進行 Visual Studio 2017 + Xamarin
開發環境的安裝與相關設定
ObservableCollection<T>
的 C# 屬性,只要我們有增/修/刪 這個 ObservableCollection<T>
的物件內容,ListView 則會自動進行更新。ViewCell
的 BindingContext
物件,不是整個 ListView 要顯示的集合物件,而是,要顯示的那筆紀錄的物件。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>
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>
PropertyChanged.Fody
這個套件,因此,簡化了整個 ViewModel 的程式碼數量,關於要綁定到 View 中的各個屬性,其定義如下。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;
}
});
Views
資料夾內,找到 MainPage.xaml
檔案,滑鼠右擊這個項目,選擇 重新命名
,接著輸入 HomePage
。HomePage.xaml
檔案,找到 ContentPage 這個根節點。<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="PrismUnityApp2.Views.MainPage"
Title="MainPage">
x:Class="PrismUnityApp2.Views.MainPage"
修改成為 x:Class="PrismUnityApp2.Views.HomePage"
,並且按下 Ctrl+S 將此次修改的內容存檔,而 ContentPage 的定義將會成為<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="PrismUnityApp2.Views.HomePage"
Title="MainPage">
Views
資料夾內,打開 HomePage.xaml.cs
檔案。HomePage
,如下所示:除了類別名稱之外,建構式名稱也需要一併修正。
public partial class HomePage : ContentPage
{
public HomePage()
{
InitializeComponent();
}
}
App.xaml.cs
檔案,在 OnInitialized
方法內,呼叫 NavigationService.NavigateAsync
內的字串引數,把 MainPage
內容改成 HomePage
。RegisterTypes
方法內,將 Container.RegisterTypeForNavigation<MainPage>();
修改成為 Container.RegisterTypeForNavigation<HomePage>();
public partial class App : PrismApplication
{
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override void OnInitialized()
{
InitializeComponent();
NavigationService.NavigateAsync("NavigationPage/HomePage?title=Hello%20from%20Xamarin.Forms");
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<NavigationPage>();
Container.RegisterTypeForNavigation<HomePage>();
}
}
ViewModels
資料夾內,找到 MainPageViewModel
檔案,滑鼠右擊這個項目,選擇 重新命名
,接著輸入 HomePageViewModel
。是(Y)
SelectedItemCommand
,當使用者點選不同 Picker 項目後,將會執行這個命令) public class BindablePicker : Picker
{
public static void Init()
{
}
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create("ItemsSource",
typeof(IEnumerable), typeof(BindablePicker), null,
//propertyChanged: OnItemsSourceChanged);
propertyChanged: (bindable, oldvalue, newvalue) => ((BindablePicker)bindable).OnItemsSourceChanged(bindable, oldvalue, newvalue));
//propertyChanged: (bindable, oldvalue, newvalue) => ((WrapView)bindable).ItemsSource_OnPropertyChanged(bindable, oldvalue, newvalue));
public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create("SelectedItem",
typeof(IEnumerable), typeof(BindablePicker), null, BindingMode.TwoWay, propertyChanged: OnSelectedItemChanged);
public static readonly BindableProperty SelectedItemCommandProperty = BindableProperty.Create("SelectedItemCommand",
typeof(ICommand), typeof(BindablePicker), null);
public BindablePicker()
{
SelectedIndexChanged += (o, e) =>
{
if (SelectedIndex < 0 || ItemsSource == null || !ItemsSource.GetEnumerator().MoveNext())
{
SelectedItem = null;
return;
}
var index = 0;
foreach (var item in ItemsSource)
{
if (index == SelectedIndex)
{
SelectedItem = item;
break;
}
index++;
}
};
}
public ICommand SelectedItemCommand
{
get { return (ICommand)GetValue(SelectedItemCommandProperty); }
set { SetValue(SelectedItemCommandProperty, value); }
}
public IEnumerable ItemsSource
{
get
{
return (IEnumerable)GetValue(ItemsSourceProperty);
}
set
{
SetValue(ItemsSourceProperty, value);
}
}
public Object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set
{
if (SelectedItem != value)
{
SetValue(SelectedItemProperty, value);
InternalUpdateSelectedIndex();
if (SelectedItemCommand != null)
{
SelectedItemCommand.Execute(value);
}
}
}
}
public event EventHandler<SelectedItemChangedEventArgs> ItemSelected;
private void InternalUpdateSelectedIndex()
{
var selectedIndex = -1;
if (ItemsSource != null)
{
var index = 0;
foreach (var item in ItemsSource)
{
if (item != null && item.Equals(SelectedItem))
{
selectedIndex = index;
break;
}
index++;
}
}
SelectedIndex = selectedIndex;
}
public BindablePicker KeepBindablePicker = null;
private void OnItemsSourceChanged(BindableObject bindable, object oldval, object newval)
{
var boundPicker = (BindablePicker)bindable;
KeepBindablePicker = boundPicker;
var oldvalue = oldval as IEnumerable;
var newvalue = newval as IEnumerable;
if (oldvalue != null)
{
var observableCollection = oldvalue as INotifyCollectionChanged;
// Unsubscribe from CollectionChanged on the old collection
if (observableCollection != null)
observableCollection.CollectionChanged -= OnCollectionChanged;
}
if (newvalue != null)
{
var observableCollection = newvalue as INotifyCollectionChanged;
// Subscribe to CollectionChanged on the new collection
//and fire the CollectionChanged event to handle the items in the new collection
if (observableCollection != null)
observableCollection.CollectionChanged += OnCollectionChanged;
}
boundPicker.InternalUpdateSelectedIndex();
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
if (KeepBindablePicker == null)
{
return;
}
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var item in args.NewItems)
{
KeepBindablePicker.Items.Add(item as string);
}
break;
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Remove:
foreach (var item in args.OldItems)
{
KeepBindablePicker.Items.Remove(item as string);
}
break;
case NotifyCollectionChangedAction.Replace:
KeepBindablePicker.Items[args.NewStartingIndex] = args.NewItems[0] as string;
break;
case NotifyCollectionChangedAction.Reset:
KeepBindablePicker.Items.Clear();
break;
default:
break;
}
}
private static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
{
var boundPicker = (BindablePicker)bindable;
if (boundPicker.ItemSelected != null)
{
boundPicker.ItemSelected(boundPicker, new SelectedItemChangedEventArgs(newValue));
}
boundPicker.InternalUpdateSelectedIndex();
}
}
xmlns:customControl="clr-namespace:XFCorelPicker.CustomControls"
customControl
命名空間前置詞,引用我們開發的自訂控制項 BindablePicker
。SelectedItem
屬性,綁訂到 ViewModel 內的 .NET 屬性 SelectedMainCategory
,這樣若想要知道使用者點選了哪個項目的時候,在 ViewModel 內,只需要查看這個 .NET 屬性即可。ItemsSource
屬性,綁訂到 ViewModel 內的 .NET 屬性 MainCategoryList
,這樣我們便可以在 ViewModel 內,指定這個 Picker 可以選擇的項目清單內容。SelectedItemCommand
屬性,綁訂到 ViewModel 內的 .NET 屬性 MainCategoryChangeCommand
命令,這樣,當使用者選擇了不同的項目時候,就會執行呼叫這個命令。 <customControl:BindablePicker
Title="請選擇主分類"
SelectedItem="{Binding SelectedMainCategory}"
ItemsSource="{Binding MainCategoryList}"
SelectedItemCommand="{Binding MainCategoryChangeCommand}"
TextColor="Red"
/>
<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"
xmlns:customControl="clr-namespace:XFCorelPicker.CustomControls"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="XFCorelPicker.Views.MainPage"
Title="關聯式可綁定Picker Lab">
<StackLayout
Margin="30,0"
HorizontalOptions="FillAndExpand" VerticalOptions="Center">
<Label
Text="{Binding fooMyTask.Name}"/>
<customControl:BindablePicker
Title="請選擇主分類"
SelectedItem="{Binding SelectedMainCategory}"
ItemsSource="{Binding MainCategoryList}"
SelectedItemCommand="{Binding MainCategoryChangeCommand}"
TextColor="Red"
/>
<customControl:BindablePicker
Title="請選擇次分類"
SelectedItem="{Binding SelectedSubCategory}"
ItemsSource="{Binding SubCategoryList}"
TextColor="Red"
/>
<Button
Text="變更工作名稱"
Command="{Binding 變更工作名稱Command}"
/>
</StackLayout>
</ContentPage>
MainCategoryChangeCommand
命令執行的時候,我們便會重新定義 SubCategoryList
這個物件的集合項目內容,接著,透過資料綁定模式,頁面中的次分類 Picker,就會有與主分類選擇項目相關的清單可以選擇了。 MainCategoryChangeCommand = new DelegateCommand(() =>
{
SubCategoryList.Clear();
for (int i = 0; i < 50; i++)
{
SubCategoryList.Add($"{SelectedMainCategory} - {i}");
}
});
EventToCommandBehavior
做法;也就是,當主分類的 Picker 的 SelectedIndexChanged
事件觸發的時候,就會執行 MainCategoryChangeCommand
命令。EventToCommandBehaviorPage.xaml
/ EventToCommandBehaviorPageViewModel.cs
這兩個檔案。