第十九章:集合视图(十九)

定制单元格
当然,很少有人对应用程序的第一个版本感到满意,也许对于简单的EntryForm程序来说也是如此。 修改后的设计要求可能会从PersonalInformation中消除整数Age属性,并将文本AgeRange属性替换为某些固定范围。 另外两个属性被添加到仅与程序员有关的类中:这些是类型字符串的属性,表示程序员的首选计算机语言和平台,可从语言和平台列表中选择。
这是经过修改的ViewModel类,现在称为ProgrammerInformation:

class ProgrammerInformation : ViewModelBase
{
    string name, emailAddress, phoneNumber, ageRange;
    bool isProgrammer;
    string language, platform;
    public string Name 
    {
        set { SetProperty(ref name, value); }
        get { return name; } 
    }
    public string EmailAddress 
    {
        set { SetProperty(ref emailAddress, value); }
        get { return emailAddress; } 
    }
    public string PhoneNumber 
    {
        set { SetProperty(ref phoneNumber, value); }
        get { return phoneNumber; } 
    }
    public string AgeRange
    {
        set { SetProperty(ref ageRange, value); }
        get { return ageRange; }
    }
    public bool IsProgrammer 
    {
        set { SetProperty(ref isProgrammer, value); }
        get { return isProgrammer; }
    }
    public string Language
    {
        set { SetProperty(ref language, value); }
        get { return language; }
    }
    public string Platform
    {
        set { SetProperty(ref platform, value); }
        get { return platform; }
    }
}

AgeRange,Language和Platform属性似乎非常适合Picker,但在TableView中使用Picker需要Picker成为ViewCell的一部分。我们如何做到这一点?
使用ListView时,创建自定义单元格的最简单方法是在XAML中的DataTemplate中定义ViewCell中的可视树。这种方法很有意义,因为您定义的可视化树可能专门针对ListView中的项目进行了定制,并且可能不会在其他地方重用。
您可以在TableView中使用相同的技术,但使用TableView,您更有可能重用特定类型的交互式单元格。例如,ProgrammerInformation类有三个适用于Picker的属性。这意味着创建一个可以在这里和其他地方使用的cus?tom PickerCell类更有意义。
Xamarin.FormsBook.Toolkit库包含一个派生自ViewCell的PickerCell类,它基本上是一个Picker视图的包装器。该类由XAML文件和代码隐藏文件组成。代码隐藏文件定义了三个由可绑定属性支持的属性:Label(标识单元格,就像EntryCell中的Label属性一样),Title(对应于Picker的Title属性)和SelectedValue,它是在中选择的实际字符串选择器。此外,get-only Items属性公开Picker的Items集合:

namespace Xamarin.FormsBook.Toolkit
{
    [ContentProperty("Items")]
    public partial class PickerCell : ViewCell
    {
        public static readonly BindableProperty LabelProperty =
            BindableProperty.Create(
                "Label", typeof(string), typeof(PickerCell), default(string));
        public static readonly BindableProperty TitleProperty = 
            BindableProperty.Create(
                "Title", typeof(string), typeof(PickerCell), default(string));
        public static readonly BindableProperty SelectedValueProperty =
            BindableProperty.Create(
                "SelectedValue", typeof(string), typeof(PickerCell), null, 
                BindingMode.TwoWay,
                propertyChanged: (sender, oldValue, newValue) =>
                    {
                         PickerCell pickerCell = (PickerCell)sender;
                         if (String.IsNullOrEmpty(newValue))
                         {
                             pickerCell.picker.SelectedIndex = -1;
                         }
                         else
                         {
                             pickerCell.picker.SelectedIndex = 
                                     pickerCell.Items.IndexOf(newValue);
                         }
                });
        public PickerCell()
        {
            InitializeComponent();
        }
        public string Label
        {
            set { SetValue(LabelProperty, value); }
            get { return (string)GetValue(LabelProperty); }
        }
        public string Title
        {
            get { return (string)GetValue(TitleProperty); }
            set { SetValue(TitleProperty, value); }
        }
        public string SelectedValue
        {
            get { return (string)GetValue(SelectedValueProperty); }
            set { SetValue(SelectedValueProperty, value); }
        }
        // Items property.
        public IList<string> Items
        {
            get { return picker.Items; }
        }
        void OnPickerSelectedIndexChanged(object sender, EventArgs args)
        {
            if (picker.SelectedIndex == -1)
            {
                SelectedValue = null;
            }
            else
            {
                SelectedValue = Items[picker.SelectedIndex];
            }
        }
    }
}

XAML文件定义了PickerCell的可视树,它只包含一个标识Label和Picker本身。 请注意,XAML文件的根元素是ViewCell,它是PickerCell派生自的类:

