简体   繁体   中英

Set SelectedItem of ComboBox from object

I'm building an MVVM Light WPF app in Visual Studio 2015 with Entity Framework 6 (EF) providing the data. I have a ComboBox that displays the reasons why someone needs to take a drug test and it looks like this:

<ComboBox ItemsSource="{Binding ReasonsForTest}"
    SelectedItem="{Binding Path=ReasonsForTestVm,
                     UpdateSourceTrigger=PropertyChanged}"
    DisplayMemberPath="Description" />

The ReasonsForTest is of type ReasonForTestViewModel class:

public class ReasonForTestViewModel: ViewModelBase
{
    private int _ReasonForTestId;
    private string _ReasonForTestAbbr;
    private string _description;

    public int ReasonForTestId
    {
        get { return _ReasonForTestId; }
        set
        {
            if (value == _ReasonForTestId) return;
            _ReasonForTestId = value;
            RaisePropertyChanged();
        }
    }

    public string ReasonForTestAbbr
    {
        get { return _ReasonForTestAbbr; }
        set
        {
            if (value == _ReasonForTestAbbr) return;
            _ReasonForTestAbbr = value;
            RaisePropertyChanged();
        }
    }

    public string Description
    {
        get { return _description; }
        set
        {
            if (value == _description) return;
            _description = value;
            RaisePropertyChanged();
        }
    }
}

I have a data service class that contains the following code to fetch the data for the valid values of the ComboBox :

public async Task<ObservableCollection<ReasonForTestViewModel>> GetReasonsForTest()
{
    using (var context = new MyEntities())
    {
        var query = new ObservableCollection<ReasonForTestViewModel>
            (from rt in context.ReasonForTests
             orderby rt.description
             select new ReasonForTestViewModel
             {
                 ReasonForTestId = rt.ReasonForTestID,
                 ReasonForTestAbbr = rt.ReasonForTestAbbr,
                 Description = rt.description,
             });
        return await Task.Run(() => query);
    }
}

The view model populates the ComboBox using this:

var dataService = new TestDataService();    
ReasonsForTest = await dataService.GetReasonsForTest();

The ComboBox has the correct data; however, it's not selecting the correct value when the app starts -- it's showing blank on load. The SelectedItem ( ReasonsForTestVm ) is also of that class type ReasonForTestViewModel and gets populated from the database with the one item for this person. I've stepped through the code to ensure ReasonsForTestVm has the correct data, and it does.

Here's the property for ReasonsForTestVm :

public ReasonForTestViewModel ReasonForTestVm
{
    get
    {
        return _reasonForTestVm;
    }

    set
    {
        if (Equals(value, _reasonForTestVm)) return;
        _reasonForTestVm = value;
        RaisePropertyChanged();
    }
}

What am I doing wrong here? I'm about to lose my mind!

Update : Sorry for the confusing name in the property above. Fixed.

Any WPF items control that extends Selector (such as ComboBox and ListBox) has two properties that are often used in conjunction: ItemsSource and SelectedItem .

When you bind a collection to ItemsSource , a representation of those items are shown in the UI. Each one of the representations is bound to an instance found within the collection bound to ItemsSource . If, for an example, you're using a DataTemplate to create that representation, you'll find within each that the DataContext will be one of those instances from the collection.

When you select one of these representations, the SelectedItem property now holds the instance from the collection that was bound to that representation.

This works perfectly through user interaction with the UI. However, there's one important caveat when interacting with these controls programmatically .

It's a very common pattern to bind these properties to similar properties in your view model.

public class MuhViewModel
{
    public MuhItems[] MuhItems {get;} = new[]{ new Item(1), new Item(2) };

    // I don't want to show INPC impls in my sample code, kthx
    [SuperSlickImplementINotifyPropertyChangedAttribute]
    public MuhSelectedItem {get;set;}
}

bound to

<ComboBox ItemsSource="{Binding MuhItems}"
          SelectedItem="{Binding MuhSelectedItem}" />

If you try to manually update the selected item this way...

muhViewModel.MuhSelectedItem = new Item(2);

The UI will not change. The Selector sees that ItemsSource has changed, yes, but it doesn't find that instance in the ItemsSource collection. It doesn't know that one instance of Item with a value of 2 is equivalent to any other Item with the same value. So it does nothing. (That's a bit simplistic for what really happens. You can bust out JustDecompile and see for yourself. It gets real convoluted down in there.)

What you should be doing in this situation is updating SelectedItem with an instance found within the collection bound to ItemsSource . In our example,

var derp = muhViewModel.MuhItems.FirstOrDefault(x => x.MuhValue == 2);
muhViewModel.MuhSelectedItem = derp;

Side note, when tracking instances within a debug session, it helps to use Visual Studio's Make Object ID feature.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM