2

I have a ComboBox like this

<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}" />

However, after initialization, the combobox is blank. It actually works fine after this. I can select and show the selected item as expected. It's just the first glance doesn't work. However, I already initialized MaxXXAge and the converter has been triggered. Here is the group

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

And this is the definition for 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);
            }
        }

//Added the Overriding Equals later

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

The converter is like this

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

Any idea?

litaoshen
  • 1,762
  • 1
  • 20
  • 36
  • Did you put a breakpoint in `MaxEmailAgeToMaxEmailAgeMemberConverter.Convert()` and confirm that (a) `(MaxEmailAge)value` is not null, and (b) `...FromMaxEmailAge((MaxEmailAge)value)` is not null? – 15ee8f99-57ff-4f92-890c-b56153 Aug 24 '16 at 13:27
  • @EdPlunkett Yes, I did. It's not null, I even tested WPSettingsEmailViewModel.MaxEmailAgeMembers.Options[5]== and equals the converted value, they are true! I felt very puzzled! (PS: I added overriding the equals and hashcode later) – litaoshen Aug 24 '16 at 13:50
  • 1
    The next thing would be to confirm that, as of the first time Convert is called, the combobox has actually been populated with items. If you attempt to select an item that isn't there in the list, it won't be deferred until Items is populated; it'll just fail. By the way, I've never done UWP, only WPF. And there are differences in behavior sometimes. I'm on Win7 at work so can't check UWP stuff. – 15ee8f99-57ff-4f92-890c-b56153 Aug 24 '16 at 13:54
  • @EdPlunkett You are right! I attached loaded event and found that at that time ItemSource is null! But how can it be null?! I attached SelectionChanged and found that ItemSource at that time has been populated. Any idea? – litaoshen Aug 24 '16 at 14:18
  • At *what* time had it been populated? The first time SelectionChanged ever fires? Does that happen before or after the first time the converter is invoked? I would guess that it's not happening the first time the converter is invoked, since the converter isn't changing it from its initial null. – 15ee8f99-57ff-4f92-890c-b56153 Aug 24 '16 at 14:21
  • I think the solution is here is very likely this: In the viewmodel constructor, first populate the collection that'll be `ItemsSource`, and then set an initial value for the property that'll be driving `SelectedItem`. – 15ee8f99-57ff-4f92-890c-b56153 Aug 24 '16 at 14:25
  • @EdPlunkett, I don't know when does it get populated. converter triggered first and then the comboloaded, selectionchanged didn't happen until I actually click and select something – litaoshen Aug 24 '16 at 14:25
  • P.S. -- this means using `ObservableCollection` or `ReadOnlyObservableCollection` for any collection in the viewmodel that'll be in the UI, and raising `PropertyChanged` when any collection is replaced; but you should be doing that anyway. – 15ee8f99-57ff-4f92-890c-b56153 Aug 24 '16 at 14:56
  • @EdPlunkett, I found that I need to use x:Bind instead of Binding, Also I added the viewmodel inside code behind and it works! Thank you so much! – litaoshen Aug 24 '16 at 16:19
  • `x:Bind`, right, whoops -- at least I warned you I don't know UWP! – 15ee8f99-57ff-4f92-890c-b56153 Aug 24 '16 at 16:20
  • @EdPlunkett You pointed out the right direction! Finally solved it. LOL – litaoshen Aug 24 '16 at 16:21

3 Answers3

3

It's blank because you don't have anything selected in the first place. If I'm not mistaken, you have to either use SelectedItem to bind your selection or SelectedValue with SelectedValuePath.

I actually never use SelectedValue with SelectedValuePath myself, so after initializing collection of items which ComboBox.ItemSource will be binded to - for example ObservableCollection<Person> Persons {get; set;} - I also set selected item property Person SelectedPerson {get; set;} to one of the values from collection. Then I bind ComboBox.SelectedItem to this property, so on initialization it shows predefined selected value.

I guess you can achieve the same with SelectedValue and SelectedValuePath, but you have to use them together as described here.

Community
  • 1
  • 1
Andrei Ashikhmin
  • 2,401
  • 2
  • 20
  • 34
  • I changed to SelectedItem, but it still doesn't work. I can see that the converter has been triggered which means that binding works. However, what I don't understand is that I found selectedindex = -1 in loaded() event. How can this happen? – litaoshen Aug 23 '16 at 18:59
  • It can happen if your SelectedItem is actually not one of the collection items. Make sure you setting it to some item from collection, like this: SelectedPerson = Persons.First(); – Andrei Ashikhmin Aug 24 '16 at 03:32
  • I edited the question and added the converter, I think I did the same thing, right? – litaoshen Aug 24 '16 at 11:22
  • If it's still doesn't work, then I'm not sure where is problem exactly. Honestly, your code is a bit more complicated than I would like to keep myself. I would recommend to simplify it to elementary state where it works and build your additional logic from there. – Andrei Ashikhmin Aug 24 '16 at 11:51
  • Actually it was enum + converter and it worked fine, but the specification needs space between words which you can not do with simple enum ([description] doesn't work for me), that's the reason I adopted this complicated approach – litaoshen Aug 24 '16 at 12:22
  • @litaoshen I think the problem here is that somewhere you assign wrong item to the SelectedItem. When you had an enum, values in the ItemsSource list and in SelectedItem was compared by value, so SelectedItem worked fine. But when you change that to a class, values in SelectedItem and the source list comparing by reference. And even if two items are same and have same MaxAge property (or Description, or whatever), they still reference two different objects in heap so ComboBox doesn't display selected item. – Andrei Ashikhmin Aug 25 '16 at 03:38
  • Thank you for your info. I found that it's because of the viewmodel hasn't been initialize at that time which caused the itemsource has nothing inside. Thanks! – litaoshen Aug 25 '16 at 20:26
2

I used x:Bind for the ItemResource and added ViewModel inside code behind, and solved this problem.

litaoshen
  • 1,762
  • 1
  • 20
  • 36
1

You ComboBox isn't blank, but it don't know how to render your MaxXXAgeMembers. You should use ItemTemplate to tell this to him. For 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>
ad1Dima
  • 3,185
  • 2
  • 26
  • 37
  • thanks, seems doesn't work though. I think it knows how to render, because of DisplayMemberPath="Description", also, if I select then it works, it's just after the first view, it is blank – litaoshen Aug 23 '16 at 02:39