Xamarin Picker 使用 ID 绑定
允许选择器通过绑定的 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 切换时自动弹出并显示选项。这变得更像是一种痛苦而不是好处。我可以看到选定的值,如果我需要更改它,我可以单击按钮,这将执行默认行为,即显示列表对话框窗口以进行选择。
现有的选择器控件
选择器从其 ItemsSource
呈现模态选项显示的主要功能保持不变。SelectedItem
和 SelectedIndex
保持不变。正是添加的额外绑定带来了功能的改进。
类别类列表
假设查找表中的 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 类
我的解决方案是为 BindingIntKey
、BindingStringKey
和一个标志列 BindAsInteger
引入一些可绑定属性到一个自定义类中,该标志列用于标识在内部扫描列表以查找选定项目时要查看哪个键属性。还有其他一些属性,但这些是关键的。
通过创建这些 BindableProperty
,它允许我知道何时在选择器外部设置了 ID
,我需要搜索内部列表。但是,它也允许我处理何时选择了选择器以将 ID
组件推送出去。
我实际做的是跟踪值是否来自外部源,根据给定的 ID
(指定的 int
/string
,int
是默认值)查看内部列表,如果找到,则强制将该项目作为 SelectedItem
。
反之亦然。在类控件中,如果用户从选择器列表中选择了一些东西并且触发了 SelectedIndexChanged
,我将获取 ID
并分别通过 BindingIntKey
或 BindingStringKey
将其推送出去。
用伪代码解释,我基本上在做以下事情
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
来获取整个条目,演示代码确实显示了这一点。但现在,您可以直接绑定到记录中基于 int
或 string
的那个属性,并且选择器将双向反映。
使用 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 中的 CatID
和 CategoryPicked
只是您可以绑定到视图模型的示例。
<cp:CboSomeCategory
Title="Shopping Category"
BindingIntKey="{Binding CatID}"
SelectedItem="{Binding CategoryPicked}" />
演示的预期操作
默认选择器
在演示中,最上面的 3 个选择器是普通的。像简单的单词或数字,它们的 ID 会在右侧列中更新。如果您手动更改右侧列的 textbox Entry
值,并且它们在各自的列表中,则选择器将刷新。
示例词包括:Hello
、Good-bye
、Fast
和 Slow
。
注意:选择器控件区分大小写,否则选择器将找不到它。
示例数字包括:2
、37
、12
、19
、42
。不,这些不是按顺序排列的,但如果是一个查找表,并且记录 ID
已被删除,您无法保证哪些 ID
可用。但是填写正确的值,如果找到,选择器将显示它。
示例类别 ID 包括:3
、7
、15
、18
、24
,并直接显示在屏幕上以供参考。如果您尝试填写“ID I Want
” textbox
,选择器将不会发生任何事情,因为它无法仅根据给定的 ID
找到列表项。它只有在准备好一个完整项目并具有完全相同的部件以确认相等性后,才会找到匹配项,然后显示该项目确实存在于列表中。在这种情况下,它永远不会发生。
新 ComboPicker
现在,转向新的 ComboPicker
控件,即带有向下箭头按钮的控件。右侧显示州代码的基于 string
的键。根据显示 6 个州列表的示例代码,表示州的有效 ID
包括:AL
、AK
、AZ
、AR
、CA
、CO
。如果您手动输入这些值,选择器将像您检索具有给定州代码的记录一样更新。
第二个选择器基于 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 包括:3
、7
、15
、18
、24
,与常规选择器控件相同。但现在您可以看到,当您手动更改 ID
时,选择器显示将更新,从而表示从读取的记录中传入的值,而不是用户从下拉列表中选择的值。
完整源代码
此工具不使用任何第三方库或控件,直接使用普通的 Xamarin 控件和 C#。完整的源代码主要有 3 个文件。包含 ControlTemplate
和类 Style
声明的 App.xaml。第二个文件是实际的 ComboPicker.cs 类,其中包含大量注释,说明了类的内部工作原理以及我如何处理双向推入/推出,以更新选择器选项,或根据绑定的指定属性推出选定值。第三个文件是一个简单的向下箭头图形,位于 ComboPickerDemo.Android
项目中。它位于 Resources\drawable\downarrow.png 下。这用于提供视觉指示器“嘿,我是一个下拉控件”,并可以显示选项列表。
另外两个文件是 Main.xaml 和 Main.xaml.cs。这是为了使演示非常简单,并且全部在一个表单中。main.xaml 只是一个包含大约 10 行的网格,显示了普通选择器和新的 comboPicker
控件以及它们在选择项目时的相应结果。textbox
Entry
控件允许用户输入值以强制更新选择器,前提是在选择器列表中找到 ID
。main.xaml.cs 包含用于绑定演示的 public get
/set
属性的代码。
结论
这就是我对标准选择器控件问题的解决方案。我希望它能帮助您简化开发需求。
历史
- 2020年4月26日:初始版本