Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Xamarin

Xamarin Picker with ID-based Binding

5.00/5 (2 votes)
27 Apr 2020CPOL11 min read 21.5K   376  
Allow picker to show selected item by a bound ID such as by key/value list choices
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.

Demo Screen Showing default vs new.

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.

C#
// 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");
        }
  }
}

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:

SQL
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.

C#
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:

XML
<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:

C#
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

C#
// 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.
   };

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.

C#
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 });
                 }
   }
}

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:

XML
xmlns:cp="clr-namespace:ComboPickerDemo" 

And your XAML, the CatID and CategoryPicked are just samples that you could bind to your view model.

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

Intended Operation of Demo

Right-column fields to update pickers.

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 IDs have been deleted, you cannot guarantee what IDs 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 IDs 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.

C#
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
}

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)