I’m new to WPF and its Databinding, but I stumbled upon a strange behaviour I could not resolve for myself.
In a Dialog I’ve got a Listbox with Users and a TextBox for a username. Both are bound to a UserLogonLogic-which publishes among others a CurrentUser property.
I want the TextBox to update its text when I click on a name in the ListBox. I also want the SelectedItem in the ListBox to be updated when I enter a username directly into the TextBox. Partial names in the TextBox will be resolved to the first matching value in the listbox or null if there is none.
At first the TextBox gets updated every time I click into the ListBox. Debug shows me that every time the PropertyChangeEvent for CurrentUser is fired the method txtName_TextChanged method is called. Only after I have typed something into the textbox the DataBinding of the TextBox seems to be lost. There will be no further updates of the TextBox when I click into the ListBox. Debug now shows me that the method txtName_TextChanged is no longer being called after the CurrentUser PropertyChangeEvent is fired.
Does anybody have an idea where I could have gone wrong?
Thanks a lot
Rü
UserLogon.xaml:
<ListBox Grid.Column='0' Grid.Row='1' Grid.RowSpan='4' MinWidth='100' Margin='5' Name='lstUser' MouseUp='lstUser_MouseUp' ItemsSource='{Binding Path=Users}' SelectedItem='{Binding Path=CurrentUser, Mode=TwoWay}'/> <TextBox Grid.Column='1' Grid.Row='1' Margin='3' Name='txtName' TextChanged='txtName_TextChanged' Text='{Binding Path=CurrentUser, Mode=OneWay}' />
UserLogon.xaml.cs:
public UserLogon() { InitializeComponent(); _logic = new UserLogonLogic(); TopLevelContainer.DataContext = _logic; } private int _internalChange = 0; private void txtName_TextChanged(object sender, TextChangedEventArgs e) { if (_internalChange > 0) { return; } _internalChange++; string oldName = txtName.Text; User user = _logic.SelectByPartialUserName(oldName); string newName = (user == null) ? '' : user.Name; if (oldName != newName) { txtName.Text = (newName == '') ? oldName : newName; txtName.Select(oldName.Length, newName.Length); } _internalChange--; }
UserLogon.Logic.cs:
public class UserLogonLogic : INotifyPropertyChanged { private User _currentUser; public User CurrentUser { get { return _currentUser; } set { if (value != CurrentUser) { _currentUser = value; OnPropertyChanged('CurrentUser'); } } private IEnumerable<User> _users; public IEnumerable<User> Users { get { if (_users == null) { List<User> _users = Database.GetAllUsers(); } return _users; } } public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string prop) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); } } public User SelectByPartialUserName(string value) { if (value != '') { IEnumerable<User> allUser = GetAllUserByName(value); if (allUser.Count() > 0) { CurrentUser = allUser.First(); } else { CurrentUser = null; } } else { CurrentUser = null; } return CurrentUser; } private IEnumerable<User> GetAllUserByName(string name) { return from user in Users where user.Name.ToLower().StartsWith(name.ToLower()) select user; } }
This is a job for a good view model. Define two properties on your view model:
SelectedUser : UserUserEntry : stringBind the
ListBox‘sSelectedItemto theSelectedUserproperty, and theTextBox‘sTextproperty to theUserEntryproperty. Then, in your view model you can do the work to keep them in sync: – ifSelectedUserchanges, setUserEntryto that user’sName– ifUserEntrychanges, do an intelligent search through all users and setSelectedUserto eithernullif no match was found, or the first matchingUserHere is a complete and working sample. I wish I could easily attach a zip file right about now.
First, ViewModel.cs:
User.cs:
LogonViewModel.cs:
UserLogon.xaml:
UserLogon.xaml.cs: