1

I have a ViewModel which has a collection and a property representing the selected value in the collection. In my view this is shown in a ComboBox. When "filling" my ViewModel, the selected item is not shown in my view.

My ViewModel

public class DriverViewModel : MasterDataWithAddressViewModel<Driver>
{
    public ObservableCollection<Vehicle> Vehicles
    {
        get { return _vehicles; }
        set
        {
            if (_vehicles != value)
            {
                _vehicles = value;
                RaisePropertyChanged("Vehicles");
            }
        }
    }
    public Vehicle SelectedVehicle
    {
        get { return PrimaryModel.Vehicle; }
        set
        {
            if (PrimaryModel.Vehicle != value)
            {
                PrimaryModel.Vehicle = value;
                RaisePropertyChanged("SelectedVehicle");
            }
        }
    }
}

The setter of SelectedVehicle is called correctly and so is RaisePropertyChanged("SelectedVehicle"); ...

My ComboBox

<ComboBox DisplayMemberPath="Number" 
          ItemsSource="{Binding Vehicles, UpdateSourceTrigger=PropertyChanged}" 
          SelectedValue="{Binding SelectedVehicle, UpdateSourceTrigger=PropertyChanged}" />

I've also tried it that way:

<ComboBox DisplayMemberPath="Number" 
           ItemsSource="{Binding Vehicles, UpdateSourceTrigger=PropertyChanged}" 
           SelectedItem="{Binding SelectedVehicle, UpdateSourceTrigger=PropertyChanged}" 
            IsSynchronizedWithCurrentItem="True" />

Selecting a value in the ComboBox "manually" (via the view) works without problems. Doing it by "filling" the ViewModel in the code doesn't work.

Can anyone help ?

mosquito87
  • 4,270
  • 11
  • 46
  • 77

3 Answers3

3

Because your property SelectedVehicle is reference type.
When you binding SelectedValue to ViewModel.SelectedVehicle
combobox compare objects of bounded Collection to object of SelectedVehicle

Comparing happened by calling .Equals method, which by default compares references and return true if both objects reference to the same memory address

Because selected item not showed up, I assume reference of SelectedVehicle not in the collection Vehicles.

You can override Equals method in the class Vehicle to compare by some property. In your case this will be most shorter way.
Use your second approach in view with SelectedItem and override Equals method in the Vehicle class:

public override bool Equals(Person compareTo)
{
    if (compareTo == null)
        return false;
    return (this.ID == compareTo.ID);
}

Or I preferred next approach more
Use property which identify Vehicle for ValueMemberPath

<ComboBox DisplayMemberPath="Number" 
      ValueMemberPath="Number"
      ItemsSource="{Binding Vehicles, UpdateSourceTrigger=PropertyChanged}" 
      SelectedValue="{Binding SelectedVehicle, UpdateSourceTrigger=PropertyChanged}" />

In ViewModel

public Int32 SelectedVehicleNumber
{
    get { return PrimaryModel.Vehicle.Number; }
    set
    {
        if (PrimaryModel.Vehicle.Number != value)
        {
            PrimaryModel.Vehicle = New Vehicle(value);//Create instance by selected value
            RaisePropertyChanged("SelectedVehicle");
        }
    }
}

And one more approach with KeyedCollection

Fabio
  • 31,528
  • 4
  • 33
  • 72
1

Try binding SelectedItem instead of SelectedValue.

Also, make sure that the Vehicles property is being set before the SelectedVehicle property.

Jay
  • 56,361
  • 10
  • 99
  • 123
  • Sorry, forgot to mention that I've already tried that according to the answer @ http://stackoverflow.com/questions/19632270/binding-combobox-selecteditem-using-mvvm ... See my updates question. – mosquito87 Jun 06 '15 at 14:01
0

I've made a small example on this:

I have this simple View:

<Window x:Class="ComboSelectedItemBinding.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ComboSelectedItemBinding"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <Grid.DataContext>
        <local:ViewModel/>
    </Grid.DataContext>
    <StackPanel>
        <ComboBox ItemsSource="{Binding vehicles, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding VehicleSelected}" DisplayMemberPath="Name" SelectedIndex="0" VerticalAlignment="Top"/>
        <TextBox x:Name="VehName" MinWidth="120" Margin="80,10" />
        <Button Content="Change selection" Command="{Binding ChangeCommand}" CommandParameter="{Binding ElementName=VehName, Path=Text}" Margin="10" Width="150"/>
    </StackPanel>

</Grid>

This is the model :

public class Vehicle : INotifyPropertyChanged
{
    private string _Name;

    public string Name
    {
        get { return _Name; }
        set { _Name = value; }
    }


    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName ));
    }
}

The ViewModel:

public class ViewModel : DependencyObject
{
    public ObservableCollection<Vehicle> vehicles { get; set; }

    public ICommand ChangeCommand { get; set; }


    public Vehicle VehicleSelected
    {
        get { return (Vehicle)GetValue(VehicleSelectedProperty); }
        set { SetValue(VehicleSelectedProperty, value); }
    }

    // Using a DependencyProperty as the backing store for VehicleSelected.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty VehicleSelectedProperty =
        DependencyProperty.Register("VehicleSelected", typeof(Vehicle), typeof(ViewModel), new PropertyMetadata(null));



    public ViewModel()
    {
        vehicles = new ObservableCollection<Vehicle>();
        Vehicle veh1 = new Vehicle() { Name = "V1" };
        Vehicle veh2 = new Vehicle() { Name = "V2" };
        Vehicle veh3 = new Vehicle() { Name = "V3" };
        Vehicle veh4 = new Vehicle() { Name = "V4" };

        vehicles.Add(veh1);
        vehicles.Add(veh2);
        vehicles.Add(veh3);
        vehicles.Add(veh4);

        ChangeCommand = new ChangeCommand(this);
    }
}

And the Command for the Button:

    public class ChangeCommand : ICommand
{
    public ViewModel _vm = null;

    public ChangeCommand(ViewModel vm)
    {
        _vm = vm;
    }
    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
       _vm.VehicleSelected = _vm.vehicles.First( name => name.Name.Equals(parameter.ToString()));
    }
}

In order to make a change in that combobox you have to keep a reference to one of the objects in its bounded collection. To achieve that i've added the way to change the selection according to the vehicle name. With the vehicle name, you search the referenced item in the list and set it as the SelectedItem which is defined as a DependencyProperty just to make a small variation.

Olaru Mircea
  • 2,570
  • 26
  • 49