XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2016/10/08

企業之差旅費用 跨平台應用程式開發 (5) 導航抽屜與差旅費用申請

現在已經完成該應用程式所需要的基礎應用服務類別,因此,可以開始進行開發整個應用程式的運作流程頁面。
在這個階段的練習,您將會需要完成學會底下需求:
  1. 建立導航使用的頁面檢視 (View)
  2. 建立導航使用的頁面檢視模型 (ViewModel)
  3. 註冊導航使用的頁面檢視
  4. 建立主系統導航抽屜頁面檢視 (View)
  5. 建立系統導航抽屜頁面檢視模型 (ViewModel)
  6. 註冊系統導航抽屜頁面
  7. 建立差旅費用清單資料項目檢視模型 (ViewModel)
  8. 建立差旅費用清單頁面檢視 (View)
  9. 建立差旅費用清單頁面檢視模型 (ViewModel)
  10. 註冊差旅費用清單頁面
  11. 建立差旅費用項目資料維護頁面檢視 (View)
  12. 建立差旅費用項目資料維護頁面檢視模型 (ViewModel)
  13. 註冊差旅費用項目資料維護頁面
  14. 解決 iOS 應用程式啟動會有例外異常問題
這篇章節的練習專案的原始程式碼將會存放在 GitHubhttps://github.com/vulcanlee/XFAppSample/tree/master/XFDoggy/4.MainRec 內

建立導航使用的頁面檢視 (View)

  1. 在核心PCL XFDoggy 專案內,使用滑鼠右鍵點選 ViewModels 資料夾,接著,選擇 加入 > 新增項目
  2. 在 加入新項目 - XFDoggy 對話窗中,點選 Prism > Prism NavigationPage (Forms)
  3. 在底下名稱欄位內,輸入 NaviPage,接著,點選 新增 按鈕
  4. 使用底下程式碼替換掉剛剛產生的檔案內容
<?xml version="1.0" encoding="utf-8" ?>
<NavigationPage 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="XFDoggy.Views.NaviPage"
                NavigationPage.BarBackgroundColor="{StaticResource ToolbarBackgroundColor}"
                NavigationPage.BarTextColor="#FFF"
  >

</NavigationPage>

建立導航使用的頁面檢視模型 (ViewModel)

  1. 在核心PCL XFDoggy 專案內,使用滑鼠右鍵點選 ViewModels 資料夾,接著,選擇 加入 > 新增項目
  2. 在 加入新項目 - XFDoggy 對話窗中,點選 Prism > Prism ViewModel
  3. 在底下名稱欄位內,輸入 NaviPageViewModel,接著,點選 新增 按鈕

註冊導航使用的頁面檢視

  1. 在核心PCL XFDoggy 專案內,開啟 App.xaml.cs 檔案
  2. 使用底下C#程式碼替換掉剛剛開啟的檔案內的 RegisterTypes 方法定義

App.xaml.cs

        protected override void RegisterTypes()
        {
            Container.RegisterTypeForNavigation<MainPage>();
            Container.RegisterTypeForNavigation<LoadingPage>();
            Container.RegisterTypeForNavigation<LoginPage>();
            Container.RegisterTypeForNavigation<NaviPage>();
        }

建立主系統導航抽屜頁面檢視 (View)

  1. 在核心PCL XFDoggy 專案內,使用滑鼠右鍵點選 ViewModels 資料夾,接著,選擇 加入 > 新增項目
  2. 在 加入新項目 - XFDoggy 對話窗中,點選 Prism > Prism MasterDetailPage (Forms)
  3. 在底下名稱欄位內,輸入 MainMDPage,接著,點選 新增 按鈕
  4. 使用底下程式碼替換掉剛剛產生的檔案內容

MainMDPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage 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="XFDoggy.Views.MainMDPage">

  <MasterDetailPage.Master >
    <ContentPage Title="功能表" Icon="hamburger.png">
      <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                  iOS="0,20,0,0" Android="0" WinPhone="0,0"
                  />
      </ContentPage.Padding>

      <ContentPage.Resources>
        <ResourceDictionary>
          <Style x:Key="MenuItemLevel1Style" TargetType="Label">
            <Setter Property="HorizontalOptions" Value="Start" />
            <Setter Property="VerticalOptions" Value="Center" />
            <Setter Property="FontSize" Value="16" />
            <Setter Property="TextColor" Value="Black" />
            <Setter Property="Margin" Value="35,10,0,0" />
          </Style>
          <Style x:Key="MenuItemLevel2Style" TargetType="Label">
            <Setter Property="HorizontalOptions" Value="Start" />
            <Setter Property="VerticalOptions" Value="Center" />
            <Setter Property="FontSize" Value="Large" />
            <Setter Property="TextColor" Value="White" />
            <Setter Property="Margin" Value="60,10,0,0" />
          </Style>
        </ResourceDictionary>
      </ContentPage.Resources>
      <StackLayout
        Orientation="Vertical" BackgroundColor="White"
        VerticalOptions="FillAndExpand">

        <Grid>
          <Image>
            <Image.Source>
              <OnPlatform x:TypeArguments="ImageSource">
                <OnPlatform.iOS>
                  <FileImageSource File="sidebar.png"/>
                </OnPlatform.iOS>
                <OnPlatform.Android>
                  <FileImageSource File="sidebar.png"/>
                </OnPlatform.Android>
                <OnPlatform.WinPhone>
                  <FileImageSource File="Assets/Images/sidebar.png"/>
                </OnPlatform.WinPhone>
              </OnPlatform>
            </Image.Source>
          </Image>

          <Image Margin="15,15,0,0"
                 WidthRequest="64" HeightRequest="64"
                 Aspect="Fill"
                 HorizontalOptions="Start" VerticalOptions="Start"
            >
            <Image.Source>
              <OnPlatform x:TypeArguments="ImageSource">
                <OnPlatform.iOS>
                  <FileImageSource File="Logo.png"/>
                </OnPlatform.iOS>
                <OnPlatform.Android>
                  <FileImageSource File="Logo.png"/>
                </OnPlatform.Android>
                <OnPlatform.WinPhone>
                  <FileImageSource File="Assets/Images/Logo.png"/>
                </OnPlatform.WinPhone>
              </OnPlatform>
            </Image.Source>
          </Image>

          <Label Text="多奇數位創意有限公司" TextColor="White" FontSize="18"
                 HorizontalOptions="Start" VerticalOptions="End"
                 Margin="15,0,0,15"
                 />
        </Grid>
        <Label
          Margin="35,30,0,0"
          Text ="差旅費用申請"
          Style="{StaticResource MenuItemLevel1Style}"
          TextColor="{Binding 差旅費用申請Color}"
          >
          <Label.GestureRecognizers>
            <TapGestureRecognizer
                Command="{Binding 差旅費用申請Command}"/>
          </Label.GestureRecognizers>
        </Label>
        <Label
          Text ="登出"
          Style="{StaticResource MenuItemLevel1Style}"
          TextColor="{Binding 登出Color}"
          >
          <Label.GestureRecognizers>
            <TapGestureRecognizer
                Command="{Binding 登出Command}"/>
          </Label.GestureRecognizers>
        </Label>

      </StackLayout>
    </ContentPage>
  </MasterDetailPage.Master>

</MasterDetailPage>

建立主系統導航抽屜頁面檢視模型 (ViewModel)

  1. 在核心PCL XFDoggy 專案內,使用滑鼠右鍵點選 ViewModels 資料夾,接著,選擇 加入 > 新增項目
  2. 在 加入新項目 - XFDoggy 對話窗中,點選 Prism > Prism ViewModel
  3. 在底下名稱欄位內,輸入 MainMDPageViewModel,接著,點選 新增 按鈕
  4. 使用底下程式碼替換掉剛剛產生的檔案內容

MainMDPageViewModel.cs

using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using Xamarin.Forms;
using XFDoggy.Helps;

namespace XFDoggy.ViewModels
{
    public class MainMDPageViewModel : BindableBase
    {
        private readonly INavigationService _navigationService;


        #region 差旅費用申請Color
        private Color _差旅費用申請Color;
        /// <summary>
        /// 差旅費用申請
        /// </summary>
        public Color 差旅費用申請Color
        {
            get { return this._差旅費用申請Color; }
            set { this.SetProperty(ref this._差旅費用申請Color, value); }
        }
        #endregion

        #region 登出Color
        private Color _登出Color;
        /// <summary>
        /// 登出
        /// </summary>
        public Color 登出Color
        {
            get { return this._登出Color; }
            set { this.SetProperty(ref this._登出Color, value); }
        }
        #endregion


        public DelegateCommand 差旅費用申請Command { get; private set; }
        public DelegateCommand 登出Command { get; private set; }

        public MainMDPageViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;

            差旅費用申請Command = new DelegateCommand(差旅費用申請);
            登出Command = new DelegateCommand(登出);

            更新正在執行項目的顏色();
        }

        private async void 登出()
        {
            AppData.正在執行功能 = 執行功能列舉.登出;
            更新正在執行項目的顏色();
            await _navigationService.NavigateAsync("xf:///LoginPage");
        }

        private async void 差旅費用申請()
        {
            if (AppData.正在執行功能 != 執行功能列舉.差旅費用申請)
            {
                AppData.正在執行功能 = 執行功能列舉.差旅費用申請;
                更新正在執行項目的顏色();
                await _navigationService.NavigateAsync("xf:///MainMDPage/NaviPage/TravelExpensesListPage");
            }
        }

