cjibo
cjibo

Reputation: 4657

WPF MVVM ComboBox SelectedItem or SelectedValue not working

Update

After a bit of investigating. What seems to be the issue is that the SelectedValue/SelectedItem is occurring before the Item source is finished loading. If I sit in a break point and wait a few seconds it works as expected. Don't know how I'm going to get around this one.

End Update

I have an application using in WPF using MVVM with a ComboBox. Below is the ViewModel Example. The issue I'm having is when we leave our page and migrate back the ComboBox is not selecting the current Value that is selected.

View Model

public class MyViewModel
{
     private MyObject _selectedObject;
     private Collection<Object2> _objects;
     private IModel _model;

     public MyViewModel(IModel model)
    {
         _model = model;
         _objects = _model.GetObjects();
    }

    public Collection<MyObject> Objects
    {
         get
         {
              return _objects;
         }
         private set
         {
              _objects = value;
         }
     }

     public MyObject SelectedObject
     {
          get
          {
              return _selectedObject;
          }
          set
          {
               _selectedObject = value;
          }
      }
 }

For the sake of this example lets say MyObject has two properties (Text and Id). My XAML for the ComboBox looks like this.

XAML

<ComboBox Name="MyComboBox" Height="23"  Width="auto" 
    SelectedItem="{Binding Path=SelectedObject,Mode=TwoWay}" 
    ItemsSource="{Binding Objects}"
    DisplayMemberPath="Text"
    SelectedValuePath="Id">

No matter which way I configure this when I come back to the page and the object is reassembled the ComboBox will not select the value. The object is returning the correct object via the get in the property though.

I'm not sure if this is just an issue with the way the ComboBox and MVVM pattern works. The text box binding we are doing works correctly.

Upvotes: 46

Views: 123275

Answers (19)

Rosidin Bima
Rosidin Bima

Reputation: 237

If your combo box options coming from dynamic data (DB or Array) then on the backend side you need to set selected value like this:

ddlBank.SelectedValue = "Your Value";

then on xml file just set

IsSynchronizedWithCurrentItem="True"

Upvotes: 0

Andre
Andre

Reputation: 21

You can also bind your SelectedIndex to a property in your ViewModel and manipulate your SelectedItem that way:

        public int SelectedIndex
        {
            get { return _selectedIndex; }
            set
            {
                _selectedIndex = value;
                OnPropertyChanged();
            }    
        }

And in your XAML:

<ComboBox SelectedIndex="{Binding SelectedIndex,Mode=TwoWay}" ... >

Upvotes: 0

Peter Tadros
Peter Tadros

Reputation: 9297

I solved the problem by adding dispatcher in UserControl_Loaded event

 Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
 {
     combobox.SelectedIndex = 0;
 }));

Upvotes: 3

Dave
Dave

Reputation: 41

When leaving the current page, the CollectionView associated with the ItemsSource property of the ComboBox is purged. And because the ComboBox IsSyncronizedWithCurrent property is true by default, the SelectedItem and SelectedValue properties are reset.
This seems to be an internal data type issue in the binding. As others suggested above, if you use SelectedValue instead by binding to an int property on the viewmodel, it will work. A shortcut for you would be to override the Equals operator on MyObject so that when comparing two MyObjects, the actual Id properties are compared.

Another hint: If you do restructure your viewmodels and use SelectedValue, use it only when SelectedValuePath=Id where Id is int. If using a string key, bind to the Text property of the ComboBox instead of SelectedValue.

Upvotes: 4

rahulpatil2009
rahulpatil2009

Reputation: 676

Setting IsSynchronizedWithCurrentItem="True" worked for me!

Upvotes: 48

Vinicius
Vinicius

Reputation: 1

Use Loaded event:

private void cmb_Loaded(object sender, RoutedEventArgs e) {
    if (cmb.Items.Count > 0) cmb.SelectedIndex = 0;          
}

It works for me.

Upvotes: 0

Breno P. Lucena
Breno P. Lucena

Reputation: 121

In this case, the selecteditem bind doesn't work, because the hash id of the objects are different.

One possible solution is:

Based on the selected item id, recover the object on the itemsource collection and set the selected item property to with it.

Example:

<ctrls:ComboBoxControlBase SelectedItem="{Binding Path=SelectedProfile, Mode=TwoWay}" ItemsSource="{Binding Path=Profiles, Mode=OneWay}" IsEditable="False" DisplayMemberPath="Name" />

The Property binded to ItemSource is:

public ObservableCollection<Profile> Profiles
{
   get { return this.profiles; }
   private set { profiles = value; RaisePropertyChanged("Profiles"); }
}

The property binded to SelectedItem is:

public Profile SelectedProfile 
{
    get { return selectedProfile; }
    set
    {
        if (this.SelectedUser != null)
        {
            this.SelectedUser.Profile = value; 
            RaisePropertyChanged("SelectedProfile");  
        } 
    } 
}

The recovery code is:

    [Command("SelectionChanged")]
    public void SelectionChanged(User selectedUser)
    {
        if (selectedUser != null)
        {
            if (selectedUser is User)
            {
                if (selectedUser.Profile != null)
                {
                    this.SelectedUser = selectedUser;
                    this.selectedProfile = this.Profiles.Where(p => p.Id == this.SelectedUser.Profile.Id).FirstOrDefault();
                    MessageBroker.Instance.NotifyColleagues("ShowItemDetails"); 
                }
            }
        }            
    }

I hope it helps you. I spent a lot of my time searching for answers, but I couldn´t find.

Upvotes: 12

JCH2k
JCH2k

Reputation: 3621

I had this problem with a ComboBox displaying a list of colors ( List<Brush> ).
Selecting a color was possible but it wasnt displayed when the selection closed (although the property was changed!)

