天天看點

第二十四章:頁面導航(四)

執行模式

通常,除了應用程式需要從使用者擷取關鍵資訊的特殊情況外,您的應用程式可能會使用無模式頁面。 然後,應用程式可以顯示使用者在輸入此關鍵資訊之前無法解除的模态頁面。

但是,一個小問題是Android或Windows Phone使用者可以通過按裝置上的标準後退按鈕傳回上一頁。 要強制執行模式 - 確定使用者在離開頁面之前輸入所需資訊 - 應用程式必須禁用該按鈕。

ModalEnforcement程式示範了這種技術。 首頁僅包含一個Button:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ModalEnforcement.ModalEnforcementHomePage"
             Title="Main Page">
    <Button Text="Go to Modal Page"
            HorizontalOptions="Center"
            VerticalOptions="Center"
            Clicked="OnGoToButtonClicked" />
</ContentPage>           

代碼隐藏檔案通過導航到模式頁面來處理按鈕的Clicked事件:

public partial class ModalEnforcementHomePage : ContentPage
{
    public ModalEnforcementHomePage()
    {
        InitializeComponent();
    }
    async void OnGoToButtonClicked(object sender, EventArgs args)
    {
        await Navigation.PushModalAsync(new ModalEnforcementModalPage());
    }
}           

ModalEnforcementModalPage的XAML檔案包含兩個Entry元素,一個Picker元素和一個标記為Done的Button。 标記比您預期的更廣泛,因為它包含一個MultiTrigger,隻有當某些内容被輸入到兩個Entry元素中并且某些内容也被輸入到Picker中時,才會将按鈕的IsEnabled屬性設定為True。 這個MultiTrigger需要三個隐藏的Switch元素,使用第23章“觸發器和行為”中讨論的技術:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ModalEnforcement.ModalEnforcementModalPage"
             Title="Modal Page">
    <StackLayout Padding="20, 0">
        <Entry x:Name="entry1"
               Text=""
               Placeholder="Enter Name"
               VerticalOptions="CenterAndExpand" />
        <!-- Invisible Switch to help with MultiTrigger logic -->
        <Switch x:Name="switch1" IsVisible="False">
            <Switch.Triggers>
                <DataTrigger TargetType="Switch"
                             Binding="{Binding Source={x:Reference entry1}, Path=Text.Length}"
                             Value="0">
                    <Setter Property="IsToggled" Value="True" />
                </DataTrigger>
            </Switch.Triggers>
        </Switch>
        <Entry x:Name="entry2"
               Text=""
               Placeholder="Enter Email Address"
               VerticalOptions="CenterAndExpand" />

        <!-- Invisible Switch to help with MultiTrigger logic -->
        <Switch x:Name="switch2" IsVisible="False">
            <Switch.Triggers>
                <DataTrigger TargetType="Switch"
                             Binding="{Binding Source={x:Reference entry2}, Path=Text.Length}"
                             Value="0">
                    <Setter Property="IsToggled" Value="True" />
                </DataTrigger>
            </Switch.Triggers>
        </Switch>
        <Picker x:Name="picker"
                Title="Favorite Programming Language"
                VerticalOptions="CenterAndExpand">
            <Picker.Items>
                <x:String>C#</x:String>
                <x:String>F#</x:String>
                <x:String>Objective C</x:String>
                <x:String>Swift</x:String>
                <x:String>Java</x:String>
            </Picker.Items>
        </Picker>
        <!-- Invisible Switch to help with MultiTrigger logic -->
        <Switch x:Name="switch3" IsVisible="False">
            <Switch.Triggers>
                <DataTrigger TargetType="Switch"
                             Binding="{Binding Source={x:Reference picker}, Path=SelectedIndex}"
                             Value="-1">
                    <Setter Property="IsToggled" Value="True" />
                </DataTrigger>
            </Switch.Triggers>
        </Switch>
        <Button x:Name="doneButton"
 Text="Done"
 IsEnabled="False"
 HorizontalOptions="Center"
 VerticalOptions="CenterAndExpand"
 Clicked="OnDoneButtonClicked">
            <Button.Triggers>
                <MultiTrigger TargetType="Button">
                    <MultiTrigger.Conditions>
                        <BindingCondition Binding="{Binding Source={x:Reference switch1},
                                                                      Path=IsToggled}"
                                                    Value="False" />
                        <BindingCondition Binding="{Binding Source={x:Reference switch2},
                                                                      Path=IsToggled}"
                                                    Value="False" />

                        Chapter 24 Page navigation 950
                        <BindingCondition Binding="{Binding Source={x:Reference switch3},
                                                                      Path=IsToggled}"
                                                    Value="False" />
                    </MultiTrigger.Conditions>
                    <Setter Property="IsEnabled" Value="True" />
                </MultiTrigger>
            </Button.Triggers>
        </Button>
    </StackLayout>