        public void 更新正在執行項目的顏色()
        {
            清除所有功能的顏色設定();
            switch (AppData.正在執行功能)
            {
                case 執行功能列舉.差旅費用申請:
                    差旅費用申請Color = Color.FromHex("ed6d45");
                    break;
                case 執行功能列舉.登出:
                    登出Color = Color.FromHex("ed6d45");
                    break;
                default:
                    break;
            }
        }

        public void 清除所有功能的顏色設定()
        {
            差旅費用申請Color = Color.FromHex("040000");
            登出Color = Color.FromHex("040000");
        }
    }
}

註冊主系統導航抽屜頁面檢視

  1. 在核心PCL XFDoggy 專案內,開啟 App.xaml.cs 檔案
  2. 使用底下C#程式碼替換掉剛剛開啟的檔案內的 RegisterTypes 方法定義

App.xaml.cs

        protected override void RegisterTypes()
        {
            Container.RegisterTypeForNavigation<MainPage>();
            Container.RegisterTypeForNavigation<LoadingPage>();
            Container.RegisterTypeForNavigation<LoginPage>();
            Container.RegisterTypeForNavigation<NaviPage>();
            Container.RegisterTypeForNavigation<MainMDPage>();
        }

建立差旅費用清單資料項目檢視模型 (ViewModel)

  1. 在核心PCL XFDoggy 專案內,使用滑鼠右鍵點選 ViewModels 資料夾,接著,選擇 加入 > 新增項目
  2. 在 加入新項目 - XFDoggy 對話窗中,點選 Prism > Prism ViewModel
  3. 在底下名稱欄位內,輸入 TravelExpensesListItemViewModel,接著,點選 新增 按鈕
  4. 使用底下程式碼替換掉剛剛產生的檔案內容

TravelExpensesListItemViewModel.cs

using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;

namespace XFDoggy.ViewModels
{
    public class TravelExpensesListItemViewModel : BindableBase
    {

        #region ID
        private int id;
        /// <summary>
        /// ID
        /// </summary>
        public int ID
        {
            get { return this.id; }
            set { this.SetProperty(ref this.id, value); }
        }
        #endregion

        #region Title
        private string title;
        /// <summary>
        /// Title
        /// </summary>
        public string Title
        {
            get { return this.title; }
            set { this.SetProperty(ref this.title, value); }
        }
        #endregion


        #region TravelDate
        private DateTime travelDate;
        /// <summary>
        /// TravelDate
        /// </summary>
        public DateTime TravelDate
        {
            get { return this.travelDate; }
            set { this.SetProperty(ref this.travelDate, value); }
        }
        #endregion

        public TravelExpensesListItemViewModel()
        {

        }
    }
}

建立差旅費用清單頁面檢視 (View)

  1. 在核心PCL XFDoggy 專案內,使用滑鼠右鍵點選 ViewModels 資料夾,接著,選擇 加入 > 新增項目
  2. 在 加入新項目 - XFDoggy 對話窗中,點選 Prism > Prism ContentPage (Forms)
  3. 在底下名稱欄位內,輸入 TravelExpensesListPage,接著,點選 新增 按鈕
  4. 使用底下程式碼替換掉剛剛產生的檔案內容

TravelExpensesListPage.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:behaviors="clr-namespace:Behaviors;assembly=Behaviors"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="XFDoggy.Views.TravelExpensesListPage"
             BackgroundColor="{StaticResource PageBackgroundColor}"
             Title="差旅費用"
             >

  <ContentPage.ToolbarItems>
    <ToolbarItem
      Command="{Binding 新增Command}"
      Text="新增"
      Order="Primary"
      Priority="0">
      <ToolbarItem.Icon>
        <OnPlatform x:TypeArguments="FileImageSource"
                     iOS="Add.png"
                     Android="Add.png"
                     WinPhone="Assets/Images/Add.png" />  
      </ToolbarItem.Icon>
    </ToolbarItem>
  </ContentPage.ToolbarItems>

  <ListView 
    ItemsSource="{Binding MyData}"
    SelectedItem="{Binding MyDataSelected, Mode=TwoWay}"
    Margin="20,20"
    IsPullToRefreshEnabled="True"
    RefreshCommand="{Binding 更新資料Command}"
    IsRefreshing="{Binding IsBusy, Mode=TwoWay}"
    BackgroundColor="Transparent"
      >
    <ListView.Behaviors>
      <behaviors:EventHandlerBehavior EventName="ItemTapped">
        <behaviors:InvokeCommandAction Command="{Binding MyDataClickedCommand}"  />
      </behaviors:EventHandlerBehavior>
    </ListView.Behaviors>
    <ListView.ItemTemplate>
      <DataTemplate>
        <ViewCell>
          <Grid
              >
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="*" />
              <ColumnDefinition Width="120" />
            </Grid.ColumnDefinitions>
              <Label Grid.Column="0"
                FontSize="14"
                Text="{Binding Title}"
                TextColor="#494849"
                VerticalOptions="Center"
                     />
              <Label Grid.Column="1"
                FontSize="14"
                Text="{Binding TravelDate, StringFormat='{0:yyyy-MM-dd}'}"
                TextColor="#494849"
                VerticalOptions="Center"/>
          </Grid>
        </ViewCell>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>

