We use cookies to give you the best experience on our website. If you continue to browse, then you agree to our privacy policy and cookie policy. Image for the cookie policy date
close icon

exceptions and strange behaviour when using SfAutoComplete SelectedValue + SelectedValuePath

Hello,

sf version 17.1.0.47
Android 9.0
iOS 12.3

public class Item
{
     public int? Id { get; set; }
     public string Label { get; set; }
     // public object NoGetter { set {} }
}

public class Context
{
     public Item[] Items { get; set; }
     public int? SelectedId { get; set; }
}

[SfAutoComplete 
     DataSource="{Binding Brands}" 
     DisplayMemberPath="Label" 
     SelectedValuePath="Id" 
     SelectedValue="{Binding SelectedId}"]

things to note:
- 1. [iOS, Android] uncommenting the NoGetter line causes an ArgumentException complaining about the property not having a getter, even though it takes no part in the binding or anywhere related to SfAutoComplete. I guess you iterate over the properties to find the right one, although there is GetRuntimeProperty(propertyname) which gives exactly the one needed. The exception is thrown when clicking in the entry box.
- 2. [Android] having SelectedValuePath specified leads to a NullReferenceException alone when loading up the control!
- 3. [iOS] SelectedValue will have the value of Id in NSString format! I expect it to be of type "int?" obviously. I don't even know about the NSString type in the portable project! SelectedValue should have the value of SelectedItem[SelectedValuePath] or null otherwise. 

i guess the SfComboBox control has the same issues.

thanks,
 Adam


3 Replies

MK Muneesh Kumar G Syncfusion Team May 20, 2019 07:23 AM UTC

Hi Adam, 
 
Thanks for contacting Syncfusion support. 
 
We have analyzed the reported problem, we are able to reproduce it. And we would like to inform you that we are already planned to change "SelectedValue property set access specifier to internal". So, we suggest you to use SfAutoComplete SelectedItem property to achieve your requirement as per the below code snippet. 
 
Code snippet 
 <ContentPage.BindingContext>
        <local:EmployeeViewModel/>
    </ContentPage.BindingContext>
    
    <StackLayout VerticalOptions="Start" HorizontalOptions="Start" Padding="30">
        <autocomplete:SfAutoComplete HeightRequest="40" 
                                     x:Name="autoComplete" 
                                     DisplayMemberPath="Name" 
                                     SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
                                     DataSource="{Binding EmployeeCollection}" />
    </StackLayout> 
 
 
public class EmployeeViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<Employee> employeeCollection;
        public ObservableCollection<Employee> EmployeeCollection
        {
            get { return employeeCollection; }
            set { employeeCollection = value; }
        }

        private object selectedItem;

        public event PropertyChangedEventHandler PropertyChanged;

        public object SelectedItem
        {
            get
            {
                return selectedItem;
            }
            set
            {
                selectedItem = value;
                NotifyPropertyChanged();
            }
        }

        private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }


        //ViewModel property for MultiSelect Mode
        //private ObservableCollection<object> selectedItems;
        //public ObservableCollection<object> SelectedItems
        //{
        //    get { return selectedItems; }
        //    set { selectedItems = value; NotifyPropertyChanged(); }
        //}


        public EmployeeViewModel()
        {
            employeeCollection = new ObservableCollection<Employee>();
            employeeCollection.Add(new Employee() { ID = 1, Name = "Eric" });
            employeeCollection.Add(new Employee() { ID = 2, Name = "James" });
            employeeCollection.Add(new Employee() { ID = 3, Name = "Jacob" });
            employeeCollection.Add(new Employee() { ID = 4, Name = "Lucas" });
            employeeCollection.Add(new Employee() { ID = 5, Name = "Mark" });
            employeeCollection.Add(new Employee() { ID = 6, Name = "Aldan" });
            employeeCollection.Add(new Employee() { ID = 7, Name = "Aldrin" });
            employeeCollection.Add(new Employee() { ID = 8, Name = "Alan" });
            employeeCollection.Add(new Employee() { ID = 9, Name = "Aaron" });

            //Assigned initial SelectedItem for AutoComplete.
            SelectedItem = employeeCollection[0];

            //Assigned initial SelectedItems for AutoComplete in MultiSelect mode.
            //SelectedItems = new ObservableCollection<object>();
            //SelectedItems.Add(employeeCollection[0]);
            //SelectedItems.Add(employeeCollection[1]);
            //SelectedItems.Add(employeeCollection[2]);
        }
    } 
 
 
 
