How to bind to property with only get accessor
Asked Answered
M

5

6

I have some custom editable listbox on my wpf window. I also have a viewmodel class with Property Changed which looks like that:

public bool HasChanges
{
    get
    {
        return customers.Any(customer => customer.Changed);
    }
}

So, I would like to bind my Save button to this property:

<Button IsEnabled="{Binding HasChanges, Mode=OneWay}"...

My question is how to update Save button if one of the listbox rows is changed?

Madge answered 19/8, 2013 at 10:22 Comment(0)
D
2

The proper way to deal with buttons is to implement ICommand interface. Here is an example from my solution:

public class RelayCommand : ICommand
{
    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    public RelayCommand(Action<object> execute) : this(execute, null)
    {
    }

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;           
    }

    #region ICommand Members

    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    #endregion
}

You can then databind to button like this:

<Button Command="{Binding MyCommand}" .../>

Whats left is to declare an ICommand property on your viewmodel:

public ICommand MyCommand { get; private set; }

//in constructor:
MyCommand = new RelayCommand(_ => SomeActionOnButtonClick(), _ => HasChanges);

The state of the button will then automatically update on most changes. If it doesnt for some reason - you can force the update by calling CommandManager.InvalidateRequerySuggested

Domitiladomonic answered 19/8, 2013 at 10:43 Comment(0)
W
2

In order for WPF to react to changes in properties, the class must implement INotifyPropertyChanged interface. You need to send notifications every time a customer is changed, like this:

class CustomerList : INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;
    private List<Customer> customers = ...
    public bool HasChanges {
        get {
            return customers.Any(customer => customer.Changed);
        }
    }
    // Callers who change customers inside your list must call this method
    public void ChangeCustomer(Customer c) {
        // Do whatever you need to do, ...
        ...
        // then send out the notification to WPF
        OnPropertyChanged("HasChanges");
    }
    protected void OnPropertyChanged(string name) {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }
}
Wodge answered 19/8, 2013 at 10:30 Comment(0)
D
2

The proper way to deal with buttons is to implement ICommand interface. Here is an example from my solution:

public class RelayCommand : ICommand
{
    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    public RelayCommand(Action<object> execute) : this(execute, null)
    {
    }

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;           
    }

    #region ICommand Members

    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    #endregion
}

You can then databind to button like this:

<Button Command="{Binding MyCommand}" .../>

Whats left is to declare an ICommand property on your viewmodel:

public ICommand MyCommand { get; private set; }

//in constructor:
MyCommand = new RelayCommand(_ => SomeActionOnButtonClick(), _ => HasChanges);

The state of the button will then automatically update on most changes. If it doesnt for some reason - you can force the update by calling CommandManager.InvalidateRequerySuggested

Domitiladomonic answered 19/8, 2013 at 10:43 Comment(0)
P
1

Your ViewModel should implement INotifyPropertyChanged and should raise the PropertyChanged Event for HasChanges when ever you change a Customer in customers

Update:

If Customers implement INotifyPropertyChanged and customers it self is an observable collection. You could subscribe, and depending on the action desubscribe, to all the customers in the CollectionChangedEvent of your customers collection.

Pseudo answered 19/8, 2013 at 10:26 Comment(0)
K
0

If your ViewModel implements INotifyPropertyChanged, you just need to call the OnPropertyChanged() method on HasChanges. With Prism, the equivalent method is RaisePropertyChanged.

However, with MVVM, you might want to put that test in the CanExecute method of your command which is bound to your button Command property. This will handle the IsEnabled automatically.

Kazukokb answered 19/8, 2013 at 10:28 Comment(0)
J
0

The button somehow has to receive notifications. In your case you probably implement the INotifyPropertyChanged interface in your viewmodel. When your "listbox row is changed" you should raise the PropertyChanged event for the "HasChanges" property. Changes should be noticed however in you viewmodel and there the event should be raised. As a different solution since you have a viewmodel you could use a command on your button and the CanExecute would have the logic returning true or false, which also has to be marked by you when changes have happened.

Jemmy answered 19/8, 2013 at 10:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.