65.9K
CodeProject 正在变化。 阅读更多。
Home

Xamarin Picker 使用 ID 绑定

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2020年4月27日

CPOL

11分钟阅读

viewsIcon

22298

downloadIcon

407

允许选择器通过绑定的 ID 显示所选项目,例如通过键/值列表选项

引言

默认的选择器控件仅在例如它是简单字符串列表(其中字符串是键)时才能很好地工作。同样,如果是其他不同的值……整数、小数或其他非常有限的单一事物。然而,对于列表基于键/值对的任何实例,它就力不从心了。

我的应用程序有许多查找选项(选择器控件),其中许多基于键/值对,有些来自枚举器,有些来自查找表。最终的 ID 以整数键的形式存储在记录中。如果我有一个基于键/值的选择器,当我检索只有 ID 列的记录时,选择器不会刷新正确的显示值。由于当您只有键部分并想显示配对部分时无法绑定到部分键/值对,那么选择器类允许什么呢?选择器类允许两种绑定选项。

SelectedIndex 不实用。如果您扩展、减少或重新排序选项列表,您就改变了存储 ID 的上下文,因为列表的序数位置现在可能不同。

SelectedItem 不实用,因为准备好的列表基于一个多部分属性对象,而 ID 是这些属性之一。当从数据库中检索到记录并且它有一个 ID = 3 时,选择器控件不知道如何根据该 ID 在其列表中呈现正确的项目。此外,当将键/值绑定到 SelectedItem 时,您获得的值是列表中的整个键/值条目,而不仅仅是键 ID 部分。

我发现我无法绑定到记录的 ID 列。因此,当从数据库中检索记录时,选择器将显示为空白(未显示值),即使 ID 具有合法值。

欢迎使用新的 ComboPicker 控件

我将此控件的大部分功劳归功于我早期(90年代)使用 Visual FoxPro (VFP) 和 combobox 控件。这个控件具有我需要的所有功能,甚至更多,例如如果您真的想在运行时添加到列表中。我创建了一个新的组合选择器类,它允许控件更容易刷新,并且明确地基于 ID 键,而无需知道如何或实际列表项中包含了什么。因此,当检索到记录时,它将在 ID 单独匹配列表中的 ID 时刷新。

下图显示了包含 3 个普通选择器和 3 个新 ComboPicker 控件的演示程序屏幕。右侧列显示从选择器中选择项目时返回的值。对于绑定到键/值对的普通 Categories 选择器,我首先将 SelectedItem 绑定到视图模型上的一个属性,然后强制从中提取 ID 以显示实际的 ID——因此,从键/值中获取 ID 需要两步过程。

控件之间的视觉风格略有不同。在 ComboPicker 控件上,我有一个 IsRequired 属性,它允许触发器更改背景颜色。此外,我还有一个向下箭头按钮,提供视觉提示,表明这是一种选择列表类型的条目,而不仅仅是自由格式的文本框条目。

这种改变外观的原因是,很多时候,我不希望选择器在我通过字段进行 Tab 切换时自动弹出并显示选项。这变得更像是一种痛苦而不是好处。我可以看到选定的值,如果我需要更改它,我可以单击按钮,这将执行默认行为,即显示列表对话框窗口以进行选择。

Demo Screen Showing default vs new.

现有的选择器控件

选择器从其 ItemsSource 呈现模态选项显示的主要功能保持不变。SelectedItemSelectedIndex 保持不变。正是添加的额外绑定带来了功能的改进。

类别类列表

假设查找表中的 Category 记录具有以下类结构。

  // Category class for the last combo picker sample
  public class CCategory : INotifyPropertyChanged
  {
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propName)
    { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); }

    private int _categoryID;
    public int CategoryID
    {
      get { return _categoryID; }
      set { _categoryID = value;
            OnPropertyChanged("CategoryID");
          }
    }

    private string _category = "";
    public string Category
    {
      get { return _category; }
      set { _category = value;
            OnPropertyChanged("Category");
          }
    }

    private string _longDescription;
    public string LongDescription
    {
      get { return _longDescription; }
      set { _longDescription = value;
            OnPropertyChanged("LongDescription");
          }
    }
  }

现在,一些类别记录的示例数据。ID 号不能保证按顺序排列,如列表中所示。实际列表可以更容易地从数据库中检索,例如

   select * from CategoryTable order by Category

注意:这个静态列表与演示中用于常规选择器和 ComboPicker 控件的列表相同。

   public static List<ccategory> MyCategoriesList 
      = new List<ccategory>
      {
         new CCategory{ CategoryID = 3, Category = "Accessories", 
                        LongDescription = "All types of accessories" },
         new CCategory{ CategoryID = 18, Category = "Desktops", 
                        LongDescription = "Power desktop machines" },
         new CCategory{ CategoryID = 7, Category = "Laptops", 
                        LongDescription = "Lightweight Laptops" },
         new CCategory{ CategoryID = 24, Category = "Monitors", 
                        LongDescription = "All size / resolutions" },
         new CCategory{ CategoryID = 15, Category = "Servers", 
                        LongDescription = "Server class machines" }
      };

