0

I have a collection of Patients which I set in my ComboBox, whenever I ran and test the form, it seems fine, but whenever I update a record or add another one (from another form), the ComboBox doesn't get updated. I can do a remedy to this by using the code behind and IContent interface but I like to reduce the use of code behind as much as possible. Can you pinpoint to me what markup or code that is lacking?

Here is my Grid Markup (I omitted the RowDefinitions and ColumnDefinitions)

<UserControl.Resources> <common:ImageSourceConverter x:Key="ToImageSourceConverter" /> <businessLogic:PatientMgr x:Key="PatientsViewModel" /> <ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" /> </UserControl.Resources> <Grid x:Name="DetailsGrid" DataContext="{StaticResource ItemsSource}"> <Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2"> <Image x:Name="InsideButtonImage" VerticalAlignment="Center" HorizontalAlignment="Center" StretchDirection="Both" Stretch="Uniform" Source="{Binding ElementName=FullNameComboBox, Path=SelectedItem.PictureId, Converter={StaticResource ToImageSourceConverter}, UpdateSourceTrigger=PropertyChanged}" /> </Border> <TextBox x:Name="IdTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.Id, UpdateSourceTrigger=PropertyChanged}" Grid.Row="0" Grid.Column="1" Visibility="Collapsed"/> <ComboBox x:Name="FullNameComboBox" Grid.Row="10" Grid.Column="1" IsEditable="True" ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath = "FullName" SelectedIndex="0" SelectionChanged="FullNameComboBox_OnSelectionChanged" /> <TextBox x:Name="GenderTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.GenderName, UpdateSourceTrigger=PropertyChanged}" Grid.Row="11" Grid.Column="1" IsReadOnly="True" IsReadOnlyCaretVisible="True"/> </Grid> 

Here is my BusinessLogicLayer

public class PatientMgr :INotifyPropertyChanged { #region Fields private readonly PatientDb _db; private Patient _entity; private List<Patient> _entityList; private ObservableCollection<Patient> _comboBoxItemsCollection; private Patient _selectedItem; #endregion #region Properties public Patient Entity { get { return _entity; } set { if (Equals(value, _entity)) return; _entity = value; OnPropertyChanged(); } } public List<Patient> EntityList { get { return _entityList; } set { if (Equals(value, _entityList)) return; _entityList = value; OnPropertyChanged(); } } public ObservableCollection<Patient> ComboBoxItemsCollection { get { return _comboBoxItemsCollection; } set { if (Equals(value, _comboBoxItemsCollection)) return; _comboBoxItemsCollection = value; OnPropertyChanged(); } } public Patient SelectedItem { get { return _selectedItem; } set { if (Equals(value, _selectedItem)) return; _selectedItem = value; OnPropertyChanged(); } } #endregion #region Constructor public PatientMgr() { _db = new PatientDb(); Entity = new Patient(); EntityList = new List<Patient>(); Parameters = new Patient(); ComboBoxItemsCollection = new ObservableCollection<Patient>(_db.RetrieveMany(Entity)); SelectedItem = ComboBoxItemsCollection[0]; } #endregion public List<Patient> RetrieveMany(Patient parameters) { return _db.RetrieveMany(parameters); } public event PropertyChangedEventHandler PropertyChanged; [NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { var handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } } 

3 Answers 3

1

Because ItemsSource used statically, your ComboBox doesn't get notified when it's source changed. Try using an instance of PatientMgr as your UserControl.DataContext like this:

public partial class YourUserControl: UserControl { private PatientMgr PatientsViewModel; public YourUserControl() { InitializeComponent(); PatientsViewModel = new PatientMgr(); DataContext = PatientsViewModel; } public void AddComboItem(Patient item) { PatientsViewModel.ComboBoxItemsCollection.Add(item); } public void UpdateComboItem(Patient item) { //your update code } 

After removing static bindings, your XAML should looks like this:

<UserControl.Resources> <common:ImageSourceConverter x:Key="ToImageSourceConverter" /> </UserControl.Resources> <Grid x:Name="DetailsGrid" <Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2"> <Image x:Name="InsideButtonImage" ... 

Since ComboBoxItemsCollection is an ObservableCollection, it notifies UI on add/remove automatically, however you have to write proper update functionality in order to force UI to refresh.

EDIT:

To implement an update method by taking advantage of Editable feature of ComboBox you could follow these steps:

