2

I am using MVVM in a WPF application with C# and got a problem binding a ComboBox correctly.

This is my ComboBox line in the XAML:

<ComboBox ItemsSource="{Binding Repository.Models}" SelectedValue="{Binding Repository.SelectedModel}" DisplayMemberPath="Name"></ComboBox>

This is the interesting part of my Repository:

 class Repository : INotifyPropertyChanged
    {
        //init MVVM pattern
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private ObservableCollection<Model> _models;
        public ObservableCollection<Model> Models
        {
            get
            {
                return _models;
            }

            set
            {
                _models = value;
                NotifyPropertyChanged("Models");
            }
        }

        private Model _selectedModel;
        public Model SelectedModel
        {
            get
            {
                return _selectedModel;
            }

            set
            {
                _selectedModel = value;
                NotifyPropertyChanged("SelectedModel");
            }
        }  

This is the interesting part of my Model class:

 abstract class Model : INotifyPropertyChanged
    {
        //init MVVM pattern
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private string _name;
        public string Name
        {
            get
            {
                return _name;
            }

            set
            {
                _name = value;
                NotifyPropertyChanged("Name");
            }
        }

So when I select/change different items of the combobox a DataGrid that is binded to Repository.SelectedModel.Parameters does update just as i want it to. Because of that I know, that the binding does work!

When I restart the application and debug into my Repository, I see that there is a SelectedModel (deserialised on startup) but the ComboBox stays blank. The DataGrid though does show the right data.

So the binding itself does work, but the binding to the ComboBoxLabel somehow fails.

I tried a lot of things like switching between SelectedItem and SelectedValue, between Binding and Binding Path, between IsSynchronizedWithCurrentItem true and false, but nothing worked so far.

Do you see my mistake? Thanks in advance!

EDIT Here is the interesting part of my MainWindowViewModel:

class MainWindowViewModel : INotifyPropertyChanged
    {
        //init MVVM pattern
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private Repository _repository;
        public Repository Repository
        {
            get
            {
                return _repository;
            }

            set
            {
                _repository = value;
                NotifyPropertyChanged("Repository");
            }
        }

And here is my App.xaml.cs where I init my DataContext:

   //init point of app
    public partial class App : Application
    {
        private MainWindowViewModel mainWindowViewModel;

        //gets fired as the app starts
        protected override void OnStartup(StartupEventArgs e)
        {
            //create the ViewModel
            mainWindowViewModel = new MainWindowViewModel();

            //create the mainWindow
            var mainWindow = new MainWindow();        
            mainWindow.DataContext = mainWindowViewModel;

            //show the mainWindow
            mainWindow.Show();
        }
selmaohneh
  • 553
  • 2
  • 17
  • I'm guessing there is no path `Repository.Models` (assuming your main model doesn't have `Repository` property of type `Repository`). You set binding context to `Repository` instance and then `ItemsSource="{Binding Models}" SelectedItem="{Binding SelectedModel}`. How does it even display something? How do you set `DataContext`? Do you set `ItemsSource` manually in code? – dkozl Jun 01 '16 at 18:35
  • Added the missing code. The path DOES exists as you can see now. – selmaohneh Jun 01 '16 at 18:38
  • Assuming that you've changed `SelectedValue` to `SelectedItem` is setter of `SelectedModel` ever called? Any binding errors in the output window? Is it all done from single thread? – dkozl Jun 01 '16 at 18:42
  • Yes, right away when I start the app and afterwards every time when I select something in the combobox -> just as it should. Also my DataGrid updates correctly. But the Combobox still keeps blank after the start although my SelectedModel is se. – selmaohneh Jun 01 '16 at 18:45

2 Answers2

2

When I restart the application and debug into my Repository, I see that there is a SelectedModel (deserialised on startup) but the ComboBox stays blank. The DataGrid though does show the right data.

Looks like the deserialization is the problem.

You have a selected item which was deserialized. That means that a new Model instance was created which has a Name of whatever, and Properties that are whatever. And you have a list of Model instances in an ObservableCollection<Model> which are displayed in a ComboBox.

And you assure us that at least sometimes, you have ComboBox.SelectedItem bound to SelectedModel, though for some reason the code in your question binds ComboBox.SelectedValue instead. That's not going to work. Here's how ComboBox.SelectedValue would be used:

<ComboBox
    ItemsSource="{Binding Repository.Models}" 
    SelectedValuePath="Name"
    SelectedValue="{Binding SelectedModelName}"
    />

...and you would have to have a String SelectedModelName { get; set; } property on your viewmodel. The Name property of the selected Model would be assigned to that by the ComboBox when the selection changed. But you don't have SelectedModelName, and you don't want it, so forget about SelectedValue.

Back to SelectedItem. The ComboBox gets the value of SelectedModel from the binding, and tries to find that exact object in its list of Items. Since that exact object is not in that list, it selects nothing. There is probably an item in Repository.Models that has the same name and has identical Properties, but it is not the same actual instance of the Model class. ComboBox doesn't look for an identical twin of the value in SelectedItem; it looks for the same object.

SelectedModel.Properties works in the DataGrid because the DataGrid doesn't know or care what's in Models. You give it a collection, it's good.

So: If you want to deserialize a SelectedModel and have it mean anything, what you need to do is go ahead and deserialize, but then find the equivalent item in Repository.Models (same Name, same Properties), and assign that actual object instance to SelectedModel.

You may be tempted to overload Model.Equals(). Don't. I've done that to solve the same problem. The resulting behavior is not expected in C# and will bite you, hard, and when you least expect it, because you are invisibly altering behavior that happens in framework code. I've spent days tracking down bugs I created that way, and I'll never do it to myself again.

  • Thank you very much, you opened my eyes. As you said I looked for the model with the same name as my deserialized selectedModel via a LINQ expression. Works great. – selmaohneh Jun 02 '16 at 07:06
  • @EdPlunkett I faced some strange behavior. That is right after initialization it's blank, but you can select and it shows up, I'm wondering if you can give me some idea, thank you! http://stackoverflow.com/questions/39090923/why-this-uwp-combobox-can-be-blank-after-initialization-but-works-fine-for-selec – litaoshen Aug 23 '16 at 20:18
1

Try SelectedItem instead of SelectedValue in ComboBox.