因此,我们有上面的数据列表,它将通过视图模型中的 public get/set 公开,以便它可以绑定到选择器的 ItemsSource 属性,并且我们必须明确告知要使用 ItemDisplayBinding 属性显示哪个列。下面显示了一个默认选择器 XAML 示例

   <Picker
      Title="Class List of Categories"
      ItemsSource="{Binding MyCategories}"
      ItemDisplayBinding="{Binding Category}"
      SelectedItem="{Binding CatPicked}" />

使用常规选择器并绑定 SelectedItem,在进行选择后,选定的完整项目将被推送。从中,我只能获取 ID 部分。然而,如前所述的问题是,如果检索到的记录的 ID = 15 会怎样。我无法告诉常规选择器“嘿,你……在你的列表中找到 ID = 15 的项目”。它就是不那样工作。

使用新的 ComboPicker 类

我的解决方案是为 BindingIntKeyBindingStringKey 和一个标志列 BindAsInteger 引入一些可绑定属性到一个自定义类中,该标志列用于标识在内部扫描列表以查找选定项目时要查看哪个键属性。还有其他一些属性,但这些是关键的。

通过创建这些 BindableProperty,它允许我知道何时在选择器外部设置了 ID,我需要搜索内部列表。但是,它也允许我处理何时选择了选择器以将 ID 组件推送出去。

我实际做的是跟踪值是否来自外部源,根据给定的 ID(指定的 int/stringint 是默认值)查看内部列表,如果找到,则强制将该项目作为 SelectedItem

反之亦然。在类控件中,如果用户从选择器列表中选择了一些东西并且触发了 SelectedIndexChanged,我将获取 ID 并分别通过 BindingIntKeyBindingStringKey 将其推送出去。

用伪代码解释,我基本上在做以下事情

WhichWayWasTrigger = n/a

Binding Int Property Changed
   if no value for WhichWayWasTrigger
   {
      WhichWayWasTrigger = CameFromOutsideID
      get the list from the ItemsSource
      find the one with the given ID
      set the SelectedItem = the one found
      WhichWayWasTrigger = n/a
      get out
   }
   
Selected Item Changed
   if no value for WhichWayWasTrigger
   {
      WhichWayWasTrigger = ByPickerSelection
      BindingInt = ID from selected item
      WhichWayWasTrigger = n/a
   }

实际上还有更多内容,因为选择器中实际上还有一个 textbox Entry 控件,所以它会根据选定项目的 ShowValue 进行刷新。因此,当选择器不可见时,您仍然可以看到正确的值。

您仍然可以利用绑定到 SelectedItem 来获取整个条目,演示代码确实显示了这一点。但现在,您可以直接绑定到记录中基于 intstring 的那个属性,并且选择器将双向反映。

使用 ComboPicker

在这个类中,我利用了另一个具有四个属性的类

   public class CPKeyValue
   {
      public int IntKey { get; set; } = 0;
      public string StringKey { get; set; } = "";
      public string ShowValue { get; set; } = "";
      public object RecOrigin { get; set; } = "";
   }

前两个属性应该很明显,分别是整数或字符串基源的键 ID 是什么。第三个属性是当选择器暴露时,您希望从源中显示哪个值。第四个属性是可选的。您可以存储一个代表列表中记录源的整个对象。如果您正在处理一些相当静态的东西,您可以直接创建一个列表并放入键/值,如下所示

示例 1 - 固定列表

   // Sample, get a list of string-based IDs.
   public List<CPKeyValue> MyListOfStates {get; set;}
      = new List<CPKeyValue>
      {
         // just a sample of FIXED entries
         new CPKeyValue{ StringKey = "AL", ShowValue = "Alabama" },
         new CPKeyValue{ StringKey = "AK", ShowValue = "Alaska" },
         new CPKeyValue{ StringKey = "AZ", ShowValue = "Arizona" },
         new CPKeyValue{ StringKey = "AR", ShowValue = "Arkansas" },
         new CPKeyValue{ StringKey = "CA", ShowValue = "California" },
         new CPKeyValue{ StringKey = "CO", ShowValue = "Colorado" }
         // could continue with all, just a sample though.
      };

但即使是州、国家或其他列表也可以更有效地来自查找表。然后你可以使用 ItemsSource="{Binding MyListOfStates}",就像你为常规选择器所做的那样。

示例 2 - 来自数据库的数据

