Universal app - Loading combobox' ItemsSource async gives weird behaviour
Asked Answered
F

3

6

While working on an Universal App (currently only on the WP8.1-side), I've stumbled upon the following weird thing.

I've got a ComboBox, the UserControl (located in the WindowsPhone-project) it's in is binded to a VM in the Shared project. Both the ItemsSource and SelectedItem are binded to their respective properties in the VM.

When running the application, when you select any item except the first one, it is working perfectly. But, when I select the first item, the string displayed in the ComboBox shows the .ToString()-method of the VM instead...

(Btw, it's a simple List<string>, the selected item is a string. It can't get much more simpler than that :p)

I've created a sample app, containing just this Combobox, and the VM. I was able to reproduce this, the moment I asynchronously fill in property binded to the ItemsSource. When doing it from a synchronous method, it works. But just filling it from an async method provides the above problem.

A few screenshots:

The first one shows the app when it's loaded. When the collection changes, the first element of the list is selected. It is shown here:

After is app loaded

When you click on the ComboBox, you get to see its items as usual: enter image description here

Say you click on any element other than the first, you still get normal behaviour: enter image description here

So far, so normal. Now click the first item. You get this: enter image description here

...

I've tried a variety of things like making it a list of an object instead of just strings. Adding a converter to the binded objects, just for debugging purposes, only reveales the actual string-values. I've got no idea how, nor why, the binded SelectedItem suddenly shows the DataContext of the ComboBox...

You can download the sample app here: http://1drv.ms/1DhklCQ (contains no binaries, just the code)

Anybody got any ideas?


EDIT: The code required to reproduce this issue:

Create a blank Universal store app (8.1). In the WindowsPhone project, the file MainPage.xaml: I've added a simple combobox, and catch the Loaded event.

<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />

In its code behind. I've assigned the DataContext to the VM. And in the Loaded event I asychronously call the VM.LoadData()

private VM _vm = new VM();
public MainPage()
{
    this.InitializeComponent();
    this.DataContext = _vm;
}

private async void Page_Loaded(object sender, RoutedEventArgs e)
{
    await _vm.LoadDataAsync();
}

The VM object is defined as followed:

public class VM : INotifyPropertyChanged
{
    private List<string> _items;
    public List<string> Items
    {
        get { return _items; }
        set
        {
            _items = value;
            _selectedItem = _items.FirstOrDefault();
            RaisePropertyChanged("Items");
            RaisePropertyChanged("SelectedItem");
        }
    }

    private string _selectedItem;
    public string SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            _selectedItem = value;
            RaisePropertyChanged("SelectedItem");
        }
    }

    public VM()
    {
    }

    public async Task LoadDataAsync()
    {
        this.Items = new List<string>()
        {
            "a",
            "b",
            "c",
            "d",
            "e",
            "f",
        };
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void RaisePropertyChanged(string propName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
}
Flibbertigibbet answered 28/1, 2015 at 18:32 Comment(3)
why dont you post the code here?Kao
I've added the code to the Original post.Flibbertigibbet
For what it's worth, we also have the same problem in our application, which uses many async patterns. Our conclusion is that it's a bug with the ComboBox.Depute
V
7

Found a workaround cause previous solutions didn't solve my problem.

Just add a pause between binding and selecting an item or index of your combobox.

Code below :

myCombobox.ItemsSource = myList;
await Task.Delay(100);
myCombobox.SelectedIndex = 12;

Hope this helps !

Varanasi answered 11/3, 2015 at 18:35 Comment(2)
Had error in data binding. This solved my bug of binding item source. ThanksWsan
Only possible solution, itemssource binding or selected item manipulation any other way results in this unbelievable bug, so bad QA. By the way this also happens on combobox fullscreen selection but the results is a blank selected item, an impossible to properly bind comboboxScourge
S
2

I checked it out and I can't see any problem with your code, so I guess it is a bug in the ComboBox.

Understanding the problem and finding a true fix may take you some time, so I'd suggest you use some workaround that works for you. I tried the following and it seemed to work:

  1. Change the Items property in the VM to be of type ObservableCollection<string>
  2. Initialize the property/field in the VM's constructor to an empty collection.
  3. When loading the items, just fill the collection (add items to it using the Add() method) instead of replacing it.

Edit: example of how I fill tested it.

public class VM : INotifyPropertyChanged
{
    private ObservableCollection<string> _items;
    public ObservableCollection<string> Items
    {
        get { return _items; }
        set
        {
            _items = value;
            _selectedItem = _items.FirstOrDefault();
            RaisePropertyChanged("Items");
            RaisePropertyChanged("SelectedItem");
        }
    }

    private string _selectedItem;
    public string SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            _selectedItem = value;
            RaisePropertyChanged("SelectedItem");
        }
    }


    public VM()
    {
        this._items = new ObservableCollection<string>();
    }

    public async Task LoadDataAsync()
    {
        var items = new List<string>() {
            "1",
            "b",
            "c",
            "d",
            "e",
            "f",
            "f",
            "f",
            "f",
            "f",
            "f",
        };

        foreach (var i in items) {
            this._items.Add(i);
        }
        this.SelectedItem = items.FirstOrDefault();
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void RaisePropertyChanged(string propName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
}

This works fine for me.

Stephine answered 29/1, 2015 at 11:1 Comment(3)
I've tried this, but if after I fill the collection with Add(), I set the SelectedItem to the first element. And then the bug appears when I select it.Flibbertigibbet
@TomWuyts I modified my answer, adding the code I used to test it. Setting the SelectedItem works in this case (at least for me).Stephine
That didn't work for me, very weird. I've worked around it by changing my use case so it doesn't require the selected item to be set.Flibbertigibbet
S
1

Not only asynchronously - if you put _vm.Items = new List... in OnLoaded event, instead of await _vm.LoadDataAsync(); - you will get the same issue.

Seems that the issue won't occur once you set your Items before setting the DataContext.

The other thing is that the issue won't appear (as I've tried) if you don't set Selected item from code:

public ObservableCollection<string> Items
{
    get { return _items; }
    set
    {
        _items = value;
    //    _selectedItem = _items.FirstOrDefault();
        RaisePropertyChanged("Items");
     //   RaisePropertyChanged("SelectedItem");
    }
}

As for now I've no idea why this happens.

Subtle answered 28/1, 2015 at 22:27 Comment(2)
Yea, When I don't set the selected item, it works... Very odd. Perhaps I should change my use case so I don't really have to select a default item..Flibbertigibbet
@TomWuyts It seems to be a bug, so you will need to use workaround.Subtle

© 2022 - 2024 — McMap. All rights reserved.