7

I am trying to bind my ViewModel to my ComboBox. I have ViewModel class defined like this:

class ViewModel
{
    public ViewModel()
    {
        this.Car= "VW";
    }

    public string Car{ get; set; }
}

I set this ViewModel as DataContext in Window_Load like:

   private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        this.DataContext = new CarModel();
    }

Then in my xaml, I do this to bind my ComboBox to this ViewModel. I want to show the "VW" as selected by default in my ComboBox:

        <ComboBox Name="cbCar" SelectedItem="{Binding Car, UpdateSourceTrigger=PropertyChanged}">
            <ComboBoxItem Tag="Mazda">Mazda</ComboBoxItem>
            <ComboBoxItem Tag="VW">VW</ComboBoxItem>
            <ComboBoxItem Tag="Audi">Audi</ComboBoxItem>
        </ComboBox>

I have 2 questions:

  1. How do I set default value selected in Combo Box to "VW" (once form loads, it should show "VW" in combo box).
  2. Instead of setting ComboBoxItems like above in xaml, how to I set it in my ViewModel and then load these in ComboBox?

Thanks,

UPDATE: So far, I manage to implement this but I get error as below in the ViewModel c-tor:

namespace MyData
{
    class ViewModel
    {
        public ViewModel()
        {
            this.Make = "";
            this.Year = 1;

            this.DefaultCar = "VW"; //this is where I get error 'No string allowed'
        }

        public IEnumerable<Car> Cars
        {
            get
            {
                var cars = new Car[] { new Car{Model="Mazda"}, new Car{Model="VW"}, new Car{Model="Audi"} };
                DefaultCar = cars.FirstOrDefault(car => car.Model == "VW"); 
            }
        }

        public string Make { get; set; }
        public int Year { get; set; }
        public Car DefaultCar { get; set; }
    }

    class Car
    {
        public string Model { get; set; }
    }
}
pixel
  • 9,653
  • 16
  • 82
  • 149
  • Ok from your new update, Make and Year properties should live in Car class, not in ViewModel. DefaultCar should be assigned to an instance of car, not to a string, and remove it from your ViewModel constructor. – E-Bat Dec 16 '15 at 21:19
  • @E-Bat Are you saying that ViewModel CLASS should only hold IEnumerable Cars and everything else should be moved to Car CLASS? Or I should just remove it from ViewModel C-TOR? And how to assign DefaultCar to instance of a car in this case? Isn't it already assigned using the expression above? Would it be possible to show the change please? – pixel Dec 16 '15 at 21:28
  • 1
    dbnex14, In this case ViewModel should only contain Cars and DefaultCar properties. DefaultCar can be assigned in the very moment you get the Cars from your data store. When your View loads and it tries to resolve its bindings, them Cars property will be queried from your View ( triggered by ItemsSource property from ComboBox ). Then in this moment you can assign DefaultCar before returning the list. See my answer bellow. – E-Bat Dec 16 '15 at 21:56
  • 1
    BTW, it should be SelectedCar not DefaultCar as it is how it defined in SelectedItem binding for ComboBox – E-Bat Dec 16 '15 at 22:09
  • @E-Bat I think my question is misleading. So, I marked this as answered and created new one at http://stackoverflow.com/questions/34326241/wpf-how-to-bind-combobox It is explaining better what I want to achieve. – pixel Dec 17 '15 at 03:43
  • to accept the answer and close the question you have to check the post bellow where I posted my code to you. Thanks. – E-Bat Dec 17 '15 at 14:12

2 Answers2

11

As you are going to implement MVVM it will be a lot better if you start to think in objects to represent Cars in your application:

    public class ViewModel
    {    
            public Car SelectedCar{ get; set; }
            public ObservableCollection<Car> Cars{ 
                get  {
                    var cars = new ObservableCollection(YOUR_DATA_STORE.Cars.ToList());
                    SelectedCar = cars.FirstOrDefault(car=>car.Model == "VW");
                    return cars;
                }
            }
    }
    
    public class Car 
    {
        public string Model {get;set;}
        public string Make { get; set; }
        public int Year { get; set; }
    }

Your Xaml:

<ComboBox SelectedItem="{Binding SelectedCar}", ItemsSource="{Binding Cars}" 
        UpdateSourceTrigger=PropertyChanged}"/>
E-Bat
  • 4,792
  • 1
  • 33
  • 58
  • Thanks. how would you rewrite this line, I dont understand what is it doing as "car" is nowhere defined "SelectedCar = cars.FirstOrDefault(car=>car.Model == "VW");"? Thansk, – pixel Dec 16 '15 at 20:33
  • 1
    @dbnex14, car variable represents each item in the collection of cars, it does not need to be defined before. Look at the syntaxis car=>. This is lambda expression. – E-Bat Dec 16 '15 at 20:38
  • 1
    @dbnex14 please look at this question for lambda expressions: http://stackoverflow.com/questions/3970219/c-sharp-lambda – E-Bat Dec 16 '15 at 20:41
  • I added update. it errors out on that line. Thanks – pixel Dec 16 '15 at 20:49
  • 1
    @dbnex14 You are defining an array of string, when it should be an array of Car ;) var cars = new Car[] { new Car { Model = "Mazda"}, new Car { Model ="VW"}, new Car {Model = "Audi"}, new Car { Model = "BMW"}, new Car { Model = "Honda"} }; – E-Bat Dec 16 '15 at 20:54
  • Yes, I am new to this, find the syntax confusing. I updated again the update in my question to reflect this but now I am getting another error as per comment in the update. Much appreciated E-Bat – pixel Dec 16 '15 at 21:13
5
  1. Default Value: If you set viewModel.Car = "VW", then it should auto-select that item in the combo box. For this to work you will need to either implement INotifyPropertyChanged or set Car before you set DataContext.

INotifyPropertyChanged implementation might look like:

class ViewModel : INotifyPropertyChanged
{
    private string car;

    public ViewModel()
    {
        this.Car = "VW";
        this.Cars = new ObservableCollection<string>() { "Mazda", "VW", "Audi" };
    }

    public string Car
    {
        get
        {
            return this.car;
        }
        set
        {
            if (this.car != value)
            {
                this.car = value;
                OnPropertyChanged();
            }
        }
    }

    public ObservableCollection<string> Cars { get; }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2. Bind ItemsSource and SelectedItem.

<ComboBox ItemsSource="{Binding Cars}"
          SelectedItem="{Binding Car, Mode=TwoWay}">
</ComboBox>

You can also set ComboBox.ItemTemplate if your source or view is more complex than just displaying a string:

    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding}" />
        </DataTemplate>
    </ComboBox.ItemTemplate>

In the view model just add a list property:

public ObservableCollection<string> Cars { get; set; }

It doesn't have to be ObservableCollection but that type will auto-update the UI whenever you change the collection.

FUR10N
  • 750
  • 6
  • 18
  • Where do you set Cars to their values "Mazda", "VW", "Audi"? Thanks – pixel Dec 16 '15 at 20:39
  • I am using my model to set some default values on form. Sorry, I am new to WPF, just want to clarify why I need this. So, when forms open, these values should be showing by default in my controls (in this example the Car combo box but I posted update with couple of other controls). – pixel Dec 16 '15 at 20:52