Универсальное приложение - асинхронная загрузка ItemsSource со списком дает странное поведение

Работая над универсальным приложением (в настоящее время только на стороне WP8.1), я наткнулся на следующую странную вещь.

У меня есть ComboBox, UserControl (расположенный в проекте WindowsPhone), в котором он привязан к виртуальной машине в проекте Shared. И ItemsSource, и SelectedItem привязаны к своим соответствующим свойствам в виртуальной машине.

При запуске приложения при выборе любого пункта кроме первого работает отлично. Но когда я выбираю первый элемент, строка, отображаемая в ComboBox, вместо этого показывает .ToString()-метод виртуальной машины...

(Кстати, это просто List<string>, выбранный элемент — string. Проще не бывает :p)

Я создал пример приложения, содержащего только этот Combobox и виртуальную машину. Мне удалось воспроизвести это, когда я асинхронно заполнил свойство, привязанное к ItemsSource. При выполнении синхронным методом это работает. Но просто заполнение его из асинхронного метода создает вышеуказанную проблему.

Несколько скриншотов:

Первый показывает приложение, когда оно загружено. При изменении коллекции выбирается первый элемент списка. Здесь показано:

После загрузки приложения

Когда вы нажимаете на поле со списком, вы видите его элементы, как обычно: введите здесь описание изображения

Скажем, вы нажимаете на любой элемент, кроме первого, вы все равно получаете нормальное поведение: введите описание изображения здесь

Пока так нормально. Теперь щелкните первый элемент. Вы получите это: введите здесь описание изображения

...

Я пробовал разные вещи, такие как создание списка объектов, а не просто строк. Добавление преобразователя к привязанным объектам только в целях отладки показывает только фактические строковые значения. Я понятия не имею, как и почему привязанный SelectedItem внезапно показывает DataContext ComboBox...

Вы можете загрузить пример приложения здесь: http://1drv.ms/1DhklCQ (не содержит двоичных файлов, только код)

У кого-нибудь есть идеи?


РЕДАКТИРОВАТЬ: код, необходимый для воспроизведения этой проблемы:

Создайте пустое приложение универсального магазина (8.1). В проекте WindowsPhone файл MainPage.xaml: я добавил простое поле со списком и перехватил событие Loaded.

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

В его коде позади. Я назначил DataContext виртуальной машине. И в событии Loaded я асинхронно вызываю 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();
}

Объект VM определяется следующим образом:

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

person Tom Wuyts    schedule 28.01.2015    source источник
comment
почему вы не публикуете код здесь?   -  person Sajeetharan    schedule 28.01.2015
comment
Я добавил код в исходный пост.   -  person Tom Wuyts    schedule 28.01.2015
comment
Что бы это ни стоило, у нас также есть та же проблема в нашем приложении, которое использует множество асинхронных шаблонов. Наш вывод состоит в том, что это ошибка ComboBox.   -  person Martin Plante    schedule 20.03.2015


Ответы (3)


Нашел обходной путь, потому что предыдущие решения не решили мою проблему.

Просто добавьте паузу между привязкой и выбором элемента или индекса вашего списка.

Код ниже:

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

Надеюсь это поможет !

person predalpha    schedule 11.03.2015
comment
Произошла ошибка привязки данных. Это решило мою ошибку привязки источника элемента. Спасибо - person Mohanvel V; 16.10.2015
comment
Единственное возможное решение, привязка источника элементов или манипулирование выбранным элементом каким-либо другим образом приводит к этой невероятной ошибке, так что плохой контроль качества. Кстати, это также происходит при выборе полноэкранного списка со списком, но результатом является пустой выбранный элемент, невозможно правильно связать поле со списком - person Keoz; 07.12.2015

Я проверил это и не вижу никаких проблем с вашим кодом, поэтому я предполагаю, что это ошибка в ComboBox.

Понимание проблемы и поиск истинного решения могут занять некоторое время, поэтому я предлагаю вам использовать обходной путь, который работает для вас. Я попробовал следующее, и это, похоже, сработало:

  1. Измените свойство Items в виртуальной машине на тип ObservableCollection<string>.
  2. Инициализируйте свойство/поле в конструкторе виртуальной машины пустой коллекцией.
  3. При загрузке элементов просто заполните коллекцию (добавьте в нее элементы с помощью метода Add()), а не заменяйте ее.

Редактировать: пример того, как я тестировал его.

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

Это отлично работает для меня.

person yasen    schedule 29.01.2015
comment
Спасибо за предложение, сегодня вечером попробую. - person Tom Wuyts; 29.01.2015
comment
Я пробовал это, но если после заполнения коллекции Add() я устанавливаю SelectedItem в первый элемент. И тогда появляется ошибка, когда я его выбираю. - person Tom Wuyts; 30.01.2015
comment
@TomWuyts Я изменил свой ответ, добавив код, который использовал для его проверки. Установка SelectedItem работает в этом случае (по крайней мере, для меня). - person yasen; 30.01.2015
comment
Мне это не помогло, очень странно. Я работал над этим, изменив свой вариант использования, чтобы он не требовал установки выбранного элемента. - person Tom Wuyts; 31.01.2015

Не только асинхронно — если вы поместите _vm.Items = new List... в событие OnLoaded вместо await _vm.LoadDataAsync(); — вы получите ту же проблему.

Похоже, что проблема не возникнет, если вы установите свои элементы перед настройкой DataContext.

Другое дело, что проблема не появится (как я пытался), если вы не установите выбранный элемент из кода:

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

Пока я понятия не имею, почему это происходит.

person Romasz    schedule 28.01.2015
comment
Да, когда я не устанавливаю выбранный элемент, он работает... Очень странно. Возможно, мне следует изменить свой вариант использования, чтобы мне не нужно было выбирать элемент по умолчанию. - person Tom Wuyts; 30.01.2015
comment
@TomWuyts Похоже, это ошибка, поэтому вам нужно будет использовать обходной путь. - person Romasz; 30.01.2015