</ContentPage>           

在現實生活中,可能還會檢查電子郵件位址是否有效。 XAML檔案中的簡單邏輯隻檢查是否存在至少一個字元。

這是首次出現的模态頁面,當時尚未輸入任何内容。 請注意,“完成”按鈕已禁用:

第二十四章:頁面導航(四)

通常,使用者仍然可以按Android和Windows Phone螢幕左下方的标準後退按鈕傳回首頁面。 要禁止“後退”按鈕的正常行為,模态頁面必須覆寫虛拟OnBackButtonPressed方法。 您可以在此覆寫中提供自己的後退按鈕處理并傳回true。 要完全禁用“後退”按鈕,隻需傳回true而不執行任何其他操作。 要允許進行預設的“後退”按鈕處理,請調用基類實作。 以下是ModalEnforcementModalPage的代碼隐藏檔案的用法:

public partial class ModalEnforcementModalPage : ContentPage
{
    public ModalEnforcementModalPage()
    {
        InitializeComponent();
    }
    protected override bool OnBackButtonPressed()
    {
        if (doneButton.IsEnabled)
        {
            return base.OnBackButtonPressed();
        }
        return true;
    }
    async void OnDoneButtonClicked(object sender, EventArgs args)
    {
        await Navigation.PopModalAsync();
    }
}           

僅當啟用XAML檔案中的“完成”按鈕時,OnBackButtonPressed覆寫才會調用方法的基類實作并傳回從該實作傳回的值。 這會導緻模态頁面傳回到調用它的頁面。 如果禁用“完成”按鈕,則覆寫傳回true,表示它已完成執行所需的“後退”按鈕所需的所有操作。

像往常一樣,完成按鈕的Clicked處理程式隻調用PopModalAsync。

Page類還定義了一個SendBackButtonPressed,它導緻調用OnBackButtonPressed方法。 應該可以通過調用此方法為“完成”按鈕實作Clicked處理程式:

void OnDoneButtonClicked(object sender, EventArgs args)
{
    SendBackButtonPressed();
}           

雖然這适用于iOS和Android,但它目前無法在Windows運作時平台上運作。

在實際程式設計中,您更有可能使用ViewModel來累積使用者輸入模态頁面的資訊。 在這種情況下,ViewModel本身可以包含一個屬性,該屬性訓示輸入的所有資訊是否有效。

MvvmEnforcement程式使用這種技術,并包含一個名為LittleViewModel的ViewModel:

namespace MvvmEnforcement
{
    public class LittleViewModel : INotifyPropertyChanged
    {
        string name, email;
        string[] languages = { "C#", "F#", "Objective C", "Swift", "Java" };