当您处理更复杂的键/值选项以及可能包含选择器的记录的完整实例时,建议对您的选择器控件进行子类化。有一个您重写并准备选项列表的虚拟方法,它将获取您准备的记录并像往常一样完成其余的绑定。

   public class CboSomeCategory : ComboPicker
   {
      // this is where the previous static list of categories
      // actually originate from earlier in this demo
      public static List<ccategory> MyCategoriesList = ...

      public CboSomeCategory() {}

      protected override void LoadCPKeyValues()
      {
         // could be ex: a query like
         // Select * from SomeCategoryLookupTable order by Category
         // and would be used to populate the List<ccategory>

         // Now, from this list, we want to populate the 
         // picker list with our known key description
         foreach (var d in MyCategoriesList) 
               // PickerChoices is a property of the class and is 
               // specifically a List<cpkeyvalue> that becomes the 
               // ItemsSource after prepared
               PickerChoices.Add(new CPKeyValue 
                    { IntKey = d.CategoryID, 
                      ShowValue = d.Category, 
                      // Notice storing the entire category record too.
                      RecOrigin = d }); 
                    }
      }
   }

所以现在您有了自定义的 ComboPicker 类,它有自己的位置来运行查询,拉取数据,然后填充 combopicker 要使用的列表。接下来,在 XAML 中,您将引用项目的命名空间,以便您可以看到类名,例如

xmlns:cp="clr-namespace:ComboPickerDemo" 

您的 XAML 中的 CatIDCategoryPicked 只是您可以绑定到视图模型的示例。

<cp:CboSomeCategory 
   Title="Shopping Category"
   BindingIntKey="{Binding CatID}"
   SelectedItem="{Binding CategoryPicked}" />

演示的预期操作

Right-column fields to update pickers.

默认选择器

在演示中,最上面的 3 个选择器是普通的。像简单的单词或数字,它们的 ID 会在右侧列中更新。如果您手动更改右侧列的 textbox Entry 值,并且它们在各自的列表中,则选择器将刷新。

示例词包括HelloGood-byeFastSlow
注意:选择器控件区分大小写,否则选择器将找不到它。

示例数字包括237121942。不,这些不是按顺序排列的,但如果是一个查找表,并且记录 ID 已被删除,您无法保证哪些 ID 可用。但是填写正确的值,如果找到,选择器将显示它。

示例类别 ID 包括37151824,并直接显示在屏幕上以供参考。如果您尝试填写“ID I Wanttextbox,选择器将不会发生任何事情,因为它无法仅根据给定的 ID 找到列表项。它只有在准备好一个完整项目并具有完全相同的部件以确认相等性后,才会找到匹配项,然后显示该项目确实存在于列表中。在这种情况下,它永远不会发生。

新 ComboPicker

现在,转向新的 ComboPicker 控件,即带有向下箭头按钮的控件。右侧显示州代码的基于 string 的键。根据显示 6 个州列表的示例代码,表示州的有效 ID 包括:ALAKAZARCACO。如果您手动输入这些值,选择器将像您检索具有给定州代码的记录一样更新。

第二个选择器基于 enum 列表。因此,基于整数的 enum 值是我们要绑定的,但我们想显示与该 enum 关联的词。输入这些值中的任何一个,选择器将用描述刷新自身。

   public enum eSomeStatus
   {
      // bogus list, just to show an enumerated list that
      // may not specifically be zero-based, nor incremented 
      // sequentially and has gaps
      None = 3,
      Active = 8,
      Inactive = 23,
      Pending = 29,
      Terminated = 47,
      EndOfList = 99
   }

最后一个是 categories 选择器,同样基于整数 ID,并且与本文前面提供的源完全相同。

示例类别 ID 包括37151824,与常规选择器控件相同。但现在您可以看到,当您手动更改 ID 时,选择器显示将更新,从而表示从读取的记录中传入的值,而不是用户从下拉列表中选择的值。

完整源代码

此工具不使用任何第三方库或控件,直接使用普通的 Xamarin 控件和 C#。完整的源代码主要有 3 个文件。包含 ControlTemplate 和类 Style 声明的 App.xaml。第二个文件是实际的 ComboPicker.cs 类,其中包含大量注释,说明了类的内部工作原理以及我如何处理双向推入/推出,以更新选择器选项,或根据绑定的指定属性推出选定值。第三个文件是一个简单的向下箭头图形,位于 ComboPickerDemo.Android 项目中。它位于 Resources\drawable\downarrow.png 下。这用于提供视觉指示器“嘿,我是一个下拉控件”,并可以显示选项列表。

另外两个文件是 Main.xamlMain.xaml.cs。这是为了使演示非常简单,并且全部在一个表单中。main.xaml 只是一个包含大约 10 行的网格,显示了普通选择器和新的 comboPicker 控件以及它们在选择项目时的相应结果。textbox Entry 控件允许用户输入值以强制更新选择器,前提是在选择器列表中找到 IDmain.xaml.cs 包含用于绑定演示的 public get/set 属性的代码。

结论

这就是我对标准选择器控件问题的解决方案。我希望它能帮助您简化开发需求。

历史

  • 2020年4月26日:初始版本
© . All rights reserved.