Почему этот UWP ComboBox может быть пустым после инициализации, но работает нормально для выбора?

У меня есть такой ComboBox

<ComboBox
    Grid.Column="1"
    Padding="5,0,0,0"
    DisplayMemberPath="Description" 
    SelectedItem="{Binding MaxXXAge, Mode=TwoWay, Converter={StaticResource MaxXXAgeToMaxXXAgeMemberConverter}}"
    ItemsSource="{Binding ElementName=SettingsXXScrollViewer, Path=DataContext.MaxXXAgeMemberGroup, Mode=OneWay}" />

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

public IReadOnlyList<MaxXXAgeMembers> MaxXXAgeMemberGroup { get { return MaxXXAgeMembers.Options; } }

И это определение MaxXXAgeMembers

public class MaxXXAgeMembers
        {
            public MaxXXAge MaxXXAge { get; private set; }
            public string Description { get; private set; }

            public static readonly IReadOnlyList<MaxXXAgeMembers> Options = new ReadOnlyCollection<MaxXXAgeMembers>(new[]
            {
                new MaxXXAgeMembers { MaxXXAge =  MaxXXAge.OneDay, Description = Strings.SettingSync_OneDay},
.......
            });

            public static MaxXXAgeMembers FromMaxXXAge(MaxXXAge maxXXAge)
            {
                return Options.First(option => option.MaxXXAge == maxXXAge);
            }
        }

// Позже добавили переопределение Equals

public override bool Equals(object obj)
{
     if (obj == null || !(obj is MaxEmailAgeMembers))
          return false;
     return ((MaxEmailAgeMembers)obj).Description.Equals(this.Description);
}

public override int GetHashCode()
{
     return this.Description.GetHashCode();
}

Конвертер такой

public sealed class MaxEmailAgeToMaxEmailAgeMemberConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        return WPSettingsEmailViewModel.MaxEmailAgeMembers.FromMaxEmailAge((MaxEmailAge)value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        return ((WPSettingsEmailViewModel.MaxEmailAgeMembers)value).MaxEmailAge;
    }
}

Любая идея?


person litaoshen    schedule 23.08.2016    source источник
comment
Вы установили точку останова в MaxEmailAgeToMaxEmailAgeMemberConverter.Convert() и подтвердили, что (а) (MaxEmailAge)value не равно нулю и (б) ...FromMaxEmailAge((MaxEmailAge)value) не равно нулю?   -  person 15ee8f99-57ff-4f92-890c-b56153    schedule 24.08.2016
comment
@EdPlunkett Да, я знал. Это не ноль, я даже тестировал WPSettingsEmailViewModel.MaxEmailAgeMembers.Options [5] == и равен преобразованному значению, они верны! Я был очень озадачен! (PS: позже я добавил переопределение равенства и хэш-кода)   -  person litaoshen    schedule 24.08.2016
comment
Следующим шагом будет подтверждение того, что при первом вызове Convert поле со списком фактически заполнено элементами. Если вы попытаетесь выбрать элемент, которого нет в списке, он не будет отложен, пока элементы не будут заполнены; он просто потерпит неудачу. Кстати, я никогда не делал UWP, только WPF. И иногда бывают различия в поведении. Я использую Win7 на работе, поэтому не могу проверить материал UWP.   -  person 15ee8f99-57ff-4f92-890c-b56153    schedule 24.08.2016
comment
@EdPlunkett Вы правы! Я прикрепил загруженное событие и обнаружил, что в это время ItemSource имеет значение null! Но как оно может быть нулевым ?! Я прикрепил SelectionChanged и обнаружил, что ItemSource в то время был заполнен. Любая идея?   -  person litaoshen    schedule 24.08.2016
comment
В сколько времени он был заселен? Первый раз, когда срабатывает SelectionChanged? Это происходит до или после первого запуска конвертера? Я бы предположил, что этого не происходит при первом вызове преобразователя, поскольку преобразователь не меняет его с исходного нуля.   -  person 15ee8f99-57ff-4f92-890c-b56153    schedule 24.08.2016
comment
Я думаю, что решение здесь очень вероятно следующее: в конструкторе viewmodel сначала заполните коллекцию, которая будет ItemsSource, а затем установите начальное значение для свойства, которое будет управлять SelectedItem.   -  person 15ee8f99-57ff-4f92-890c-b56153    schedule 24.08.2016
comment
@EdPlunkett, я не знаю, когда он заселяется. сначала сработал конвертер, а затем загрузился, изменение выделения не произошло, пока я не щелкнул и не выбрал что-то   -  person litaoshen    schedule 24.08.2016
comment
P.S. - это означает использование ObservableCollection или ReadOnlyObservableCollection для любой коллекции в модели просмотра, которая будет в пользовательском интерфейсе, и повышение PropertyChanged при замене любой коллекции; но вы все равно должны это делать.   -  person 15ee8f99-57ff-4f92-890c-b56153    schedule 24.08.2016
comment
@EdPlunkett, я обнаружил, что мне нужно использовать x: Bind вместо Binding. Также я добавил модель просмотра внутри кода, и она работает! Большое спасибо!   -  person litaoshen    schedule 24.08.2016
comment
x:Bind, да, упс - по крайней мере, я предупреждал вас, что не знаю UWP!   -  person 15ee8f99-57ff-4f92-890c-b56153    schedule 24.08.2016
comment
@EdPlunkett Вы указали правильное направление! Наконец решил это. ржу не могу   -  person litaoshen    schedule 24.08.2016


