ComboBox's SelectedValue doesn't update the bound property automatically

Emon Haque 3,176 Reputation points
2020-10-24T06:31:27.517+00:00

I've a ComboBox in one of my Views:

<ComboBox Grid.Row="1"
          IsSynchronizedWithCurrentItem="True"
          ItemsSource="{Binding VacantSpaces}"
          DisplayMemberPath="Name"
          SelectedValuePath="Id"
          SelectedValue="{Binding NewObject.SpaceId, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"/>

when I launch the app and change selection, SelectedValue gets assigned to NewObject.SpaceId. On a button click I new up the NewObject and filter out the space that was selected in the ComboBox this way:

protected override void renewNewObject()
{
    var spaceId = NewObject.SpaceId;
    NewObject = new Lease()
    {
        Id = ++MainVM.maxLeaseId,
        PlotId = NewObject.PlotId,
        //SpaceId = ,
        TenantId = NewObject.TenantId,
        DateStart = DateTime.Now
    };
    OnPropertyChanged(nameof(NewObject));
    NewReceivable.LeaseId = NewObject.Id;

    var space = MainVM.spaces.First(x => x.Id == spaceId);
    space.IsVacant = false;
    space.OnPropertyChanged(nameof(space.IsVacant));
    //var current = VacantSpaces.CurrentItem as Space;
    App.Current.Dispatcher.Invoke(ReceivableHeads.Refresh);;
}

I don't assign the SpaceId to the NewObject when I renew and at that point SpaceId is null. When it hits this line space.OnPropertyChanged(nameof(space.IsVacant)), ComboBox's selection changes but it neither updates the current, VacantSpaces.CurrentItem, nor assigns NewObject 's SpaceId automatically.

If I hookup an event handler on VacantSpaces.CurrentChanged, I see the the CurrentItem changes there but the SpaceId remains null and that event handler is called when it gets out of the renewNewObject function so I've to manually assign SpaceId there!

Why doesn't the ComboBox update NewObject.SpaceId automatically? How to make this assignment automatic?

Developer technologies | Windows Presentation Foundation
0 comments No comments
{count} votes

Accepted answer
  1. Emon Haque 3,176 Reputation points
    2020-10-25T10:24:12.227+00:00

    Probably, it's not possible with Mode=OneWayToSource. To make it automatic, what I've done is: first removed the Mode:

    <cc:ComboField Grid.Row="1" Label="Space"   
                    Source="{Binding VacantSpaces}"  
                    Display="Name"  
                    SelectedValuePath="Id"  
                    SelectedValue="{Binding NewObject.SpaceId}"/>  
    

    now, after the execution of renewNewObject function, the combobox's SelectedItem becomes null so to remove the null from ComboBox, I've to move CurrentItem manually in Combo's OnItemsChanged:

    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)  
    {  
        switch (e.Action)  
        {  
            ...  
            case NotifyCollectionChangedAction.Remove:  
            case NotifyCollectionChangedAction.Reset:  
                if (e.NewItems == null && !Items.IsEmpty)  
                    Items.MoveCurrentToFirst();  
                break;  
        }  
    }  
    

    I'd that in Reset before because CurrentItem remains null when Plots/Spaces.IsEmpty return false, now I've to have that in Remove as well!

    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,341 Reputation points
    2020-10-24T08:16:56.877+00:00

    Hi Emon,
    I didn't unsterstand you problem. You bind SelectedValue to property "SpaceId" of an object in ItemsSource. After select line in ComboBox the selected "Id" ("SelectedValuePath") will be written in NewObject in "SpaceId" ("SelectedValue"). Try following demo:

    XAML:

    <Window x:Class="WpfApp1.Window007"  
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
            xmlns:local="clr-namespace:WpfApp007"  
            mc:Ignorable="d"  
            Title="Window007" Height="450" Width="800">  
      <Window.DataContext>  
        <local:ViewModel/>  
      </Window.DataContext>  
      <StackPanel>  
        <ComboBox Grid.Row="1"  
               IsSynchronizedWithCurrentItem="True"  
               ItemsSource="{Binding VacantSpaces}"  
               DisplayMemberPath="Name"  
               SelectedValuePath="Id"  
               SelectedValue="{Binding NewObject.SpaceId, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"/>  
        <Button Content="New Lease" Command="{Binding}"/>  
        <Label Content="{Binding NewObject.IsVacant}"/>  
      </StackPanel>  
    </Window>  
    

    Classes:

    using System;  
    using System.Collections.Generic;  
    using System.ComponentModel;  
    using System.Linq;  
    using System.Runtime.CompilerServices;  
    using System.Windows;  
    using System.Windows.Input;  
      
    namespace WpfApp007  
    {  
      public class ViewModel : ICommand, INotifyPropertyChanged  
      {  
        public ViewModel()  
        {  
          VacantSpaces.Add(new Lease() { Id = 1, Name = "Name 1" });  
          VacantSpaces.Add(new Lease() { Id = 2, Name = "Name 2" });  
          VacantSpaces.Add(new Lease() { Id = 3, Name = "Name 3" });  
          VacantSpaces.Add(new Lease() { Id = 4, Name = "Name 4" });  
          NewObject = new Lease() { Id = 5, Name = "Name 5", IsVacant = true };  
          OnPropertyChanged(nameof(NewObject));  
        }  
        public Lease NewObject { get; set; }  
      
        public List<Lease> VacantSpaces { get; set; } = new List<Lease>();  
      
        protected void renewNewObject()  
        {  
          var spaceId = NewObject.SpaceId;  
          NewObject = new Lease()  
          {  
            Id = ++MainVM.maxLeaseId,  
            PlotId = NewObject.PlotId,  
            //SpaceId = ,  
            TenantId = NewObject.TenantId,  
            DateStart = DateTime.Now  
          };  
          OnPropertyChanged(nameof(NewObject));  
          NewReceivable.LeaseId = NewObject.Id;  
          var space = MainVM.spaces.First(x => x.Id == spaceId);  
          space.IsVacant = false;  
          space.OnPropertyChanged(nameof(space.IsVacant));  
          //var current = VacantSpaces.CurrentItem as Space;  
          Application.Current.Dispatcher.Invoke(ReceivableHeads.Refresh); ;  
        }  
      
        public void Execute(object parameter) => renewNewObject();  
      
        public event EventHandler CanExecuteChanged;  
        public bool CanExecute(object parameter) => true;  
      
        public event PropertyChangedEventHandler PropertyChanged;  
        private void OnPropertyChanged([CallerMemberName] string propName = "") =>  
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));  
      
        private class MainVM  
        {  
          public static int maxLeaseId { get; internal set; } = 99;  
          public static List<Lease> spaces { get; internal set; } = new List<Lease>() { new Lease() { Id = 3 } };  
        }  
      
        private class NewReceivable  
        {  
          public static int LeaseId { get; internal set; }  
        }  
      
        private class ReceivableHeads  
        {  
          internal static void Refresh()  
          {  
            //throw new NotImplementedException();  
          }  
        }  
      }  
      
      
      
      public class Lease : INotifyPropertyChanged  
      {  
        public int Id { get; set; }  
        public int SpaceId { get; set; }  
        public int PlotId { get; set; }  
        public int TenantId { get; set; }  
        public DateTime DateStart { get; set; }  
        public string Name { get; set; }  
      
        public bool IsVacant { get; set; }  
      
        public event PropertyChangedEventHandler PropertyChanged;  
        public void OnPropertyChanged([CallerMemberName] string propName = "") =>  
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));  
      }  
    }  
    

    Result:

    34683-x.gif

    See Debug:

    34753-x.png

    1 person found this answer helpful.

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.