The fix was overwriting the Equals(object obj) method for the type selected in the ComboBox (Brush), which wasnt simple because Brush is sealed. So i wrote a class EqualityBrush containing a Brush and implementing Equals:

public class EqualityBrush
{
    public SolidColorBrush Brush { get; set; }

    public override bool Equals(object o)
    {
        if (o is EqualityBrush)
        {
            SolidColorBrush b = ((EqualityBrush)o).Brush;
            return b.Color.R == this.Brush.Color.R && b.Color.G == this.Brush.Color.G && b.Color.B == this.Brush.Color.B;
        }
        else
            return false;
    }
}

Using a List of my new EqualityBrush class instead of normal Brush class fixed the problem!

My Combobox XAML looks like this:

<ComboBox ItemsSource="{Binding BuerkertBrushes}" SelectedItem="{Binding Brush, Mode=TwoWay}" Width="40">
    <ComboBox.Resources>
        <DataTemplate DataType="{x:Type tree:EqualityBrush}">
            <Rectangle Width="20" Height="12" Fill="{Binding Brush}"/>
        </DataTemplate>
    </ComboBox.Resources>
</ComboBox>

Remember that my "Brush"-Property in the ViewModel now has to be of Type EqualityBrush!

Upvotes: 4

Tim
Tim

Reputation: 21

I have a very simply answer for this problem. First add the following code to the View IsSynchronizedWithCurrentItem="True".

Next when ever you assign a new object in the ViewModel to that Property SelectedObject should be saved to that Property and not the private member.

The viewmodel Proptery should look like this

    public Role SelectedObject 
    {
        get { return object; }
        set
        {
            if (value != null)
            {
                if (!object.Equals(value))
                {
                    object = value;
                    OnPropertyChanged(() => SelectedObject );
                }
            }
        }
    }

This should fix the issue.

Upvotes: 2

Dummy01
Dummy01

Reputation: 1995

The type of the SelectedValuePath and the SelectedValue must be EXACTLY the same.

If for example the type of SelectedValuePath is Int16 and the type of the property that binds to SelectedValue is int it will not work.

I spend hours to find that, and that's why I am answering here after so much time the question was asked. Maybe another poor guy like me with the same problem can see it.

Upvotes: 1

Scott McFadden
Scott McFadden

Reputation: 11

I was fighting with this issue for a while. In my case I was using in complex type (List) as the Item Source and was using a KeyType as the selected value. On the load event, the KeyType was getting set to null. This caused everything to break. None of the sub elements would get updated when the key changed. It turned out that when I added a check to make sure the proposed value for KeyType was not null, everything worked as expected.

    #region Property: SelectedKey
    // s.Append(string.Format("SelectedKey : {0} " + Environment.NewLine, SelectedKey.ToString()));

    private KeyType _SelectedKey = new KeyType();
    public KeyType SelectedKey
    {
        get { return _SelectedKey; }
        set
        {
            if(value != null )
                if (!_SelectedKey.Equals(value))
                {
                    _SelectedKey = value;
                    OnPropertyChanged("SelectedKey");
                }
        }
    }
    #endregion SelectedKey

Upvotes: 1

GuestPerson
GuestPerson

Reputation: 231

You need to put the ItemsSource property BEFORE the SelectedItem property. I came across a blog a few days ago mentioning the issue.

Upvotes: 23

Rohit
Rohit

Reputation: 3638

IsSyncronizedWithCurrent=False will make it work.

Upvotes: 1

Sean Campbell
Sean Campbell

Reputation: 11

ComboBox.SelectionBoxItem.ToString()

Upvotes: 0

StefanHa
StefanHa

Reputation: 727

I had the same problem. The thing is. The selected item doesnt know which object it should use from the collection. So you have to say to the selected item to use the item from the collection.

public MyObject SelectedObject
 {
      get
      {
          Objects.find(x => x.id == _selectedObject.id)
          return _selectedObject;
      }
      set
      {
           _selectedObject = value;
      }
 }

I hope this helps.

Upvotes: 2

markti
markti

Reputation: 4518

It could be the way you are applying the DataContext to the Page. In WPF, everytime you navigate to a Page everything gets re-initialized, constructor gets called, loaded methods, everything. so if you are setting your DataContext inside your View you will no doubt be blowing away that SelectedItem that the user selected. In order to avoid that use the KeepAlive property of your pages.

<Page KeepAlive="True" ...>
   ...
</Page>

This will result in only the Loaded event being fired when navigating back to a page you have already visited. So you will need to ensure that you are setting the DataContext on Initialize (either externally or within the constructor) rather than Load.

However, this will only work for that instance of the Page. If you navigate to a new instance of that page it constructor will be called again.

Upvotes: 0

Abe Heidebrecht
Abe Heidebrecht

Reputation: 30498

I have noticed this behavior before as well. I have noticed that the SelectedIndex property doesn't cause the same bug. If you can restructure your ViewModel to expose the index of the selected item, and bind to that, you should be good to go.

Upvotes: 3

Reputation:

I have had similar issues and it was solved by making sure I was implementing IEquatable properly. When the binding occurs, it is trying to see if the objects match so make sure you are properly implementing your equality checking.

Upvotes: 11

Orion Edwards
Orion Edwards

Reputation: 123622

Have you tried implementing INotifyPropertyChanged in your viewmodel, and then raise the PropertyChanged event when the SelectedItem gets set?

If this in itself doesn't fix it, then you will be able to manually raise the PropertyChanged event yourself when navigating back to the page, and that should be enough to get WPF to redraw itself and show the correct selected item.

Upvotes: 27

Related Questions