XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

Xamarin.Forms 快速入門 電子書
Xamarin.Forms 快速入門 電子書
顯示具有 XAML 標籤的文章。 顯示所有文章
顯示具有 XAML 標籤的文章。 顯示所有文章

2018/08/28

使用 Visual Studio 工具箱 Toolbox 來設計頁面 XAML 語言

使用 Visual Studio 2017 15.8 工具箱 Toolbox 來設計頁面 XAML 語言


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

在以往我們進行 Xamarin.Forms 專案開發的時候,我們需要使用 C# 來設計這個行動應用程式的商業邏輯運作行為,對於這個行動應用程式,我們將會透過 XAML 宣告標記語言來設計可用於跨平台的頁面之使用者介面內容。不過,要如何使用這項功能呢,您需要升級 VS4W Visual Studio 2017 for Windows 到 Visual Studio 2017 15.8 版本,並且升級到 Xamarin.Forms NuGet 套件到 3.0 以上的版本。
Xamarin.Forms Toolbar
讓我們使用 Prism Template Pack (2.0.9版本) 來建立一個 Xamarin.Forms 專案
Xamarin.Forms Prism Template Pack
當專案建立完成之後,我們打開 MainPage.xaml 檔案,接著,請打開工具箱視窗,若您找不到工具箱視窗,可以點選 Visual Studio 2017 功能表,點選 [檢視] > [工具箱]。不過,您會很失望,雖然,我這裡的 VS2017 已經升級到 15.8 以上版本,可是,還是看不到關於 Xamarin.Form XAML 的工具箱。
Xamarin.Forms MainPage.xaml
檢查一下您的專案使用的 Xamarin.Forms NuGet 套件,結果發現到 Prism Template Pack 專案樣板建立起來的 Xamarin.Forms 專案,使用的是 2.5.0.122203 版本的 NuGet 套件。
Xamarin.Forms Prism Template Pack
因此,我們需要把整個方案中的所有專案都升級到 Xamarin.Forms 3.0 以上版本的 NuGet 套件,不過,若您只是升級 .NET Standard 專案中的 Xamarin.Forms NuGet 套件到最新版本,您將會得到底下錯誤訊息;所以,記得要把所有方案內的專案,都升級 Xamarin.Forms NuGet 套件到最新版本
Xamarin.Forms NuGet 套件
Xamarin.Forms tasks do not match targets. Please ensure that all projects reference the same version of Xamarin.Forms, and if the error persists, please restart the IDE.    XFToolbar.Android
最後,若您完成了上述動作,您可以切換到 MainPage.xaml 視窗中,打開工具箱視窗,您就會看到了 XAML 可以使用到的各個控制項。您可以點選工具箱中 [Controls] / [Layouts] / [Cells] 的任一項目,拖拉到 XAML 適當地方,Visual Studio 就會幫您產生相關 XAML 語法出來。在這裡,我們從 [工具箱] > [Controls] 拖拉 [Entry] 控制項到右邊的 MainPage.xaml 視窗中的 </StackLayout> 項目前,此時,就會自動產生出 Entry 的 XAML 的語言宣告 <Entry Placeholder="" /> 出來
Xamarin.Forms NuGet 套件

關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程

2017/04/30

Xamarin.Forms 中,關聯式可綁定 Bindable Picker 練習

在 2017.2.27 看到一篇文章 New Bindable Picker Control for Xamarin.Forms,提到了 Xamarin.Forms 2.3.4 版本,將會提供可綁定的 Picker 控制項更新,剛好今天有空,就順手做了一下測試。
這事是要做個關聯連動式的 Picker 應用,在這個手機頁面中,將會有兩個 Picker
  • 第一個 Picker 將會是主分類的選單
  • 第二個 Picker 則會是次分類選單
使用者於點選完成主分類選單之後,次分類選單的 Picker 可以選擇的項目,將會根據主分類選單的選擇項目,自動產生出來;也就是說,第二個 Picker 內可以選擇的清單項目,會與第一個 Picker 所選擇的結果,產生連動的關係。
因此,立即使用 Xamarin.Forms 2.3.4 最新版的套件進行撰寫 View / ViewModel,可是,突然發現到,Xamarin.Forms 2.3.4 所提供的可綁定 Picker,卻沒有相對應的 Command 可以來設定,若要做到上述的功能,還是要繼續使用事件的方式,在 Code Behind 內寫相關的程式碼。
無奈之下,只好找回之前從網路上找到的可綁定 Picker(這個客製化控制項,提供了 SelectedItemCommand,當使用者點選不同 Picker 項目後,將會執行這個命令)
這個練習中的專案原始碼,您可以在底下 GitHub 中找到
底下將會說明如何做到這樣的功能:

建立一個可綁定Picker的自訂控制項 (Custom Control)

由於,我們只是要擴增原有 Picker 的功能,讓其切換選擇項目之後,可以執行命令,因此,我們這裡需要先建立這個自訂控制項,不過,因為該自訂控制項在各原生平台下所呈現的視覺不會有任何改變,所以,我們也不需要在每個平台,撰寫任何 Renderer 程式碼。
底下將會是這個可執行命令的可綁定 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();
        }

    }

宣告要測試的頁面 XAML 內容

由於我們需要引用我們設計的自訂控制項,因此,需要加入一個額外命名空間,指向到這個自訂控制項的 .NET 命名空間中。
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>

設計 ViewModel 內容

當 MainCategoryChangeCommand 命令執行的時候,我們便會重新定義 SubCategoryList 這個物件的集合項目內容,接著,透過資料綁定模式,頁面中的次分類 Picker,就會有與主分類選擇項目相關的清單可以選擇了。
            MainCategoryChangeCommand = new DelegateCommand(() =>
            {
                SubCategoryList.Clear();
                for (int i = 0; i < 50; i++)
                {
                    SubCategoryList.Add($"{SelectedMainCategory} - {i}");
                }
            });

補充說明

若您想要使用 Xamarin.Forms 2.3.4 的 Picker 做到同樣的效果,而不使用自訂 Picker 控制項,您可以使用上一篇文章 在 Xamarin.Forms 中,如何使用 Prism EventToCommandBehavior 提供的事件轉換到命令的行為 所提到的 EventToCommandBehavior 做法;也就是,當主分類的 Picker 的 SelectedIndexChanged 事件觸發的時候,就會執行 MainCategoryChangeCommand 命令。
實際的 View 宣告與 ViewModel 的程式碼邏輯,可以參考 EventToCommandBehaviorPage.xaml / EventToCommandBehaviorPageViewModel.cs 這兩個檔案。