How to bind to property with only get accessor - c#

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?

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

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));
}
}
}

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.

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.

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.

Related

The RelayCommand for enable/disable a button don't work like expected

The case is that I try to disable a button in the window form when it was clicked and after some time (some seconds) it should be enabled again.
But this didn't work. After a click on the button the command set the enabled to false and after some seconds the command set it back to true (I tested it, the order is right and it set it to true again) but the button is still not enabled on the window form.
For that case I use a RelayCommmand. The RelayCommand is a standard class you find on Internet and will be shown in the end.
To organise the command I wrote a class called Testclass:
class Testclass
{
private bool _testValueCanExecute;
public bool TestValueCanExecute
{
get { return _testValueCanExecute; }
set
{
_testValueCanExecute = value;
OnPropertyChanged();
}
}
public ICommand TestValueCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public Testclass()
{
TestValueCommand = new RelayCommand(TestMethod, param => _testValueCanExecute);
TestValueCanExecute = true;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private async void TestMethod(object obj)
{
TestValueCanExecute = false;
await Task.Delay(3000);
TestValueCanExecute = true;
}
}
In the XAML File I added a button as followed:
<Button x:Name="TestButton" Command="{Binding TestValueCommand}" Content="Test Button" HorizontalAlignment="Left" Margin="149,96,0,0" VerticalAlignment="Top" Width="75"/>
The MainWindow code looks as followed:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new Testclass();
}
}
So the RelayCommand use the TestMethod method set the command enable variable to false, wait 3 seconds and set them back to true. But as I wrote above the button on the window form still not enabled.
It would be nice to understand what happens here and how I can solve this.
Update:
I use the following Code for the RelayCommand:
public class RelayCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
The RelayCommand is a standard class you find on Internet ...
There is no such thing as a "standard class you find on Internet". In fact there are several different implementations of the RelayCommand available "on the Internet".
A good implementation should contain a method for raising the CanExecuteChanged event. MvvmLight's implementation has a RaiseCanExecuteChanged() method that does this. You need to call this one to "refresh" the status of the command:
private async void TestMethod(object obj)
{
RelayCommand cmd = TestValueCommand as RelayCommand;
TestValueCanExecute = false;
cmd.RaiseCanExecuteChanged();
await Task.Delay(3000);
TestValueCanExecute = true;
cmd.RaiseCanExecuteChanged();
}
The event is not raised automatically when you set the TestValueCanExecute property and raise the PropertyChanged event for the view model.
Edit: Your implementation doesn't have any RaiseCanExecuteChanged() method. Add one to your RelayCommand class and call it as per above:
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
I strongly recommend using existing frameworks, instead of inveting the wheel once again.
Take a look at ReactiveUI ReactiveCommand
In your case, it would do all the work by itself:
TestValueCommand = ReactiveCommand.CreateFromTask(async () => await Task.Delay(500));
You bind that command to a button in xaml and the button is disabled until command is done.
You can easily add another condition for disabling the command, and then, binding will disable button

WPF View and ModelView creating

I'am distracted because of view and modelview objects instantiating. As an example:
I've got view V with a listview LV and a button. To the button is bound command that takes as a parametr listview LV. Commands CanExecute method checks whether listView LV has elements. But when i open view V the view model object creates before the view V does. So when CanExecture method checks a listView, it is null and my button becomes unavaliable for ever.
How solve that problem?
EDIT:
Command implementation:
public class DelegateCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
if (execute == null)
{
throw new ArgumentNullException(nameof(execute));
}
this._execute = execute;
this._canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
For that kind of command implementation, you have to call RaiseCanExecuteChanged() manually (in your case after the list view got items).
You can also change the implementation of the CanExecuteChanged event to:
[...]
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
[...]
That implementation enbales the command manager to evaluate CanExecute automatically.

Rig up a Command