        int languageIndex = -1;
        bool isValid;
        public event PropertyChangedEventHandler PropertyChanged;
        public string Name
        {
            set
            {
                if (name != value)
                {
                    name = value;
                    OnPropertyChanged("Name");
                    TestIfValid();
                }
            }
            get { return name; }
        }
        public string Email
        {
            set
            {
                if (email != value)
                {
                    email = value;
                    OnPropertyChanged("Email");
                    TestIfValid();
                }
            }
            get { return email; }
        }
        public IEnumerable<string> Languages
        {
            get { return languages; }
        }
        public int LanguageIndex
        {
            set
            {
                if (languageIndex != value)
                {
                    languageIndex = value;
                    OnPropertyChanged("LanguageIndex");
                    if (languageIndex >= 0 && languageIndex < languages.Length)
                    {
                        Language = languages[languageIndex];
                        OnPropertyChanged("Language");
                    }
                    TestIfValid();
                }

            }
            get { return languageIndex; }
        }
        public string Language { private set; get; }
        public bool IsValid
        {
            private set
            {
                if (isValid != value)
                {
                    isValid = value;
                    OnPropertyChanged("IsValid");
                }
            }
            get { return isValid; }
        }
        void TestIfValid()
        {
            IsValid = !String.IsNullOrWhiteSpace(Name) &&
            !String.IsNullOrWhiteSpace(Email) &&
           !String.IsNullOrWhiteSpace(Language);
        }
        void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}           

Name和Email屬性的類型為string,用于綁定Entry元素的Text屬性。 LanguageIndex屬性旨在綁定到Picker的SelectedIndex屬性。 但是LanguageIndex的set通路器使用該值從Languages集合中的字元串數組中設定string類型的Language屬性。

隻要Name,Email或LanguageIndex屬性發生更改,就會調用TestIfValid方法來設定IsValid屬性。 此屬性可以綁定到Button的IsEnabled屬性。

MvvmEnforcement中的首頁與ModalEnforcement中的首頁相同,但當然模态頁面的XAML檔案更簡單并實作了所有資料綁定:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MvvmEnforcement.MvvmEnforcementModalPage"
             Title="Modal Page">
    <StackLayout Padding="20, 0">
        <Entry Text="{Binding Name}"
               Placeholder="Enter Name"
               VerticalOptions="CenterAndExpand" />
        <Entry Text="{Binding Email}"
               Placeholder="Enter Email Address"
               VerticalOptions="CenterAndExpand" />

        <Picker x:Name="picker"
                Title="Favorite Programming Language"
                SelectedIndex="{Binding LanguageIndex}"
                VerticalOptions="CenterAndExpand" />
        <Button Text="Done"
                IsEnabled="{Binding IsValid}"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                Clicked="OnDoneButtonClicked" />
    </StackLayout>
</ContentPage>           

标記包含對ViewModel中的屬性的四個綁定。

模态頁面的代碼隐藏檔案負責執行個體化LittleViewModel并将對象設定為頁面的BindingContext屬性,它在構造函數中執行。 構造函數還通路ViewModel的Languages集合以設定Picker的Items屬性。 (遺憾的是,Items屬性不受可綁定屬性的支援,是以不可綁定。)

該檔案的其餘部分與ModalEnforcement中的模态頁面非常相似,隻是OnBackButtonPressed覆寫通路LittleViewModel的IsValid屬性以确定是調用基類實作還是傳回true:

public partial class MvvmEnforcementModalPage : ContentPage
{
    public MvvmEnforcementModalPage()
    {
        InitializeComponent();
        LittleViewModel viewModel = new LittleViewModel();
        BindingContext = viewModel;
        // Populate Picker Items list.
        foreach (string language in viewModel.Languages)
        {
            picker.Items.Add(language);
        }
    }
    protected override bool OnBackButtonPressed()
    {
        LittleViewModel viewModel = (LittleViewModel)BindingContext;
        return viewModel.IsValid ? base.OnBackButtonPressed() : true;
    }
    async void OnDoneButtonClicked(object sender, EventArgs args)
    {
        await Navigation.PopModalAsync();
    }
}           

繼續閱讀