You have a record that has id keys associated with a lookup table or other key/value basis. A standard picker will not show the value when a record is retrieved for display and the picker has an ItemsSource based on some key/value pair source. My ComboPicker class will show the proper value based on the bound ID, such as when a record is retrieved from database. This combo picker offers additional functionality too.
Introduction
The default picker control works well only if, for example, it is a simple list of strings where the string is the key. Similarly if some other distinct value... integer, decimal, or other very finite single thing. However, for any instance where the list is based on key/value pair basis, it falls short.
My application has many lookup choices (picker control) and many are based on a key/value pairs, some from enumerators, others from lookup tables. The final ID is stored in a record as an integer key. If I have a picker based on key/value, the picker does not refresh the proper display value when I retrieve a record with only ID columns on it. Since you cannot bind on a partial key/value pair when you only have the key part and want to show the paired part, what does the picker class allow? The picker class allows two options to bind to.
SelectedIndex
is not practical. If you ever extend, reduce or reorder a list of choices, you have just changed the context of the stored ID as the list's ordinal positions might now be different.
SelectedItem
is not practical, because the list that is prepared is based on a multi-part object of properties, and the ID is one of those properties. When a record is retrieved from the database and it has an ID = 3
, the picker control has no idea how to present the correct item in its list based on that ID
. In addition, when something is bound to the SelectedItem
that is key/value, the value you get is the entire key/value entry from the list, not just the key ID
portion.
I found that I could not bind to the ID
column of my record. So, when a record is retrieved from a database, the picker would look blank (no value displayed), even if the ID had a legitimate value.
Welcome to the New ComboPicker Control
I give most of the credit of this control going back to my early days (90s) working with Visual FoxPro (VFP) and using the combobox
control. This control had the exact functionality I needed and more, such as adding to the list on-the-fly if you really wanted to. I have created a new combination picker class that allows the control to be refreshed much easier, and based explicitly by the ID key without knowing how, nor what was in the actual list items were prepared. So when a record is retrieved, it will refresh when the ID alone matches the ID in the list.
The image below shows the demo program screen with 3 normal pickers and 3 new ComboPicker
controls. The right-side column shows the value returned from the picker when an item is selected. In the case of normal picker for Categories
, which is bound to a key/value pair, I am first binding the SelectedItem
to one property on the view model and then forcing to extract the ID from it to display the actual ID - hence a two step process to get the ID from the key/value.
The visual style between the controls is only slightly different. On the ComboPicker
control, I have an IsRequired
property which allows a trigger to change the background color. Additionally, I have a down-arrow button giving a visual cue that this is a pick-list type of entry and not just a free-form textbox entry.
The reason for this changed look is that many times, I do not want the picker to automatically popup and show choices as I am tabbing through the fields. This becomes more of a pain than a benefit. I can see the selected value and if I need to change it, I can click the button which will do the default behavior of exposing the list dialog window to make a selection.
The Existing Picker Control
The primary functionality of the picker presenting a modal display of choices from its ItemsSource
remains the same. The SelectedItem
and SelectedIndex
remain the same. It's the additional bindings that were added that give the improvement of functionality.
Category Class List
Assume the following class structure for Category
records from a lookup table.
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");
}
}
}
And now, some sample data of category records. The ID
numbers are never guaranteed to be in order as can be seen in the list. The actual list could have more easily been retrieved from a database such as:
select * from CategoryTable order by Category
Note: This static list is the same list that will be used in the demo for both the regular picker and the ComboPicker
controls.
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" }
};
So, we have our list of data above, and this would really be exposed via a public get
/set
in the view model so it could be bound to the picker's ItemsSource
property, and we would have to explicitly tell what column to show with the ItemDisplayBinding
property. A sample default picker XAML is shown below:
<Picker
Title="Class List of Categories"
ItemsSource="{Binding MyCategories}"
ItemDisplayBinding="{Binding Category}"
SelectedItem="{Binding CatPicked}" />
Using the regular picker and having the binding of SelectedItem
, after a selection is made, the selected full item is pushed. From that, I can grab just the ID
portion. However, the problem as previously stated is, what if the record retrieved has an ID = 15
. I am unable to tell the normal picker "hey you... go find the item in your list that has the ID = 15
". It just doesn't work that way.
Using the New ComboPicker Class
The resolution for me was to introduce a few bindable properties to a custom class for BindingIntKey
, BindingStringKey
, and a flag column BindAsInteger
which is used to identify which key property to look at when internally scanning through the list to find the selected item. There are some other properties, but these are the critical ones.
By creating these BindableProperty
, it allows me to know when an ID
is set outside the picker and I need to search the internal list. But, it also allows me to handle when a picker is selected to push the ID
component back out.
What I am actually doing is tracking if a value is coming in from an outside source, look at the internal list based on the given ID
(int
/string
as designated, int
is default), and if found, force that item as SelectedItem
.
The inverse is true too. Within the class control, if a user picks something from the picker list and that triggers the SelectedIndexChanged
, I grab the ID
and push it back out via the BindingIntKey
or BindingStringKey
respectively.
In a pseudo-code explanation, I am basically doing the following:
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
}
There is a bit more to it as there is actually a textbox
Entry
control in the picker too, so that gets refreshed based on the ShowValue
from the selected item. So when the picker is not visible, you can still see the proper value.
You can still utilize binding to SelectedItem
to get the entire entry, which the demo code does show. But now, you can bind directly to that one property on the record that is int
or string
based, and the picker will reflect in either direction.
Using the ComboPicker
Within this class, I utilize another class that has four properties:
public class CPKeyValue
{
public int IntKey { get; set; } = 0;
public string StringKey { get; set; } = "";
public string ShowValue { get; set; } = "";
public object RecOrigin { get; set; } = "";
}
The first two properties should be obvious, what is the key ID
of the respective integer or string based origin. The third property is what value from your source do you want to show when the picker is exposed. The fourth property is optional. You can store an entire object that represents the record origin in the list. If you are dealing with something pretty static, you could just do a direct list and put in the key/values such as below:
Sample 1 - Fixed List
public List<CPKeyValue> MyListOfStates {get; set;}
= new List<CPKeyValue>
{
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" }
};
But even a listing of states, countries, or other could be more efficient coming from lookup tables. Then you could have the ItemsSource="{Binding MyListOfStates}"
as you would do similar for the regular picker.
Sample 2 - Data Coming from Database
When you are dealing with a more complex option of key/value and possibly a full instance of the record included with the picker, it is suggested to subclass your picker control. There is a virtual method that you override and prepare the list of choices, and it will take the records you prepare and finish the rest of the binding as normal.
public class CboSomeCategory : ComboPicker
{
public static List<ccategory> MyCategoriesList = ...
public CboSomeCategory() {}
protected override void LoadCPKeyValues()
{
foreach (var d in MyCategoriesList)
PickerChoices.Add(new CPKeyValue
{ IntKey = d.CategoryID,
ShowValue = d.Category,
RecOrigin = d });
}
}
}
So now you have your custom ComboPicker
class that has its own location to run a query, pull back data, then populate the list for the combopicker
to use. Next, in the XAML, you would reference your namespace of the project so you can see the class name such as:
xmlns:cp="clr-namespace:ComboPickerDemo"
And your XAML, the CatID
and CategoryPicked
are just samples that you could bind to your view model.
<cp:CboSomeCategory
Title="Shopping Category"
BindingIntKey="{Binding CatID}"
SelectedItem="{Binding CategoryPicked}" />
Intended Operation of Demo
The Default Picker
With the demo, the top 3 pickers are normal. Things like simple words or numbers, their ID gets updated in the right column. If you manually change the right-column textbox Entry
values, and they are in the respective list, the picker will be refreshed.
The Sample Words include: Hello
, Good-bye
, Fast
, and Slow
.
Note: The picker control is case sensitive, otherwise the picker will not find it.
The Sample numbers include: 2
, 37
, 12
, 19
, 42
. No, these are not in sequence, but if a lookup table, and record ID
s have been deleted, you cannot guarantee what ID
s are available. But fill in the right value, the picker will show it, if found.
The Sample Category IDs include: 3
, 7
, 15
, 18
, 24
, and are directly shown on the screen just for reference. If you try to fill in the "ID I Want
" textbox
, nothing will happen to the picker because it cannot find the list item based on just giving it an ID
. It will only find a match if an entire item is prepared with exactly the same parts to confirm equality before showing the item did exist in the list. In this case, it will never happen here.
The New ComboPicker
Now, on to the new ComboPicker
controls, the ones with the down arrow buttons. The right side shows the string
-based key for the state code. As per the sample code showing the list of 6 states, the valid ID
s representing the states include: AL
, AK
, AZ
, AR
, CA
, CO
. If you manually enter those values, the picker will get updated just as if you retrieved a record with the given state code.
The second picker is based on an enum
list. So again the integer-based enum
value is what we would be binding to, but want to show the words associated with that enum
. Put in any of these values and the picker will refresh itself with the description.
public enum eSomeStatus
{
None = 3,
Active = 8,
Inactive = 23,
Pending = 29,
Terminated = 47,
EndOfList = 99
}
The last is the categories
picker, again based on an integer ID
and is the exact same source as previously provided in this article.
The Sample Category IDs include: 3
, 7
, 15
, 18
, 24
, same as the regular picker control. But now you can see that the picker display will be updated when you change the ID
manually, thus representing an incoming value from a record being read, not from a user picking from the drop-down list.
Full Source Code
This tool uses no third-party libraries or controls and is directly using normal Xamarin controls and C#. The full source code is primarily 3 files. The App.xaml which has the ControlTemplate
and the Style
declaration for the class. The second file is the actual ComboPicker.cs class and is heavily commented with the inner workings of the class and how I am handling the directional push in/out to either update the picker choice, or push out the selected value based on the bound-to designated property. The third file is a simple down arrow graphic which is within the ComboPickerDemo.Android
project. It is located under Resources\drawable\downarrow.png. This is used so you have a visual indicator "hey, I am a drop-down control" and can show the list of choices.
The other 2 files are the Main.xaml and Main.xaml.cs. This was to keep the demo very simple and all in one form. The main.xaml just has a grid with about 10 rows showing both the normal picker and new comboPicker
controls along with their respective results as items are selected. The textbox
Entry
controls allow the user to enter in values to force updating the picker, provided the ID
is found within the picker's list. The main.xaml.cs has the code exposing the public get
/set
properties used for binding the demo.
Conclusion
So that's my solution to a problem with the standard picker control. I hope that it can help simplify your development needs.
History
- 26th April, 2020: Initial version