第二十四章:页面导航(四)
执行模式
通常,除了应用程序需要从用户获取关键信息的特殊情况外,您的应用程序可能会使用无模式页面。 然后,应用程序可以显示用户在输入此关键信息之前无法解除的模态页面。
但是,一个小问题是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();
}
}