I have a command parameter set on a button as follows:
<hw:ActionButton Content="MC" Command="{Binding ActionCommand}" CommandParameter="{x:Static hw:Action.MemoryClear}" Grid.Row="2" Grid.Column="0" />
The command is in the class:
public class ActionCommand : ICommand
{
private readonly CalculatorViewModel _viewModel;
public ActionCommand(CalculatorViewModel viewModel)
{
_viewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return _viewModel != null && parameter is Action;
}
public void Execute(object parameter)
{
_viewModel.ProcessAction((Action)parameter);
}
public event EventHandler CanExecuteChanged;
}
Now I know commands must be in the ViewModel, so how do I couple this command to the ViewModel? Do I merely have an instance of it in the ViewModel called ActionCommand, which doesn't seem to work, or what must I do?
here is a sample for the same
class CalculatorViewModel
{
public CalculatorViewModel()
{
ActionCommand = new ActionCommand(this);
...
}
public ActionCommand ActionCommand { get; private set; }
...
}
above is a simple sample demonstrating a property for command which can be bound to button in the UI
point to note here is that the binding works only with public properties, public variables does not work in the same way.
as a suggestion you may use some DelegateCommand if you are going to create more similar commands.
What is CanExecuteChanged?
from Allowing CommandManager to query your ICommand objects
Because by default WPF has no idea that your custom ICommand objects exist. How would it?
Fortunately there is an easy solution to this problem. In your ICommand implementation, you make the CanExecuteChanged event hook the CommandManager’s RequerySuggested event.
sample
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}

WPF Commands - Relation of View to the Model