</ContentPage>

建立差旅費用清單頁面檢視模型 (ViewModel)

  1. 在核心PCL XFDoggy 專案內,使用滑鼠右鍵點選 ViewModels 資料夾,接著,選擇 加入 > 新增項目
  2. 在 加入新項目 - XFDoggy 對話窗中,點選 Prism > Prism ViewModel
  3. 在底下名稱欄位內,輸入 TravelExpensesListPageViewModel,接著,點選 新增 按鈕
  4. 使用底下程式碼替換掉剛剛產生的檔案內容

TravelExpensesListPageViewModel.cs

using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using XFDoggy.Helps;
using XFDoggy.Infrastructure;
using XFDoggy.Models;

namespace XFDoggy.ViewModels
{
    public class TravelExpensesListPageViewModel : BindableBase, INavigationAware
    {
        private readonly INavigationService _navigationService;

        private readonly IEventAggregator _eventAggregator;
        public DelegateCommand MyDataClickedCommand { get; private set; }
        public DelegateCommand 更新資料Command { get; private set; }
        public DelegateCommand 新增Command { get; private set; }

        #region MyData
        private ObservableCollection<TravelExpensesListItemViewModel> myData = new ObservableCollection<TravelExpensesListItemViewModel>();
        /// <summary>
        /// MyData
        /// </summary>
        public ObservableCollection<TravelExpensesListItemViewModel> MyData
        {
            get { return this.myData; }
            set { this.SetProperty(ref this.myData, value); }
        }
        #endregion


