c# - Xamarin Forms ListView SelectedItem Binding Issue -
listviews follow itempicker/selector pattern of ui controls. speaking, these types of controls, regardless of platform have selecteditem, , itemssource. basic idea there list of items in itemssource, , selecteditem can set 1 of items. other examples comboboxes (silverlight/uwp/wpf), , pickers (xamarin forms).
in cases, these controls async ready, , in other cases, code needs written in order handle scenarios itemssource populated later selecteditem. in our case, of time, bindingcontext (which contains property bound selecteditem) set before itemssource. so, need write code allow function correctly. have done comboboxes in silverlight example.
in xamarin forms, listview control not async ready, i.e. if itemssource not populated before selecteditem set, selected item never highlighted on control. design , ok. the point of thread find way make listview async ready itemssource can populated after selecteditem set.
there should straight forward work arounds can implemented on other platforms achieve this, there few bugs in xamarin forms list view make seemingly impossible work around issue. sample app have created shared between wpf , xamarin forms in order show how listview behaves differently on each platform. wpf listview example, async ready. if itemssource populated after datacontext set on wpf listview, selecteditem bind item in list.
in xamarin forms, cannot consistently selecteditem 2 way binding on listview work. if select item in listview, sets property on model, if set property on model, item should selected not reflected being selected in listview. after items have been loaded, when set property on model, no selecteditem displayed. happening on uwp , android. ios remains untested.
you can see sample problem in git repo: https://christianfindlay@bitbucket.org/christianfindlay/xamarin-forms-scratch.git . run uwp, or android sample, , click async listview. can run xamarinformswpfcomparison sample see how wpf version behaves differently.
when run xamarin forms sample, see there no item selected after items load. in wpf version, selected. note: it's not highlighted blue, grey indicating selected. problem is, , reason can't work around async issue.
here code (clone repo absolute latest code):
public class asynclistviewmodel : inotifypropertychanged { #region fields private itemmodel _itemmodel; #endregion #region events public event propertychangedeventhandler propertychanged; #endregion #region public properties public itemmodel itemmodel { { return _itemmodel; } set { _itemmodel = value; propertychanged?.invoke(this, new propertychangedeventargs(nameof(itemmodel))); } } #endregion } public class itemmodel : inotifypropertychanged { #region fields private int _name; private string _description; #endregion #region events public event propertychangedeventhandler propertychanged; #endregion #region public properties public int name { { return _name; } set { _name = value; propertychanged?.invoke(this, new propertychangedeventargs(nameof(name))); } } public string description { { return _description; } set { _description = value; propertychanged?.invoke(this, new propertychangedeventargs(nameof(description))); } } #endregion #region public methods public override bool equals(object obj) { var itemmodel = obj itemmodel; if (itemmodel == null) { return false; } var returnvalue = name.equals(itemmodel.name); debug.writeline($"an {nameof(itemmodel)} tested equality. equal: {returnvalue}"); return returnvalue; } public override int gethashcode() { debug.writeline($"{nameof(gethashcode)} called on {nameof(itemmodel)}"); return name; } #endregion } public class itemmodelprovider : observablecollection<itemmodel> { #region events public event eventhandler itemsloaded; #endregion #region constructor public itemmodelprovider() { var timer = new timer(timercallback, null, 3000, 0); } #endregion #region private methods private void timercallback(object state) { device.begininvokeonmainthread(() => { add(new itemmodel { name = 1, description = "first" }); add(new itemmodel { name = 2, description = "second" }); add(new itemmodel { name = 3, description = "third" }); itemsloaded?.invoke(this, new eventargs()); }); } #endregion }
this xaml:
<grid x:name="thegrid"> <grid.resources> <resourcedictionary> <local:itemmodelprovider x:key="items" /> </resourcedictionary> </grid.resources> <grid.rowdefinitions> <rowdefinition /> <rowdefinition height="100" /> </grid.rowdefinitions> <listview x:name="thelistview" margin="4" selecteditem="{binding itemmodel, mode=twoway}" itemssource="{staticresource items}" horizontaloptions="center" verticaloptions="center" backgroundcolor="#eeeeee" > <listview.itemtemplate> <datatemplate> <viewcell> <grid > <grid.rowdefinitions> <rowdefinition height="20" /> <rowdefinition height="20" /> </grid.rowdefinitions> <label text="{binding name}" textcolor="#ff0000ee" verticaloptions="center" /> <label text="{binding description}" grid.row="1" verticaloptions="center" /> </grid> </viewcell> </datatemplate> </listview.itemtemplate> </listview> <activityindicator x:name="theactivityindicator" isrunning="true" isvisible="true" margin="100" /> <stacklayout grid.row="1" orientation="horizontal"> <label text="name: " /> <label text="{binding itemmodel.name}" /> <label text="description: " /> <label text="{binding itemmodel.description}" /> <button text="new model" x:name="newmodelbutton" /> <button text="set 2" x:name="settotwobutton" /> </stacklayout> </grid>
code behind:
public partial class asynclistviewpage : contentpage { itemmodelprovider items; itemmodel two; private asynclistviewmodel currentasynclistviewmodel => bindingcontext asynclistviewmodel; public asynclistviewpage() { initializecomponent(); createnewmodel(); items = (itemmodelprovider)thegrid.resources["items"]; items.itemsloaded += items_itemsloaded; newmodelbutton.clicked += newmodelbutton_clicked; settotwobutton.clicked += settotwobutton_clicked; } private void settotwobutton_clicked(object sender, system.eventargs e) { if (two == null) { displayalert("wait items load", "wait items load", "ok"); return; } currentasynclistviewmodel.itemmodel = two; } private void newmodelbutton_clicked(object sender, system.eventargs e) { createnewmodel(); } private void createnewmodel() { //note: if replace line below this, behaviour works: //bindingcontext = new asynclistviewmodel { itemmodel = 2 }; bindingcontext = new asynclistviewmodel { itemmodel = getnewtwo() }; } private static itemmodel getnewtwo() { return new itemmodel { name = 2, description = "second" }; } private void items_itemsloaded(object sender, system.eventargs e) { theactivityindicator.isrunning = false; theactivityindicator.isvisible = false; 2 = items[1]; } }
note: if change method createnewmodel this:
private void createnewmodel() { bindingcontext = new asynclistviewmodel { itemmodel = 2 }; }
the selecteditem reflected on screen. seems indicate listview comparing items based on object reference opposed using equals method on objects. tend think of bug. but, not issue here, because if issue, clicking settotwobutton should yield same result.
it clear there several bugs around xamarin forms. have documented repro steps here: https://bugzilla.xamarin.com/show_bug.cgi?id=58451
the adaptlistview suitable alternative listview control , isn't subject these issues.
Comments
Post a Comment