Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bind to property with only get accessor

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?

like image 927
Oleg Ignatov Avatar asked Aug 19 '13 10:08

Oleg Ignatov


2 Answers

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

like image 144
Nikita B Avatar answered Oct 17 '22 02:10

Nikita B


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));
        }
    }
}
like image 37
Sergey Kalinichenko Avatar answered Oct 17 '22 02:10

Sergey Kalinichenko