Universal app - Loading combobox' ItemsSource

2019-04-27 11:56发布

问题:

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:

When you click on the ComboBox, you get to see its items as usual:

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

So far, so normal. Now click the first item. You get this:

...

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

回答1:

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 !



回答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.



回答3:

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.