  1. Defined an integer property to track the last valid index (index != -1) of combo-box selected item and bind it to FullNameComboBox.SelectedIndex.
  2. Defined a string property that represents the text value of combo-box selected item and bind it to FullNameComboBox.Text (the binding should trigger on LostFocus so the changes only accepted when user finished typing).
  3. Find and remove ComboBoxItemsCollection.OldItem (find it by last valid index) and insert ComboBoxItemsCollection.ModifiedItem when FullNameComboBox.Text changes. Using remove and insert instead of assigning (OldItem = ModifiedItem;) will force UI to update.

In PatientMgr Code:

public int LastValidIndex { get { return _lastIndex; } set { if (value == -1) return; _lastIndex = value; OnPropertyChanged(); } } public string CurrentFullName { get { return SelectedItem.FullName; } set { var currentItem = SelectedItem; ComboBoxItemsCollection.RemoveAt(LastValidIndex); currentItem.FullName = value; ComboBoxItemsCollection.Insert(LastValidIndex, currentItem); SelectedItem = currentItem; } } 

In UserControl.Xaml :

<ComboBox x:Name="FullNameComboBox" Grid.Row="1" IsEditable="True" ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedItem}" SelectedIndex="{Binding LastValidIndex}" IsTextSearchEnabled="False" Text="{Binding CurrentFullName, UpdateSourceTrigger=LostFocus}" DisplayMemberPath = "FullName" /> 
Sign up to request clarification or add additional context in comments.

2 Comments

can u give me an example on how to force the ui to refresh? i've been reading and trying all day. the IContent seems to work but still has a lot of weakness (like a user control is inside another user control) and I am using Modern ui so I use NavigationCommands.GoToPage.Execute("/Pages/nameofform.xaml", this); which annoys me if the user control is inside another user control
amazing! I have never thought you can also use LostFocus for updating source trigger
0

I don’t like this:

public ObservableCollection<Patient> ComboBoxItemsCollection { get { return _comboBoxItemsCollection; } set { if (Equals(value, _comboBoxItemsCollection)) return; _comboBoxItemsCollection = value; OnPropertyChanged(); } } 

Try this:

OnPropertyChanged(“ComboBoxItemsCollection”); 

And, are you sure this equals is resolved right?

if (Equals(value, _comboBoxItemsCollection)) return; 

Try to debug it …

Comments

0

Your problem has to do with that IsEditable="True" set on the ComboBox. Your bindings work fine for me.

Here is what i've just tried:

Simple Patient model with its FullName property:

public class Patient : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged = delegate { }; private string _FullName; public string FullName { get { return _FullName; } set { _FullName = value; PropertyChanged(this, new PropertyChangedEventArgs("FullName")); } } } 

This is the XAML:

<Window x:Class="PatientsStack.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PatientsStack" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <local:PatientsMgr x:Key="PatientsViewModel"/> <ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" /> </Window.Resources> <Grid DataContext="{StaticResource ItemsSource}"> <StackPanel> <ComboBox x:Name="FullNameComboBox" IsEditable="True" ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}" TextSearch.TextPath="FullName" DisplayMemberPath="FullName" SelectedItem="{Binding SelectedPatient}" Text="{Binding SelectedPatient.FullName, Mode=TwoWay}" VerticalAlignment="Top" IsTextSearchEnabled="False" SelectedIndex="0"> </ComboBox> <Button Content="Change name" Command="{Binding ChangeNameCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/> <Button Content="Add patient" Command="{Binding AddPatientCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/> </StackPanel> </Grid> 

This two pieces right here did the job:

SelectedItem="{Binding SelectedFilter}" Text="{Binding SelectedPatient.FullName, Mode=TwoWay}" 

Without the Text binding, the updated worked, but you didn't see it unless you click the drop down.

As you see, i have 2 command to change one existing Patient's FullName or to add a new one. Both work as expected.

Here is the ViewModel for this:

public class PatientsMgr : INotifyPropertyChanged { private ObservableCollection<Patient> _ComboBoxItemsCollection; public ObservableCollection<Patient> ComboBoxItemsCollection { get { return _ComboBoxItemsCollection; } set { _ComboBoxItemsCollection = value; PropertyChanged(this, new PropertyChangedEventArgs("ComboBoxItemsCollection")); } } private Patient _SelectedPatient; public Patient SelectedPatient { get { return _SelectedPatient; } set { _SelectedPatient = value; PropertyChanged(this, new PropertyChangedEventArgs("SelectedPatient")); } } public ICommand ChangeNameCommand { get; set; } public ICommand AddPatientCommand { get; set; } public event PropertyChangedEventHandler PropertyChanged = delegate { }; public PatientsMgr() { ComboBoxItemsCollection = new ObservableCollection<Patient>(); ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient1" }); ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient2" }); ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient3" }); ChangeNameCommand = new RelayCommand<Patient>(ChangePatientName); AddPatientCommand = new RelayCommand<Patient>(AddPatient); } public void ChangePatientName(Patient patient) { patient.FullName = "changed at request"; } public void AddPatient(Patient p) { ComboBoxItemsCollection.Add(new Patient() { FullName = "patient added" }); } } 

I am posting my RelayCommand too, but i am sure you have it defined for your actions:

public class RelayCommand<T> : ICommand { public Action<T> _TargetExecuteMethod; public Func<T, bool> _TargetCanExecuteMethod; public RelayCommand(Action<T> executeMethod) { _TargetExecuteMethod = executeMethod; } public bool CanExecute(object parameter) { if (_TargetExecuteMethod != null) return true; return false; } public event EventHandler CanExecuteChanged; public void Execute(object parameter) { T tParam = (T)parameter; if (_TargetExecuteMethod != null) _TargetExecuteMethod(tParam); } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.