0
votes

I'm trying to fiddle a little with WPF bindings, so I created a simple project. Here's the code:

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public int Age {
        get { return age; }
        set {
            age = value;
            FirePropertyChanged("Age");
        }
    }

    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            FirePropertyChanged("Name");
        }
    }

    private void FirePropertyChanged(string v)
    {
        if(PropertyChanged !=null)
            PropertyChanged(this, new PropertyChangedEventArgs(v));
    }
    private int age;
    private string name;
}

My viewmodel contains ObservableCollection of Person, and single Person to track selected Person.

I've bound listbox's ItemsSource to ObservableCollection, and SelectedItem to single Person, called CurrentPerson. Also, I've bound TextBox to CurrentPerson.Name.

Code works fine, but whenever I change content of TextBox - my listbox also changes. And no matter what combination of "OneWay, TwoWay, OneWayToSource" binding modes on listbox\selecteditem I cannot prevent listbox from updating from CurrentPerson.

How can I prevent this behavior? I'd like to update listbox from CurrentPerson only by using ICommand interface from VM.

3

3 Answers

1
votes

There is only one copy of the Person object which is being used in both ListBox.ItemsSource and TextBox.Text, so naturally updating that object from one location will reflect the change in the other as well.

Two easy solutions would be

  • Change the BindingMode on TextBox.Text to Explicit, so it doesn't update the Person object until you tell it to

  • Use a separate string property for TextBox.Text and copy it over to your SelectedPerson.Name whenever the command executes

Personally I prefer the second option because I'm not a big fan of bindings that don't accurately reflect the data object behind the UI component, and it would allow the user to change the SelectedItem without resetting the TextBox value.


For an example of the second option, your ViewModel might look like this :

public class MyViewModel()
{
    ObservableCollection<Person> People { get; set; }
    Person SelectedPerson { get; set; }
    string NewPersonName { get; set; }
    ICommand UpdatePersonName { get; }
}

where the UpdatePersonName command would execute

SelectedPerson.Name = NewPersonName;

and the CanExecute would only return true if

SelectedPerson != null 
&& !NewPersonName.IsNullOrWhiteSpace() 
&& NewPersonName != SelectedPerson.Name
0
votes

I'm not sure if I've followed the question properly. So, we have a class Person as

public class Person : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public int Age
        {
            get { return age; }
            set
            {
                age = value;
                FirePropertyChanged("Age");
            }
        }

        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                FirePropertyChanged("Name");
            }
        }

        private void FirePropertyChanged(string v)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(v));
        }
        private int age;
        private string name;
    }

And we have a view model as

public class ViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<Person> List { get; set; }
        Person currentPerson;
        public Person CurrentPerson {
            get { return currentPerson; }
            set { currentPerson = value;
            FirePropertyChanged("CurrentPerson");
            }
        }

        private void FirePropertyChanged(string v)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(v));
        }
        public event PropertyChangedEventHandler PropertyChanged;

    }

The xaml is

<ListBox ItemsSource="{Binding List}" SelectedItem="{Binding CurrentPerson}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBox Text="{Binding Name}" Width="100" />
        </DataTemplate>
    </ListBox.ItemTemplate>       
</ListBox>

And I bind the view model to the view via

    ViewModel vm = new ViewModel();
    vm.List = new ObservableCollection<Person>();
    foreach (var i in Enumerable.Range(1,10))
    {
        vm.List.Add(new Person() { Name = "Test" + i.ToString(), Age= i });
    }
    vm.CurrentPerson = null;
    this.DataContext = vm;

Whenever I change the value at textbox, it updates the name properly. I tried to add a handler for list changed, but it doesn't happen to get triggered.

vm.List.CollectionChanged += List_CollectionChanged;
void List_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            MessageBox.Show(e.Action.ToString());
        }

Can you comment if it isn't the same as your problem statement?

0
votes

If you want to control when and what is saved/updated, you obviously need is a ViewModel for editing your Person model.

When selecting a person in your Listbox, you have to pass the person's id (avoid passing the object itself) to the PersonEditViewModel which is bound to the properties that shall be edited, load the persons data into the PersonEditViewModel and then edit. Once you hit the "Save" button, it should commit the change and update the database or whatever you are using for persistence.

Use either events/messages to pass values/events back and forth, or use a navigation approach (like INavigationAware interface in Prism).