Ответы (3)


Он пуст, потому что у вас вообще ничего не выделено. Если я не ошибаюсь, вы должны либо использовать SelectedItem для привязки вашего выбора, либо SelectedValue с SelectedValuePath.

Я сам никогда не использую SelectedValue с SelectedValuePath, поэтому после инициализации коллекции элементов, к которым будет привязан ComboBox.ItemSource, например ObservableCollection<Person> Persons {get; set;}, я также установил свойство выбранного элемента Person SelectedPerson {get; set;} на одно из значений из коллекции. Затем я привязываю ComboBox.SelectedItem к этому свойству, поэтому при инициализации он показывает предопределенное выбранное значение.

Я думаю, вы можете добиться того же с помощью SelectedValue и SelectedValuePath, но вы должны использовать их вместе, как описано здесь.

person Andrei Ashikhmin    schedule 23.08.2016
comment
Я перешел на SelectedItem, но он по-прежнему не работает. Я вижу, что преобразователь сработал, что означает, что привязка работает. Однако я не понимаю, что обнаружил selectedindex = -1 в событии loaded (). Как такое могло случиться? - person litaoshen; 23.08.2016
comment
Это может произойти, если ваш SelectedItem на самом деле не является одним из элементов коллекции. Убедитесь, что вы установили его на какой-то элемент из коллекции, например: SelectedPerson = Persons.First (); - person Andrei Ashikhmin; 24.08.2016
comment
Отредактировал вопрос и добавил конвертер, думаю, сделал то же самое, да? - person litaoshen; 24.08.2016
comment
Если это все еще не работает, я не уверен, в чем именно проблема. Честно говоря, ваш код немного сложнее, чем мне хотелось бы сохранить. Я бы рекомендовал упростить его до элементарного состояния, в котором он работает, и построить дополнительную логику оттуда. - person Andrei Ashikhmin; 24.08.2016
comment
На самом деле это был преобразователь enum +, и он работал нормально, но в спецификации нужен пробел между словами, который нельзя сделать с помощью простого перечисления ([описание] не работает для меня), поэтому я применил этот сложный подход. - person litaoshen; 24.08.2016
comment
@litaoshen Я думаю, что проблема в том, что где-то вы назначаете неправильный элемент для SelectedItem. Когда у вас было перечисление, значения в списке ItemsSource и в SelectedItem сравнивались по значению, поэтому SelectedItem работал нормально. Но когда вы меняете это на класс, значения в SelectedItem и исходном списке сравниваются по ссылке. И даже если два элемента одинаковы и имеют одинаковое свойство MaxAge (или описание, или что-то еще), они все равно ссылаются на два разных объекта в куче, поэтому ComboBox не отображает выбранный элемент. - person Andrei Ashikhmin; 25.08.2016
comment
Спасибо за вашу информацию. Я обнаружил, что это из-за того, что модель представления не была инициализирована в то время, что привело к тому, что у источника элементов нет ничего внутри. Спасибо! - person litaoshen; 25.08.2016

Я использовал x: Bind для ItemResource и добавил ViewModel внутри кода позади, и решил эту проблему.

person litaoshen    schedule 24.08.2016

Вы ComboBox не пусто, но он не знает, как отобразить ваш MaxXXAgeMembers. Вы должны использовать ItemTemplate, чтобы сказать ему это. Для Ex:

<ComboBox
    Grid.Column="1"
    Padding="5,0,0,0"
    SelectedValue="{Binding MaxXXAge, Mode=TwoWay, Converter={StaticResource MaxXXAgeToMaxXXAgeMemberConverter}}"
    ItemsSource="{Binding ElementName=SettingsXXScrollViewer, Path=DataContext.MaxXXAgeMemberGroup, Mode=OneWay}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Description}" /> 
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>
person ad1Dima    schedule 23.08.2016
comment
спасибо, кажется, не работает. Я думаю, что он знает, как визуализировать, из-за DisplayMemberPath = Description, а также, если я выберу, он работает, он сразу после первого просмотра, он пуст - person litaoshen; 23.08.2016