<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
          xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
          x:Class="Xamarin.FormsBook.Toolkit.PickerCell"
          x:Name="cell">
    <ViewCell.View>
        <StackLayout Orientation="Horizontal"
                     BindingContext="{x:Reference cell}"
                     Padding="16, 0">
 
            <Label Text="{Binding Label}"
                   VerticalOptions="Center" />
            <Picker x:Name="picker"
                    Title="{Binding Title}"
                    VerticalOptions="Center"
                    HorizontalOptions="FillAndExpand"
                    SelectedIndexChanged="OnPickerSelectedIndexChanged" />
        </StackLayout>
    </ViewCell.View>
</ViewCell>

StackLayout上设置的Padding值是根据经验选择的,与Xamarin.Forms EntryCell在视觉上一致。
通常,此XAML文件中不需要ViewCell.View属性元素标记,因为View是ViewCell的content属性。但是,代码隐藏文件将PickerCell的content属性定义为Items集合,这意味着content属性不再是View,而ViewCell.View标记是必需的。
XAML文件的根元素具有x:Name属性,该属性为对象提供名称“cell”,StackLayout将其BindingContext设置为该对象,这意味着StackLayout的子节点的BindingContext是PickerCell实例本身。这允许Label和Picker包含对代码隐藏文件中PickerCell定义的Label和Title属性的绑定。
Picker触发在代码隐藏文件中处理的SelectedIndexChanged事件,以便代码隐藏文件可以将Picker的SelectedIndex转换为PickerCell的SelectedValue。
这不是创建自定义PickerCell类的唯一方法。您还可以通过为每个平台定义单独的PickerCellRenderer类来创建它。
ConditionalCells程序中的TableView将此PickerCell用于ProgrammerInformation类中的三个属性,并使用字符串集合初始化每个PickerCell:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ConditionalCells"
             xmlns:toolkit=
                 "clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
             x:Class="ConditionalCells.ConditionalCellsPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                    iOS="0, 20, 0, 0" />
    </ContentPage.Padding>
    <StackLayout>
        <TableView Intent="Form">
 
            <TableView.BindingContext>
                <local:ProgrammerInformation /> 
            </TableView.BindingContext>
 
            <TableRoot Title="Data Form">
                <TableSection Title="Personal Information">
                    <EntryCell Label="Name:"
                               Text="{Binding Name}"
                               Placeholder="Enter name"
                               Keyboard="Text" />
                    <EntryCell Label="Email:"
                               Text="{Binding EmailAddress}"
                               Placeholder="Enter email address"
                               Keyboard="Email" />
                    <EntryCell Label="Phone:"
                               Text="{Binding PhoneNumber}"
                               Placeholder="Enter phone number"
                               Keyboard="Telephone" />
                    <toolkit:PickerCell Label="Age Range:"
                                        Title="Age Range"
                                        SelectedValue="{Binding AgeRange}">
                        <x:String>10 - 19</x:String>
                        <x:String>20 - 29</x:String>
                        <x:String>30 - 39</x:String>
                        <x:String>40 - 49</x:String>
                        <x:String>50 - 59</x:String>
                        <x:String>60 - 99</x:String>
                    </toolkit:PickerCell>
                    <SwitchCell Text="Are you a programmer?"
 On="{Binding IsProgrammer}" />
                    <toolkit:PickerCell Label="Language:"
                                        Title="Language"
                                        IsEnabled="{Binding IsProgrammer}"
                                        SelectedValue="{Binding Language}">
                        <x:String>C</x:String>
                        <x:String>C++</x:String>
                        <x:String>C#</x:String>
                        <x:String>Objective C</x:String>
                        <x:String>Java</x:String>
                        <x:String>Other</x:String>
                    </toolkit:PickerCell>
                    <toolkit:PickerCell Label="Platform:"
                                        Title="Platform"
                                        IsEnabled="{Binding IsProgrammer}"
                                        SelectedValue="{Binding Platform}">
                        <x:String>iPhone</x:String>
                        <x:String>Android</x:String>
                        <x:String>Windows Phone</x:String>
                        <x:String>Other</x:String>
                    </toolkit:PickerCell>
                </TableSection>
            </TableRoot>
        </TableView>
    </StackLayout>
</ContentPage>

请注意PickerCell的IsEnabled属性如何与平台和语言属性绑定到IsProgrammer属性,这意味着除非打开SwitchCell并且IsProgrammer属性为true,否则应禁用这些单元格。 这就是为什么这个程序被称为ConditionalCells。
但是,它似乎不起作用,因为此屏幕截图验证:
第十九章:集合视图(十九)
即使IsProgrammer开关关闭,并且最后两个PickerCell元素中的每一个的IsEnabled属性都设置为false,这些元素仍然响应并允许选择值。 此外,PickerCell在Windows 10移动平台上看起来不太好用。
那么让我们尝试另一种方法。