Andrew Simpson
Andrew Simpson

Reputation: 7324

SelectedValue not updating when set in code

I can see this question has been asked before but nothing seems to work for me.

I have a wpf desktop app.

i have this comboBox:

<ComboBox ItemsSource="{Binding Users, Mode=TwoWay,  UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Value.Login"
   SelectedValue="{Binding SelectedManagerUser, 
   Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"  
   SelectedValuePath="Value"  
   IsSynchronizedWithCurrentItem="True"  />

The data source is a dictionary object:

public Dictionary<string,UserRecord> Users
{
    get
    {
       //get data
    }
    set { _Users =  value; RaisePropertyChanged(Constants.VM_Users); }
}

I add a new entry in my MVVM and update the data.

I then set the selected item in my mvvm:

private UserRecord _selectedManagerUser;
public UserRecord SelectedManagerUser 
{ 
    get
    {
        return _selectedManagerUser;
    }
    set
    {
        _selectedManagerUser = value;
        RaisePropertyChanged("SelectedManagerUser");
    }
}
SelectedManagerUser = Users[temp];

public class UserRecord : ViewModelBase
{
    private int _Active;
    private int _UserRecordId;
    private string _UserRef;
    private string _FName;
    private string _SName;
    private string _Login;
    private string _Salt;
    private int _IsAdmin;
    private string _FullName;
    private string _Branch;
    private string _Position;
    private string _Department;

    public int Disabled { get { return _Active; } set { _Active = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Active); } }
    public int UserRecordId { get { return _UserRecordId; } set { _UserRecordId = value; RaisePropertyChanged("UserRecordId"); } }
    public string UserRef { get { return _UserRef; } set { _UserRef = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_UserRef); } }
    public string FName { get { return _FName; } set { _FName = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_FName); } }
    public string SName { get { return _SName; } set { _SName = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_SName); } }
    public string Login { get { return _Login; } set { _Login = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Login); } }
    public string Salt { get { return _Salt; } set { _Salt = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Salt); } }
    public int IsAdmin { get { return _IsAdmin; } set { _IsAdmin = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_IsAdmin); } }
    public string Branch { get { return _Branch; } set { _Branch = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Branch); } }
    public string Position { get { return _Position; } set { _Position = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Position); } }
    public string Department { get { return _Department; } set { _Department = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Department); } }
    public string FullName { get { return FName + ", " + SName; } set { _FullName = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Fullname); } }
}

I know the new item has been added because -

  1. I can see it int the dropdown
  2. I set a breakpoint in my code and inspect.

The combo box just displays an empty value.

Anything else I can try?

thanks

Upvotes: 2

Views: 837

Answers (4)

user1228
user1228

Reputation:

One of two things can cause this. First, it might be because you're not setting SelectedManagerUser to be an instance of UserRecord that is NOT in the Dictionary, or that Dictionaries still suck for databinding. Lemme cover them both.

When you work with ItemsSource and SelectedItem bindings, if you want SelectedItem changes to be reflected in the UI, you must set it to an instance that can be found within ItemsSource. The control, by default, will look for the item in the source that is referentially equal to the selected item. I'm 99% sure it will use IEquatable<T> instead of reference checking if your items implement it.

If that's not your problem, then it's because Dictionaries suck for databinding.

Dictionaries are TERRIBLE for databinding. Just awful. If you need a keyed collection and you want to bind against it, create a custom collection extending KeyedCollection. With some extra work TItem can implement INPC (make the key read only, tho) and the collection can implement INCC. Works great for binding. Why do I mention this? Read on...

Your problem is that, within the ComboBox, SelectedItem is actually of type KeyValuePair<string,UserRecord>, and NOT UserRecord. So the binding will NOT work. If you grab a copy of Snoop and examine the bindings at runtime, you'll see this.

The problem is that the control doesn't know jack squat about Dictionaries. All it knows is IEnumerable<T>. Dictionary<K,T> implements IEnumerable<KeyValuePair<K,T>>, so the control creates an item for each key value pair. SelectedItem is also a key value pair. So, when you bind that to a property of type UserRecord, yes it is able to use the SelectedValuePath to set the value properly, but it cannot (does not) [ninja edit: unless this behavior has changed over the past few years :/] iterate the enumerable in order to find the correct key value pair when you set the value in your view model.

If UserRecord's key value is a property within the type, then definitely create a KeyedCollection for it. KeyedCollection<Tkey,TItem> implements 'IEnumerable` so it works seamlessly with bindings. If not, wrap it in a proxy, or add the property.

And when I say "wrap it in a proxy", anybody who says "what, like a KeyValuePair?" I'm going to punch you through the internet. The proxy becomes the value you bind against. Don't waste your time with this SelectedValuePath nonsense. Work with the proxies directly. When you need your value, extract it at the last moment, not immediately after the binding executes.

Upvotes: 0

EgoPingvina
EgoPingvina

Reputation: 823

Download Prism from Nuget and inherit your class from BindableBase.

After it use this:

private UserRecord selectedManagerUser;

public UserRecord SelectedManagerUser
{
    get { return this.selectedManagerUser; }
    set { this.SetProperty(ref this.selectedManagerUser, value); }
}

Upvotes: 0

Clemens
Clemens

Reputation: 128061

Not sure what's going wrong on your side, but it might be helpful to look at a working solution.

XAML:

<ComboBox ItemsSource="{Binding Users}"
          DisplayMemberPath="Value.Name"
          SelectedValue="{Binding SelectedUser}"  
          SelectedValuePath="Value" />

Code behind:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        Loaded += WindowLoaded;

        var vm = new ViewModel();
        vm.Users.Add("u1", new UserRecord { Name = "User 1" });
        vm.Users.Add("u2", new UserRecord { Name = "User 2" });
        vm.Users.Add("u3", new UserRecord { Name = "User 3" });
        DataContext = vm;
    }

    private void WindowLoaded(object sender, RoutedEventArgs e)
    {
        // make sure it works after DataContext was set
        var vm = (ViewModel)DataContext;
        vm.SelectedUser = vm.Users["u2"];
    }
}

public class UserRecord
{
    public string Name { get; set; }
}

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public Dictionary<string, UserRecord> Users { get; }
        = new Dictionary<string, UserRecord>();

    private UserRecord selectedUser;
    public UserRecord SelectedUser
    {
        get { return selectedUser; }
        set
        {
            selectedUser = value;
            PropertyChanged?.Invoke(this,
                new PropertyChangedEventArgs(nameof(SelectedUser)));
        }
    }
}

Upvotes: 1

Kim Hoang
Kim Hoang

Reputation: 1368

Your SelectedManagerUser property should be changed to this. The SelectedManagerUser property is set with new value but you do not raise that event so the UI will not be updated.

    private UserRecord _selectedManagerUser;
    public UserRecord SelectedManagerUser 
    { 
        get
        {
            return _selectedManagerUser;
        }
        set
        {
            _selectedManagerUser = value;
            RaisePropertyChanged("SelectedManagerUser");
        }
    }

Upvotes: 0

Related Questions