I'm trying to understand a little better the MVVM design pattern for WPF developing and this is a basic question in this subject.
Let's say I got the model implemented and it got a method which does the main action. In the View i want to create a button which will activate this action when pressing it. In order to do so I need to associate the Click event with an event handler, which actually is only suppose to call the model method.
The problem is that as I understand, the view doesn't even suppose to know the model. So how can I make the button in the View do the action I want?
That's where the view model comes in. First you should consider using Commands instead of event handlers. With Commands you can bind the "action" to the button instead of hard coding an event to the Click event. Like this:
<Button Command="{Binding Path=ActionCommand}"/>
Now your view model have to have a property that implements ICommand. There are a lot of implementations for this, e g RelayCommand in MVVM Light Toolkit. Through this property you call your models action. This is done by a reference to the model that your view model has. The reference to the model could be set through dependency injection or just supplying it at the creation of the view model.
Simple View model class:
public class ViewModel : INotifyPropertyChanged
{
private Model _model;
private ICommand _actionCommand;
public ViewModel(Model model)
{
_model = model;
_actionCommand = new RelayCommand(ExecuteAction);
}
public ICommand ActionCommand
{
get { return _actionCommand; }
}
private void ExecuteAction()
{
_model.Action();
}
}
This means that your view doesn't really know the type of the ViewModel, just that it has a Command-property called ActionCommand. To set the Views view model you use the View.Datacontext. This can be done in several different ways. Dependency injection could be used here too. Another solution is to use a ViewModelLocator, that uses the Locator pattern to connect the view to its ViewModel.
In your Model you have your function:
class MainWindowModel
{
public void MyAction()
{...}
}
In the constructor of your ViewModel you create an instance of your model like:
class MainWindowViewModel
{
private readonly MainWindowModel mainWindowModel;
public MainWindowViewModel()
{
this.mainWindowModel = new MainWindowModel();
}
Then you have an implementation of ICommand like RelayCommand:
public class RelayCommand : ICommand
{
private readonly Action<object> execute;
private readonly Predicate<object> canExecute;
public RelayCommand(Action<object> exectue, Predicate<object> canExecute = null)
{
if (exectue == null)
throw new ArgumentNullException("exectue");
this.execute = exectue;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
so. now you have a property in your ViewModel:
private ICommand myCommand;
public ICommand MyCommand
{
get { return this.myCommand; }
set
{
this.myCommand = value;
OnPropertyChanged();
}
}
The OnPropertyChanged-Event you get, when you implement the INotifyPropertyChanged-Interface
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
In your constructor of your ViewModel you instanziate the MyCommand like
this.MyCommand = new RelayCommand(MyCommandExecute);
Then you have to create a method in your viewmodel where you call the MyAction-Method of your model:
public void MyCommandExecute(object parameter)
{
this.mainWindowModel.MyAction();
}
In your xaml you have to set the DataContext like:
<Window.DataContext>
<viewModel:MainWindowViewModel/>
</Window.DataContext>
The small viewModel is the Namespace of your ViewModel. This you have to add in the Window-Definition like:
xmlns:viewModel="clr-namespace:TestApplication.ViewModel"
Now you can bind your button-Command to the ICommand-Property of your ViewModel like:
You need to use the ICommand interface, and supply an implementation, for which I suggest that you read this article on MSDN.
In your ViewModel, you would create an instance of ICommand, e.g. ButtonClick, which would look something like (based on RelayCommand):
public class ViewModel
{
public ViewModel()
{
this.ButtonClick = new RelayCommand(_ => this.DoSomething());
}
public ICommand ButtonClick { get; set; }
public void DoSomething()
{
// Something...
}
}
Then in your xaml you would bind to ButtonClick:
<Button Text="Click" Command="{Binding ButtonClick}" />

Disable selected checkbox on button click

Just started working with the MVVM design pattern and I'm stuck.
When my application launches, I have a treeview populated with a list of objects names. I've setup the IsChecked Binding, and it works fine. I'm trying to setup the IsEnabled Binding.
I want the user to select the items in the treeview he wants, then click one of three buttons to perform an action. On click, I want the selected items to remain in the treeview, but be disabled, so the user cannot perform another action on those items.
I'm using a RelayCommand class in the application.
private ICommandOnExecute _execute;
private ICommandOnCanExecute _canExecute;
public RelayCommand(ICommandOnExecute onExecuteMethod,
ICommandOnCanExecute onCanExecuteMethod)
{
_execute = onExecuteMethod;
_canExecute = onCanExecuteMethod;
}
#region ICommand Members
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute.Invoke(parameter);
}
public void Execute(object parameter)
{
_execute.Invoke(parameter);
}
#endregion
My object model class uses this
private bool _isEnabled;
public bool IsEnabled
{
get { return true; }
set { _isEnabled = value};
}
Then within my button method I have
if (interfaceModel.IsChecked)
{
//Does Something
MyObjectName.IsEnabled = false;
}
And here is my xaml
<CheckBox IsChecked="{Binding IsChecked}" IsEnabled="{Binding IsEnabled, Mode=TwoWay}">
<TextBlock Text="{Binding MyObjectName}" Margin="5,2,1,2" HorizontalAlignment="Left" />
</CheckBox>
You need a setup like this:
// Your ViewModel should implement INotifyPropertyChanged
class ViewModel : INotifyPropertyChnaged
{
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
SetPropertyChanged("IsEnabled"); // Add this to your setter.
}
}
// This comes from INotifyPropertyChanged - the UI will listen to this event.
public event PropertyChangedEventHandler PropertyChanged;
private void SetPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged( this, new PropertyChangedEventArgs(property) );
}
}
}
Note that PropertyChanged comes from having your ViewModel implement INotifyPropertyChanged. To notify the UI, you have to raise that event, and tell it what property was changed (usually in the setter - see above).
Alternatively, if you don't like raw strings (I don't, personally), you can use generics and expression trees to do something like this:
public void SetPropertyChanged<T>(Expression<Func<T, Object>> onProperty)
{
if (PropertyChanged != null && onProperty.Body is MemberExpression)
{
String propertyNameAsString = ((MemberExpression)onProperty.Body).Member.Name;
PropertyChanged(this, new PropertyChangedEventArgs(propertyNameAsString));
}
}
Where in your setter you can say:
public bool IsEnabled
{
set
{
_isEnabled = value;
SetPropertyChanged<ViewModel>(x => x.IsEnabled);
}
}
And now it's strongly typed, which is kinda nice.

Categories

Resources