We have attached the simple sample, in which we have bound the SfAutoComplete SelectedItem property in two-way mode. Please find the sample from the below location. 
 
 
Please check the attached sample and let us know if you have any concern on this. 
 
Regards, 
Muneesh Kumar G. 



AD Adam May 20, 2019 09:18 AM UTC

Hello,

I was aware of the SelectedItem property, however i only have the Id when i want to set up the selection! And that is the use case which the SelectedValue/Path is meant to remedy.
I would like to point out that SelectedValue { get; } property also has wrong behavior, because it returns the selected value in some sort of stringified format. Unfortunately this makes SelectedValue/SelectedValuePath unusable for even readonly purposes, so to avoid confusion both of these properties could be removed altogether.

Instead I suggest using something along the lines of the following that allows get/set and works as expected:

    public class SfAutoComplete : Syncfusion.SfAutoComplete.XForms.SfAutoComplete
    {
        public new static BindableProperty SelectedValueProperty = BindableProperty.Create(nameof(SelectedValue), typeof(object), typeof(SfAutoComplete), propertyChanged: (b, o, n) => OnValueChanged(b, n));
        public new object SelectedValue { get => GetValue(SelectedValueProperty); set => SetValue(SelectedValueProperty, value); }

        public new static BindableProperty SelectedValuePathProperty = BindableProperty.Create(nameof(SelectedValuePath), typeof(string), typeof(SfAutoComplete), propertyChanged: (b, o, n) => OnItemChanged(b));
        public new string SelectedValuePath { get => (string) GetValue(SelectedValuePathProperty); set => SetValue(SelectedValuePathProperty, value); }

        protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            base.OnPropertyChanged(propertyName);
            if (propertyName == nameof(SelectedItem))
                OnItemChanged(this);
            if (propertyName == nameof(DataSource))
                OnValueChanged(this, GetValue(SelectedValueProperty));
        }

        static void OnItemChanged(BindableObject b)
        {
            // item => value
            var item = b.GetValue(SelectedItemProperty);
            var valuePath = (string) b.GetValue(SelectedValuePathProperty);
            if (valuePath != null)
                b.SetValue(SelectedValueProperty, GetProperty(valuePath, item)?.GetValue(item));
        }

        static void OnValueChanged(BindableObject b, object newValue)
        {
            // value => item
            var valuePath = (string)b.GetValue(SelectedValuePathProperty);
            if (valuePath != null)
            {
                var items = b.GetValue(DataSourceProperty) as System.Collections.IEnumerable;
                // try finding an interface to obtain getter: IEnumerable<T> -> T.[id]
                var property = items?.GetType()
                    .GetInterfaces()
                    .Where(x => x.IsGenericType && (x.GetGenericTypeDefinition() == typeof(IEnumerable<>) || x.GetGenericTypeDefinition().IsSubclassOf(typeof(IEnumerable<>))))
                    .Select(x => x.GetGenericArguments()[0].GetProperty(valuePath))
                    .FirstOrDefault(x => x != null);

                b.SetValue(SelectedItemProperty, items?.Cast<object>().First(item => Equals(newValue, (property ?? GetProperty(valuePath, item))?.GetValue(item))));
            }
        }

        static System.Reflection.PropertyInfo GetProperty(string id, object value) 
            => id == null ? null : value?.GetType().GetProperty(id);
    }


MK Muneesh Kumar G Syncfusion Team May 20, 2019 12:25 PM UTC

Hi Adam,  
 
Thanks for your suggestion, we will consider this in our improvement. We glad to know that the issue resolved and please get back to us if you need any other assistance.  

Regards,
Muneesh Kumar G
 


Loader.
Live Chat Icon For mobile
Up arrow icon