        #region MyDataSelected
        private TravelExpensesListItemViewModel myDataSelected;
        /// <summary>
        /// MyDataSelected
        /// </summary>
        public TravelExpensesListItemViewModel MyDataSelected
        {
            get { return this.myDataSelected; }
            set { this.SetProperty(ref this.myDataSelected, 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 TravelExpensesListPageViewModel(INavigationService navigationService, IEventAggregator eventAggregator)
        {
            _navigationService = navigationService;

            _eventAggregator = eventAggregator;
            MyDataClickedCommand = new DelegateCommand(MyDataClicked);
            更新資料Command = new DelegateCommand(更新資料);
            新增Command = new DelegateCommand(新增);

            _eventAggregator.GetEvent<CRUDEvent>().Subscribe(CRUD處理事件);
        }

        private void CRUD處理事件(string obj)
        {
            if (obj == "Refresh")
            {
                重新整理資料();
            }
        }

        private async void 新增()
        {
            var fooPara = new NavigationParameters();
            fooPara.Add("模式", "新增");
            fooPara.Add("TravelExpense", new TravelExpense
            {
                Account = AppData.Account,
                Category = "",
                Domestic = true,
                Expense = 0,
                HasDocument = false,
                Location = "",
                Memo = "",
                Title = "",
                TravelDate = DateTime.Now
            });
            await _navigationService.NavigateAsync("TravelExpensesPage", fooPara);
        }

        private async void MyDataClicked()
        {
            var fooData = AppData.AllTravelExpense.FirstOrDefault(x => x.ID == MyDataSelected.ID);
            var fooPara = new NavigationParameters();
            fooPara.Add("模式", "修改");
            fooPara.Add("TravelExpense", new TravelExpense
            {
                ID = fooData.ID,
                Account = AppData.Account,
                Category = fooData.Category,
                Domestic = fooData.Domestic,
                Expense = fooData.Expense,
                HasDocument = fooData.HasDocument,
                Location = fooData.Location,
                Memo = fooData.Memo,
                Title = fooData.Title,
                TravelDate = fooData.TravelDate,
            });
            await _navigationService.NavigateAsync("TravelExpensesPage", fooPara);
        }

        private async void 更新資料()
        {
            var fooItems = (await AppData.DataService.GetTravelExpensesAsync(AppData.Account)).ToList();
            AppData.AllTravelExpense = fooItems;
            重新整理資料();
            IsBusy = false;
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {
            重新整理資料();
        }

        public void 重新整理資料()
        {
            MyData.Clear();
            foreach (var item in AppData.AllTravelExpense)
            {
                MyData.Add(new TravelExpensesListItemViewModel
                {
                    ID = item.ID,
                    Title = item.Title,
                    TravelDate = item.TravelDate,
                });
            }
        }
    }
}

註冊差旅費用清單頁面檢視

  1. 在核心PCL XFDoggy 專案內,開啟 App.xaml.cs 檔案
  2. 使用底下C#程式碼替換掉剛剛開啟的檔案內的 RegisterTypes 方法定義

App.xaml.cs

        protected override void RegisterTypes()
        {
            Container.RegisterTypeForNavigation<MainPage>();
            Container.RegisterTypeForNavigation<LoadingPage>();
            Container.RegisterTypeForNavigation<LoginPage>();
            Container.RegisterTypeForNavigation<NaviPage>();
            Container.RegisterTypeForNavigation<MainMDPage>();
            Container.RegisterTypeForNavigation<TravelExpensesListPage>();
        }

建立差旅費用項目資料維護頁面檢視 (View)

  1. 在核心PCL XFDoggy 專案內,使用滑鼠右鍵點選 ViewModels 資料夾,接著,選擇 加入 > 新增項目
  2. 在 加入新項目 - XFDoggy 對話窗中,點選 Prism > Prism ContentPage (Forms)
  3. 在底下名稱欄位內,輸入 TravelExpensesPage,接著,點選 新增 按鈕
  4. 使用底下程式碼替換掉剛剛產生的檔案內容

TravelExpensesPage.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="XFDoggy.Views.TravelExpensesPage"
             BackgroundColor="{StaticResource PageBackgroundColor}"
             NavigationPage.HasBackButton="False"
             Title=""
             >

  <ContentPage.ToolbarItems>
    <ToolbarItem
      Command="{Binding 儲存Command}"
      Text="儲存"
      Order="Primary"
      Priority="0">
      <ToolbarItem.Icon>
        <OnPlatform x:TypeArguments="FileImageSource"
                     iOS="Save.png"
                     Android="Save.png"
                     WinPhone="Assets/Images/Save.png" />
      </ToolbarItem.Icon>
    </ToolbarItem>
    <ToolbarItem
      Command="{Binding 取消Command}"
      Text="取消"
      Order="Primary"
      Priority="0">
      <ToolbarItem.Icon>
        <OnPlatform x:TypeArguments="FileImageSource"
                     iOS="Cancel.png"
                     Android="Cancel.png"
                     WinPhone="Assets/Images/Cancel.png" />
      </ToolbarItem.Icon>
    </ToolbarItem>
  </ContentPage.ToolbarItems>

  <ContentPage.Resources>
    <ResourceDictionary>
      <x:String x:Key="LablWidth">40</x:String>
      <Style x:Key="LabelStyle" TargetType="Label">
        <Setter Property="VerticalOptions" Value="Center" />
      </Style>
    </ResourceDictionary>
  </ContentPage.Resources>

  <StackLayout>
    <ScrollView
  Orientation="Vertical"
  Margin="20">
      <StackLayout
        Orientation="Vertical"
      >
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="{StaticResource LablWidth}"/>
            <ColumnDefinition Width="*" />
          </Grid.ColumnDefinitions>
          <Label Text="日期" Grid.Column="0" Style="{StaticResource LabelStyle}" />
          <DatePicker Grid.Column="1"
            Date="{Binding TravelDate, Mode=TwoWay}"
            Format="yyyy-MM-dd"
        />
        </Grid>
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="{StaticResource LablWidth}"/>
            <ColumnDefinition Width="*" />
          </Grid.ColumnDefinitions>
          <Label Text="類型" Grid.Column="0" Style="{StaticResource LabelStyle}"/>
          <Picker Grid.Column="1"
            x:Name="picker分類"
        />
        </Grid>
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="{StaticResource LablWidth}"/>
            <ColumnDefinition Width="*" />
          </Grid.ColumnDefinitions>
          <Label Text="名稱" Grid.Column="0" Style="{StaticResource LabelStyle}"/>
          <Entry Grid.Column="1"
            Text="{Binding Title, Mode=TwoWay}"
        />
        </Grid>
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="{StaticResource LablWidth}"/>
            <ColumnDefinition Width="*" />
          </Grid.ColumnDefinitions>
          <Label Text="地點" Grid.Column="0" Style="{StaticResource LabelStyle}"/>
          <Entry Grid.Column="1"
            Text="{Binding Location, Mode=TwoWay}"
        />
        </Grid>
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="{StaticResource LablWidth}"/>
            <ColumnDefinition Width="*" />
          </Grid.ColumnDefinitions>
          <Label Text="費用" Grid.Column="0" Style="{StaticResource LabelStyle}"/>
          <Entry Grid.Column="1"
            Text="{Binding Expense, Mode=TwoWay}"
            Keyboard="Numeric"
        />
        </Grid>
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="{StaticResource LablWidth}"/>
            <ColumnDefinition Width="*" />
          </Grid.ColumnDefinitions>
          <Label Text="備註" Grid.Column="0" Style="{StaticResource LabelStyle}"
                 VerticalOptions="Start"/>
          <Editor Grid.Column="1"
            Text="{Binding Memo, Mode=TwoWay}"
            HeightRequest="300"
        />
        </Grid>
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="{StaticResource LablWidth}"/>
            <ColumnDefinition Width="*" />
          </Grid.ColumnDefinitions>
          <Label Text="國內" Grid.Column="0" Style="{StaticResource LabelStyle}"/>
          <Switch Grid.Column="1"
            IsToggled="{Binding Domestic, Mode=TwoWay}"
        />
        </Grid>
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="{StaticResource LablWidth}"/>
            <ColumnDefinition Width="*" />
          </Grid.ColumnDefinitions>
          <Label Text="單據" Grid.Column="0" Style="{StaticResource LabelStyle}"/>
          <Switch Grid.Column="1"
            IsToggled="{Binding HasDocument, Mode=TwoWay}"
        />
        </Grid>
        <BoxView HeightRequest="80" Color="Transparent" />
      </StackLayout>
    </ScrollView>
    <Grid
      IsVisible="{Binding ShowDeleteMode}"
      >
      <BoxView
        HeightRequest="70"
        Color="{StaticResource BottomCommandBackgroundColor}"
        />
      <Button Text="刪除"
             Command="{Binding 刪除Command}"
             BackgroundColor="{StaticResource ToolbarBackgroundColor}"
             TextColor="#FFF"
             WidthRequest="250" HeightRequest="40"
             HorizontalOptions="Center"
              >
        <Button.Margin>
          <OnPlatform x:TypeArguments="Thickness"
                      iOS="0,5,0,5" Android="0,0,0,0"  WinPhone="0,5,0,5"/>
        </Button.Margin>
      </Button>
    </Grid>
  </StackLayout>
</ContentPage>

建立差旅費用項目資料維護頁面檢視模型 (ViewModel)

  1. 在核心PCL XFDoggy 專案內,使用滑鼠右鍵點選 ViewModels 資料夾,接著,選擇 加入 > 新增項目
  2. 在 加入新項目 - XFDoggy 對話窗中,點選 Prism > Prism ViewModel
  3. 在底下名稱欄位內,輸入 TravelExpensesPageViewModel,接著,點選 新增 按鈕
  4. 使用底下程式碼替換掉剛剛產生的檔案內容

TravelExpensesPageViewModel.cs

using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using Prism.Navigation;
using Prism.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using XFDoggy.Helps;
using XFDoggy.Infrastructure;
using XFDoggy.Models;

namespace XFDoggy.ViewModels
{
    public class TravelExpensesPageViewModel : BindableBase, INavigationAware
    {
        #region ViewModel Property

        #region ID
        private int id;
        /// <summary>
        /// ID
        /// </summary>
        public int ID
        {
            get { return this.id; }
            set { this.SetProperty(ref this.id, value); }
        }
        #endregion

        #region Account
        private string account;
        /// <summary>
        /// Account
        /// </summary>
        public string Account
        {
            get { return this.account; }
            set { this.SetProperty(ref this.account, value); }
        }
        #endregion

        #region TravelDate
        private DateTime travelDate = DateTime.Now;
        /// <summary>
        /// TravelDate
        /// </summary>
        public DateTime TravelDate
        {
            get { return this.travelDate; }
            set { this.SetProperty(ref this.travelDate, value); }
        }
        #endregion

        #region Category
        private string category;
        /// <summary>
        /// Category
        /// </summary>
        public string Category
        {
            get { return this.category; }
            set { this.SetProperty(ref this.category, value); }
        }
        #endregion

        #region Title
        private string title;
        /// <summary>
        /// Title
        /// </summary>
        public string Title
        {
            get { return this.title; }
            set { this.SetProperty(ref this.title, value); }
        }
        #endregion

        #region Location
        private string location;
        /// <summary>
        /// Location
        /// </summary>
        public string Location
        {
            get { return this.location; }
            set { this.SetProperty(ref this.location, value); }
        }
        #endregion

        #region Expense
        private double expense;
        /// <summary>
        /// Expense
        /// </summary>
        public double Expense
        {
            get { return this.expense; }
            set { this.SetProperty(ref this.expense, value); }
        }
        #endregion

        #region Memo
        private string memo;
        /// <summary>
        /// Memo
        /// </summary>
        public string Memo
        {
            get { return this.memo; }
            set { this.SetProperty(ref this.memo, value); }
        }
        #endregion

        #region Domestic
        private bool domestic;
        /// <summary>
        /// Domestic
        /// </summary>
        public bool Domestic
        {
            get { return this.domestic; }
            set { this.SetProperty(ref this.domestic, value); }
        }
        #endregion

        #region HasDocument
        private bool hasDocument;
        /// <summary>
        /// HasDocument
        /// </summary>
        public bool HasDocument
        {
            get { return this.hasDocument; }
            set { this.SetProperty(ref this.hasDocument, value); }
        }
        #endregion

        #region Updatetime
        private DateTime updatetime;
        /// <summary>
        /// Updatetime
        /// </summary>
        public DateTime Updatetime
        {
            get { return this.updatetime; }
            set { this.SetProperty(ref this.updatetime, value); }
        }
        #endregion


        #region CategoryList
        private List<string> categoryList;
        /// <summary>
        /// CategoryList
        /// </summary>
        public List<string> CategoryList
        {
            get { return this.categoryList; }
            set { this.SetProperty(ref this.categoryList, value); }
        }
        #endregion


        #region ShowDeleteMode
        private bool isDeleteMode;
        /// <summary>
        /// PropertyDescription
        /// </summary>
        public bool ShowDeleteMode
        {
            get { return this.isDeleteMode; }
            set { this.SetProperty(ref this.isDeleteMode, value); }
        }
        #endregion

        #endregion

        public string 紀錄處理模式 { get; set; }
        private readonly INavigationService _navigationService;

        public readonly IPageDialogService _dialogService;
        private readonly IEventAggregator _eventAggregator;
        public DelegateCommand 儲存Command { get; private set; }
        public DelegateCommand 刪除Command { get; private set; }
        public DelegateCommand 取消Command { get; private set; }

        public delegate string 讀取Picker選擇項目Del();
        public 讀取Picker選擇項目Del foo讀取Picker選擇項目;
        public Action<string> foo分類Picker初始化;
        public Action<string> foo頁面Title初始化;
        TravelExpense fooTravelExpense;

        public TravelExpensesPageViewModel(INavigationService navigationService, IEventAggregator eventAggregator,
            IPageDialogService dialogService)
        {
            _navigationService = navigationService;
            _dialogService = dialogService;
            _eventAggregator = eventAggregator;
            儲存Command = new DelegateCommand(儲存);
            刪除Command = new DelegateCommand(刪除);
            取消Command = new DelegateCommand(取消);
        }

        private async void 取消()
        {
            if (檢查資料是否有異動() == true)
            {
                var fooAnswer = await _dialogService.DisplayAlertAsync("警告", "資料已經有異動,您確定要取消此次資料輸入嗎?", "是", "否");
                if (fooAnswer == true)
                {
                    await _navigationService.GoBackAsync();
                }
            }
            else
            {
                await _navigationService.GoBackAsync();
            }
        }

        private bool 檢查資料是否有異動()
        {
            bool fooIsChange = false;

            Category = foo讀取Picker選擇項目();
            if (fooTravelExpense.Category != Category)
            {
                fooIsChange = true;
            }
            else if (fooTravelExpense.Domestic != Domestic)
            {
                fooIsChange = true;
            }
            else if (fooTravelExpense.Expense != Expense)
            {
                fooIsChange = true;
            }
            else if (fooTravelExpense.HasDocument != HasDocument)
            {
                fooIsChange = true;
            }
            else if (fooTravelExpense.Location != Location)
            {
                fooIsChange = true;
            }
            else if (fooTravelExpense.Memo != Memo)
            {
                fooIsChange = true;
            }
            else if (fooTravelExpense.Title != Title)
            {
                fooIsChange = true;
            }
            else if (fooTravelExpense.TravelDate.Date != TravelDate.Date)
            {
                fooIsChange = true;
            }
            return fooIsChange;
        }

        private async void 刪除()
        {
            await AppData.DataService.DeleteTravelExpensesAsync(ID);
            var fooItems = (await AppData.DataService.GetTravelExpensesAsync(AppData.Account)).ToList();
            AppData.AllTravelExpense = fooItems;
            _eventAggregator.GetEvent<CRUDEvent>().Publish("Refresh");
            await _navigationService.GoBackAsync();
        }

        private async void 儲存()
        {
            Category = foo讀取Picker選擇項目();
            fooTravelExpense = new TravelExpense
            {
                ID = ID,
                Account = AppData.Account,
                Category = category,
                Domestic = Domestic,
                Expense = Expense,
                HasDocument = HasDocument,
                Location = Location,
                Memo = Memo,
                Title = Title,
                TravelDate = TravelDate,
                Updatetime = DateTime.Now,
            };
            if (紀錄處理模式 == "新增")
            {
                await AppData.DataService.PostTravelExpensesAsync(fooTravelExpense);
            }
            else
            {
                await AppData.DataService.PutTravelExpensesAsync(fooTravelExpense);
            }
            var fooItems = (await AppData.DataService.GetTravelExpensesAsync(AppData.Account)).ToList();
            AppData.AllTravelExpense = fooItems;
            _eventAggregator.GetEvent<CRUDEvent>().Publish("Refresh");
            await _navigationService.GoBackAsync();
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {
        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {
            紀錄處理模式 = parameters["模式"] as string;
            fooTravelExpense = parameters["TravelExpense"] as TravelExpense;
            ID = fooTravelExpense.ID;
            Category = fooTravelExpense.Category;
            Domestic = fooTravelExpense.Domestic;
            Expense = fooTravelExpense.Expense;
            HasDocument = fooTravelExpense.HasDocument;
            Location = fooTravelExpense.Location;
            Memo = fooTravelExpense.Memo;
            Title = fooTravelExpense.Title;
            TravelDate = fooTravelExpense.TravelDate;
            if (紀錄處理模式 == "新增")
            {
                ShowDeleteMode = false;
                foo頁面Title初始化("差旅費用 新增");
            }
            else
            {
                ShowDeleteMode = true;
                foo分類Picker初始化(fooTravelExpense.Category);
                foo頁面Title初始化("差旅費用 修改");
            }
        }

        private void Init()
        {
            CategoryList = new List<string>();
            var fooItems = AppData.AllTravelExpensesCategory;
            foreach (var item in fooItems)
            {
                CategoryList.Add(item.Name);
            }
        }
    }
}

建立差旅費用項目資料維護頁面後置程式碼 (code behind)

  1. 在核心PCL XFDoggy 專案內,開啟檔案 TravelExpensesPage.xaml.cs
  2. 使用底下程式碼替換掉剛剛產生的檔案內容

TravelExpensesPage.xaml.cs

using Xamarin.Forms;
using XFDoggy.Helps;
using XFDoggy.ViewModels;
using System.Linq;

namespace XFDoggy.Views
{
    public partial class TravelExpensesPage : ContentPage
    {
        TravelExpensesPageViewModel fooTravelExpensesPageViewModel;
        public TravelExpensesPage()
        {
            InitializeComponent();

            fooTravelExpensesPageViewModel = this.BindingContext as TravelExpensesPageViewModel;
            設定Picker選單內容();
            fooTravelExpensesPageViewModel.foo讀取Picker選擇項目 = 讀取Picker選擇項目;
            fooTravelExpensesPageViewModel.foo分類Picker初始化 = Picker資料初始化;
            fooTravelExpensesPageViewModel.foo頁面Title初始化 = 頁面Title初始化;
        }

        protected override bool OnBackButtonPressed()
        {
            return true;
        }

        private void 頁面Title初始化(string obj)
        {
            this.Title = obj;
        }

        public void 設定Picker選單內容()
        {
            foreach (var item in AppData.AllTravelExpensesCategory)
            {
                picker分類.Items.Add(item.Name);
            }
        }

        public string 讀取Picker選擇項目()
        {
            string fooRet = "";
            var fooIdx = picker分類.SelectedIndex;
            if (fooIdx >= 0)
            {
                fooRet = picker分類.Items[fooIdx];
            }
            else
            {
                fooRet = "";
            }
            return fooRet;
        }

        public void Picker資料初始化(string category)
        {
            var fooItem = AppData.AllTravelExpensesCategory.FirstOrDefault(x => x.Name == category);
            if (fooItem != null)
            {
                var fooIdx = AppData.AllTravelExpensesCategory.IndexOf(fooItem);
                if (fooIdx >= 0)
                {
                    picker分類.SelectedIndex = fooIdx;
                }
            }
        }
    }
}

註冊差旅費用項目資料維護頁面檢視

  1. 在核心PCL XFDoggy 專案內,開啟 App.xaml.cs 檔案
  2. 使用底下C#程式碼替換掉剛剛開啟的檔案內的 RegisterTypes 方法定義

App.xaml.cs

        protected override void RegisterTypes()
        {
            Container.RegisterTypeForNavigation<MainPage>();
            Container.RegisterTypeForNavigation<LoadingPage>();
            Container.RegisterTypeForNavigation<LoginPage>();
            Container.RegisterTypeForNavigation<NaviPage>();
            Container.RegisterTypeForNavigation<MainMDPage>();
            Container.RegisterTypeForNavigation<TravelExpensesListPage>();
            Container.RegisterTypeForNavigation<TravelExpensesPage>();
        }

解決 iOS 應用程式啟動會有例外異常問題

如果您現在在 Android 開始執行這個 Xamarin.Forms 專案,可以正常執行。
不過,當同樣的核心PCL Xamarin.Forms 應用程式在 iOS 上執行,卻發生錯誤。
  1. 在 XFDoggy.iOS 專案內,開啟 AppDelegate.cs 檔案
  2. 使用底下C#程式碼替換掉剛剛開啟的檔案內的 FinishedLaunching 方法定義
        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            // 需要加入這行解決無法載入組建的問題
            var fooBehaviorBase = new Behaviors.BehaviorBase<Xamarin.Forms.ListView>();
            global::Xamarin.Forms.Forms.Init();
            LoadApplication(new App(new iOSInitializer()));

            return base.FinishedLaunching(app, options);
        }

沒有留言:

張貼留言