Set up button's IsEnabled property with ICommand [duplicate] - c#

This question already has answers here:
WPF MVVM command canexecute enable/disable button
(4 answers)
Disable button in WPF?
(5 answers)
How does one "disable" a button in WPF using the MVVM pattern?
(5 answers)
Command source disabling and enabling
(1 answer)
Closed 5 years ago.
I have the command
public class RelayActionCommand : ICommand
{
/// <summary>
/// The Action Delegate representing a method with input parameter
/// </summary>
public Action<object> ExecuteAction { get; set; }
/// <summary>
/// The Delegate, used to represent the method which defines criteria for the execution
/// </summary>
public Predicate<object> CanExecuteAction { get; set; }
public bool CanExecute(object parameter)
{
if (CanExecuteAction != null)
{
return CanExecuteAction(parameter);
}
return true;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
if (ExecuteAction != null)
{
ExecuteAction(parameter);
}
}
}
To use it,
public RelayActionCommand SearchPersonCommnad { get; set; }
DataAccess objds;
public PersonViewModel()
{
Persons = new ObservableCollection<PersonInfo>();
objds = new DataAccess();
Persons = new ObservableCollection<PersonInfo>(objds.GetPersonData());
var defaultView = CollectionViewSource.GetDefaultView(Persons);
//based upon the data entered in the TextBox
SearchPersonCommnad = new RelayActionCommand()
{
CanExecuteAction = n=> !String.IsNullOrEmpty(Name),
ExecuteAction = n => defaultView.Filter = name => ((PersonInfo)name).FirstName.StartsWith(Name)
|| ((PersonInfo)name).LastName.StartsWith(Name)
|| ((PersonInfo)name).City==Name
};
At the beginning, the button is disabled. But in running time, it changes by different situations. My question is how to set up the button's IsEnabled property with it? Which means, when ExecuteAction I have to set up the property correctly.
UPDATE:
I use ICommand not DelegateCommand.

You can use the CanExecute method, but it is good practice is actually to avoid this, and bind the button's enabled state to a separate boolean property of the view model. Most other solutions will have unexpected effects, or be suboptimal. Why?
CanExecute is a method. This means that it needs to be polled for the button state to change. You can force the control that's using the command to re-poll on a status change, but the code is much cleaner and more straightforward if you just use a property on the view model. This is because as a method, you can't use INotifyPropertyChanged to notify for changes, whereas with a property you can.
The danger in using CanExecute is that the user will manage to click the button after the method would return false, but before the button's enablement has changed.
Edit: Code to do what you want:
public class ViewModel : INotifyPropertyChanged
{
private int someValue;
private bool isEnabled;
public ViewModel()
{
MyCommand = new RelayActionCommand(Click);
}
private void Click(object obj)
{
//Do something.
}
/// <summary>
/// Bind this to the IsEnabled property of the button, and
/// also the background using a convertor or see ButtonBackground.
/// </summary>
public bool IsEnabled => SomeValue < 5;
/// <summary>
/// Option 2 - use this property to bind to the background of the button.
/// </summary>
public Brush ButtonBackground => IsEnabled ? Brushes.SeaShell : Brushes.AntiqueWhite;
public int SomeValue
{
get { return someValue; }
set
{
if (value == someValue) return;
someValue = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsEnabled));
OnPropertyChanged(nameof(ButtonBackground));
}
}
/// <summary>
/// Bind this to the command of the button.
/// </summary>
public RelayActionCommand MyCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged
([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Relay command fixed up a bit to avoid using CanExecute:
public class RelayActionCommand : ICommand
{
public RelayActionCommand(Action<object> executeAction)
{
ExecuteAction = executeAction;
}
/// <summary>
/// The Action Delegate representing a method with input parameter
/// </summary>
public Action<object> ExecuteAction { get; }
/// <summary>
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
ExecuteAction?.Invoke(parameter);
}
//Deliberately empty.
public event EventHandler CanExecuteChanged
{
add { }
remove { }
}
}
EDIT 2: Code to do what you want using a DelegateCommand
Note, this does not use InvalidateRequerySuggested - mainly because it refreshes all buttons when any CanExecute changes, which is a poor solution. As you can see, this is less immediately straightforward than putting the code in the view model directly, but whatever floats your boat I guess.
public sealed class ViewModel : INotifyPropertyChanged
{
private int calls;
public ViewModel()
{
SafeOnceCommand = new RelayCommand(DoItOnce, HasDoneIt);
}
private bool HasDoneIt()
{
return Calls == 0;
}
private void DoItOnce()
{
if (Calls > 0) throw new InvalidOperationException();
Calls++;
}
public int Calls
{
get { return calls; }
set
{
if (value == calls) return;
calls = value;
OnPropertyChanged();
SafeOnceCommand.RaiseCanExecuteChanged();
}
}
public RelayCommand SafeOnceCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public sealed class RelayCommand : ICommand
{
private readonly Action execute;
private readonly Func<bool> canExecute;
private readonly List<EventHandler> invocationList = new List<EventHandler>();
public RelayCommand(Action execute, Func<bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute();
}
public void Execute(object parameter)
{
execute();
}
/// <summary>
/// Method to raise CanExecuteChanged event
/// </summary>
public void RaiseCanExecuteChanged()
{
foreach (var elem in invocationList)
{
elem(null, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged
{
add { invocationList.Add(value); }
remove { invocationList.Remove(value); }
}
}

Related

Is this call really async?

Following:
Converting Action method call to async Action method call
and
The 'await' operator can only be used within an async lambda expression
I've come up with the following code:
CommandHandler.cs:
public class CommandHandler : ICommand
{
private Func<Task> _action;
private Func<bool> _canExecute;
/// <summary>
/// Creates instance of the command handler
/// </summary>
/// <param name="action">Action to be executed by the command</param>
/// <param name="canExecute">A bolean property to containing current permissions to execute the command</param>
public CommandHandler(Func<Task> action, Func<bool> canExecute)
{
_action = action;
_canExecute = canExecute;
}
/// <summary>
/// Wires CanExecuteChanged event
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// Forcess checking if execute is allowed
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
public bool CanExecute(object parameter)
{
return _canExecute.Invoke();
}
public void Execute(object parameter)
{
_action(); // IS THIS REALLY ASYNC? VS DOESN'T COMPLAIN
}
}
AppliedJobsViewModel.cs:
class AppliedJobsViewModel
{
private TexParser texParser;
private ObservableCollection<AppliedJob> appliedJobsCollection;
public AppliedJobsViewModel() {
// TODO:
// -- do nothing here
}
public ObservableCollection<AppliedJob> AppliedJobsCollection
{
get
{
if (appliedJobsCollection == null)
{
appliedJobsCollection = new ObservableCollection<AppliedJob>();
}
return appliedJobsCollection;
}
set
{
if (value != null)
{
appliedJobsCollection = value;
}
}
}
private ICommand _openTexClick;
public ICommand OpenTexClick
{
get
{
return _openTexClick ?? (_openTexClick = new CommandHandler(() => ReadAndParseTexFile(), () => CanExecute));
}
}
public bool CanExecute
{
get
{
// check if executing is allowed, i.e., validate, check if a process is running, etc.
return true;
}
}
public async Task ReadAndParseTexFile()
{
if (texParser == null)
{
texParser = new TexParser();
}
// Read file asynchronously here
await Task.Run(() => ReadFileAndUpdateUI()); // LARGE SYNC CHUNK OF CODE
}
private void ReadFileAndUpdateUI()
{
texParser.ReadTexFile();
string[][] appliedJobsArray = texParser.getCleanTable();
// Update collection here
List<AppliedJob> appliedJobsList = createAppliedJobsListFromTable(appliedJobsArray);
appliedJobsCollection = new ObservableCollection<AppliedJob>(appliedJobsList);
}
private List<AppliedJob> createAppliedJobsListFromTable(string[][] table)
{
List<AppliedJob> jobsList = new List<AppliedJob>();
for (int i = 0; i < table.Length; i++)
{
jobsList.Add(new AppliedJob(table[i]));
}
return jobsList;
}
public ObservableCollection<AppliedJob> AppliedJobs {
get;
set;
}
}
Is the code "entirely"(from the start of the Func action) async? If yes, is it "fully" MVVM? If not, why?

Pass class variable using CommandParameter in MVVM [duplicate]

This question already has an answer here:
Binding to properties in both the ViewModel and CodeBehind
(1 answer)
Closed 2 years ago.
I want to pass the ViewModel parameter(class variable) when I click the buttons in the View. When specifying a string, everything comes true, but I need to pass a non-string value from the View class(function AddNewField).
Main.xaml:
...
<Button
x:Name="ButtonAddingField"
CommandParameter="{Binding Path=Myprop}"
Command="{Binding Path=Myprop, RelativeSource={RelativeSource AncestorType=UserControl}}" />
...
Main.cs:
...
private Color myprop;
public Color Myprop
{
get => myprop;
}
...
this.DataContext = new FieldCollectionViewModel(fields); // fields - List<*my Model*>
...
FieldCollectionViewModel.cs:
...
private DelegateCommand<object> addFieldCommand;
public ICommand AddFieldCommand {
get {
if (addFieldCommand == null) addFieldCommand = new DelegateCommand<object>(AddNewField);
return addFieldCommand;
}
}
private void AddNewField(object parameter)
{
// !!! parameter = ALWAYS NULL
}
...
and my DelegateCommand.cs:
...
public class DelegateCommand<T> : ICommand
{
#region Constructors
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action<T> executeMethod)
: this(executeMethod, null, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
: this(executeMethod, canExecuteMethod, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
{
if (executeMethod == null) throw new ArgumentNullException("executeMethod");
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
_isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
}
#endregion Constructors
#region Public Methods
/// <summary>
/// Method to determine if the command can be executed
/// </summary>
public bool CanExecute(T parameter)
{
if (_canExecuteMethod != null) return _canExecuteMethod(parameter);
return true;
}
/// <summary>
/// Execution of the command
/// </summary>
public void Execute(T parameter)
{
if (_executeMethod != null) _executeMethod(parameter);
}
/// <summary>
/// Raises the CanExecuteChaged event
/// </summary>
public void RaiseCanExecuteChanged()
{
OnCanExecuteChanged();
}
/// <summary>
/// Protected virtual method to raise CanExecuteChanged event
/// </summary>
protected virtual void OnCanExecuteChanged()
{
CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
}
/// <summary>
/// Property to enable or disable CommandManager's automatic requery on this command
/// </summary>
public bool IsAutomaticRequeryDisabled {
get => _isAutomaticRequeryDisabled;
set {
if (_isAutomaticRequeryDisabled != value)
{
if (value)
CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
else
CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
_isAutomaticRequeryDisabled = value;
}
}
}
#endregion Public Methods
#region ICommand Members
/// <summary>
/// ICommand.CanExecuteChanged implementation
/// </summary>
public event EventHandler CanExecuteChanged {
add {
if (!_isAutomaticRequeryDisabled) CommandManager.RequerySuggested += value;
CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2);
}
remove {
if (!_isAutomaticRequeryDisabled) CommandManager.RequerySuggested -= value;
CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value);
}
}
bool ICommand.CanExecute(object parameter)
{
// if T is of value type and the parameter is not
// set yet, then return false if CanExecute delegate
// exists, else return true
if (parameter == null &&
typeof(T).IsValueType)
return _canExecuteMethod == null;
return CanExecute((T)parameter);
}
void ICommand.Execute(object parameter)
{
Execute((T)parameter);
}
#endregion ICommand Members
#region Data
private readonly Action<T> _executeMethod;
private readonly Func<T, bool> _canExecuteMethod;
private bool _isAutomaticRequeryDisabled;
private List<WeakReference> _canExecuteChangedHandlers;
#endregion Data
}
...
How can I solve my problem?
UPDATE.
I changed my code, now it is working
Thanks to #mm8
Myprop must be a public property of the DataContext of the Button for your current binding to work.
If it's a property of the parent UserControl, you could bind to it using a RelativeSource:
CommandParameter="{Binding Path=Myprop, RelativeSource={RelativeSource AncestorType=UserControl}}"
Note that you can only bind to public properties. You cannot bind to fields.

Button command class to take a parameter

Using c# + wpf + mvvm I am trying to make a more or less generic "buttoncommand" class following level 4 at http://www.codeproject.com/Articles/819294/WPF-MVVM-step-by-step-Basics-to-Advance-Level
I have a datagrid thats bound to observablecollection<mySpecialClass> and a button bound a command that adds row and takes no parameters that works fine.
However I would like to have buttons to insert and remove rows in the grid, that would take the index of the row that is currently active/selected.
I have tried to change the buttoncommand to the following (changing Action what to Action<object> what):
public class ButtonCommandi : ICommand
{
private Action<object> _whattoExecute;
private Func<bool> _whentoExecute;
public ButtonCommandi(Action<object> what, Func<bool> when)
{
_whattoExecute = what;
_whentoExecute = when;
}
public bool CanExecute(object parameter)
{
return _whentoExecute();
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_whattoExecute(parameter);
}
}
}
However, in my viewmodel, as I try to create instances of the class in the constructor:
public class MyViewModelClass
{
private ButtonCommand _objAppendTerrainPointCommand;
private ButtonCommandi _objInsertTerrainPointCommand;
private MyModelClass _mymodel;
public MyVeiwModelClass()
{
...
_objAppendRowCommand = new ButtonCommand(_mymodel.Append, _mymodel.IsPossibleToAppend);
_objInsertRowCommand= new ButtonCommandi(
delegate(object i) { _mymodel.InsertRow(i); }, _mymodel.IsPossibleToInsert);
...
}
it says that "the best overloaded method match for 'MyModelClass.InsertRow(int)' has some invalid arguments". I have tried some different versions of the new ButtonCommandi(..) but cannot seem to find the way.
in MyModelClass the function looks like this:
internal void InsertRow(int idx)
{
_myObsColSpecial.Insert(idx);
}
Does anyone have a good hint how to have this work? (I am new to the wpf, mvvm and c# so bear with me. As far as I understand there seems to be a lot of ways to accomplish something like this but since I started down this way, it would be nice not to have to rewrite a "ton" of code :) )
For RelayCommand that passes an object try this....
class RelayCommand<T> : ICommand
{
#region Fields
readonly Action<T> _execute = null;
readonly Predicate<T> _canExecute = null;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of <see cref="DelegateCommand{T}"/>.
/// </summary>
/// <param name="execute">Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.</param>
/// <remarks><seealso cref="CanExecute"/> will always return true.</remarks>
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
///<summary>
///Defines the method that determines whether the command can execute in its current state.
///</summary>
///<param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
///<returns>
///true if this command can be executed; otherwise, false.
///</returns>
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
///<summary>
///Occurs when changes occur that affect whether or not the command should execute.
///</summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
///<summary>
///Defines the method to be called when the command is invoked.
///</summary>
///<param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to <see langword="null" />.</param>
public void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion
}
In your View Model...
class MainWindowVM
{
public RelayCommand<string> WindowCommand { get; set; }
public MainWindowVM()
{
WindowCommand = new RelayCommand<string>(OnWindowCommand);
}
private void OnWindowCommand(string obj)
{
// obj is the string passed from the Button CommandParameter
}
}
In your XAML
<Window.DataContext>
<local:MainWindowVM/>
</Window.DataContext>
<Grid>
<Button Command="{Binding WindowCommand}" CommandParameter="Test" />
</Grid>

WPF MVVM command canexecute enable/disable button

I want to enable RibbonButton when textbox property text isn't null. Disable RibbonButton when textbox property text is null. I want to use CanExecute method in ICommand for it. How can I do it?
View:
<Custom:RibbonButton
LargeImageSource="..\Shared\img\save_diskete.png"
Label="Save"
Command="{Binding ButtonCommand}">
</Custom:RibbonButton>
ViewModel
class KomentarViewModel:BaseViewModel
{
#region Data
private ICommand m_ButtonCommand;
public ICommand ButtonCommand
{
get
{
return m_ButtonCommand;
}
set
{
m_ButtonCommand = value;
}
}
private string textKomentar;
public string TextKomentar
{
get
{
return this.textKomentar;
}
set
{
// Implement with property changed handling for INotifyPropertyChanged
if (!string.Equals(this.textKomentar, value))
{
textKomentar = value;
OnPropertyChanged("TextKomentar");
}
}
}
private ObservableCollection<Komentar> allCommentsInc;
public ObservableCollection<Komentar> AllCommentsInc
{
get
{
return allCommentsInc;
}
set
{
allCommentsInc = value;
OnPropertyChanged("AllCommentsInc");
}
}
public int idIncident { get; private set; }
public Incident incident { get; private set; }
#endregion
#region Constructor
public KomentarViewModel(int id)
{
CC_RK2Entities context = new CC_RK2Entities();
this.idIncident = id;
AllCommentsInc = new ObservableCollection<Komentar>(context.Komentar.Where(a => a.Incident_id == idIncident));
incident = context.Incident.Where(a => a.id == idIncident).First();
//ButtonCommand = new RelayCommand(new Action<object>(ShowMessage));
}
#endregion
#region Methods
//ukaz napsany text
public void ShowMessage(object obj)
{
//MessageBox.Show(obj.ToString());
MessageBox.Show(this.TextKomentar);
}
}
RelayCommand
namespace Admin.Shared.Commands
{
class RelayCommand : ICommand
{
private Action<object> _action;
public RelayCommand(Action<object> action)
{
_action = action;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action(parameter);
}
#endregion
}
}
You need to modify your RelayCommand class like this
class RelayCommand : ICommand
{
private Action<object> _action;
private Func<bool> _func;
public RelayCommand(Action<object> action,Func<bool> func)
{
_action = action;
_func = func;
}
public void RaiseCanExecuteChanged()
{
if(CanExecuteChanged!=null)
CanExecuteChanged(this,new EventArgs());
}
#region ICommand Members
public bool CanExecute(object parameter)
{
if (_func != null)
return _func();
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action(parameter);
}
#endregion
}
Initialize ButtonCommand as
ButtonCommand = new RelayCommand((s) => ShowMessage(s),()=>!string.IsNullOrEmpty(TextKomentar));
RaiseCanExcuteChanged from the setter of Text property
public string TextKomentar
{
get
{
return this.textKomentar;
}
set
{
// Implement with property changed handling for INotifyPropertyChanged
if (!string.Equals(this.textKomentar, value))
{
textKomentar = value;
OnPropertyChanged("TextKomentar");
}
ButtonCommand.RaiseCanExecuteChanged();
}
}
implement this for canexecute:
public bool CanExecute(object parameter)
{if(thistext available)
return true;
else
return false;
}
Since, CanExecuteChanged is raised when the CanExecute method of an ICommand gets changed. it gets invoked when some command that could change canexecute.
and can execute changed should be changed to this:
public event EventHandler CanExecuteChanged {
add {
CommandManager.RequerySuggested += value;
}
remove {
CommandManager.RequerySuggested -= value;
}
}
EDIT
in your view model constructor:
m_ButtonCommand= new RelayCommand(Submit, CanSubmit);
now method for this submit:
private bool CanSubmit(object obj)
{
if(thistext available)
return true;
else
return false;
}
public void Submit(object _)
{//... code}
public event EventHandler CanExecuteChanged {
add {
CommandManager.RequerySuggested += value;
}
remove {
CommandManager.RequerySuggested -= value;
}
}
do it like this.
In straightforward words, you need the following:
Let's first create our own delegate command:
public class DelegateCommand : DelegateCommandBase
{
private Action _executeMethod;
private Func<bool> _canExecute;
public DelegateCommand(Action executeMethod)
: this(executeMethod, () => true) {}
public DelegateCommand(Action executeMethod, Func<bool> _canExecute): base()
{
if (executeMethod == null || _canExecute == null) {
throw new ArgumentNullException(nameof(executeMethod),
Resources.DelegateCommandDelegatesCannotBeNull);
}
_executeMethod = executeMethod;
_canExecute = _canExecute;
}
public void Execute() => _executeMethod();
public bool CanExecute() => _canExecute();
protected override void Execute(object parameter) => Execute();
protected override bool CanExecute(object parameter) => CanExecute();
public DelegateCommand ObservesProperty<T>(Expression<Func<T>> propertyExpression)
{
ObservesPropertyInternal(propertyExpression);
return this;
}
public DelegateCommand ObservesCanExecute(Expression<Func<bool>> canExecuteExpression)
{
_canExecute = canExecuteExpression.Compile();
ObservesPropertyInternal(canExecuteExpression);
return this;
}
}
Here, DelegateCommandBase is actually from Prism.Commands namespace.
If you don't use Prism as an MVVM framework for WPF, you can create your own copy of DelegateCommandBase (look for the solution here).
In your View Model, create a member with type DelegateCommand and initialize it in the constructor:
public class MyViewModel
{
private DelegateCommand _okCommand;
public DelegateCommand OkCommand
{
get => _okCommand;
set => SetProperty(ref _okCommand, value);
}
public MyViewModel()
{
OkCommand = new PrismCommands.DelegateCommand(OkCommandHandler,
OkCanExecuteCommandHandler);
}
private void OkCommandHandler()
{
// ...
}
// This is important part: need to return true/false based
// on the need to enable or disable item
private bool OkCanExecuteCommandHandler() =>
return some_condition_to_enable_disable_item;
}
Note: make sure to raise execution changed event, every time something changes that can affect some_condition_to_enable_disable_item condition behavior.
For example, in the case of Prism, you can call RaiseCanExecuteChanged method once a change happens related to the condition (in our case OkCommand.RaiseCanExecuteChanged();).
Small hint: for Telerik WPF Controls, you need to call InvalidateCanExecute() instead of RaiseCanExecuteChanged().
Finally, our XAML will look like this:
<Button x:Name="btnOk"
Content="Ok"
Command="{Binding OkCommand}"/>
Last time I used Microsoft.Practices.Prism.Commands namesapce from Microsoft.Practices.Prism.dll. Class DelegateCommand has own RaiseCanExecuteChanged() method. So the benifit is you don't have to write yout own implementation of ICommand.
XAML:
<StackPanel>
<CheckBox IsChecked="{Binding IsCanDoExportChecked}" />
<Button Command="{Binding ExportCommand}" Content="Export" />
</StackPanel>
ViewModel:
public class ViewModel
{
public DelegateCommand ExportCommand { get; }
public ViewModel()
{
ExportCommand = new DelegateCommand(Export, CanDoExptor);
}
private void Export()
{
//logic
}
private bool _isCanDoExportChecked;
public bool IsCanDoExportChecked
{
get { return _isCanDoExportChecked; }
set
{
if (_isCanDoExportChecked == value) return;
_isCanDoExportChecked = value;
ExportCommand.RaiseCanExecuteChanged();
}
}
private bool CanDoExptor()
{
return IsCanDoExportChecked;
}
}

How to run an Action on View before bound ViewModel Command is executed

As title clearly defines, I want to run an Action in my View just before a command is executed in the ViewModel.
Scenario
Assume that we have a NavigationDrawer with two or more item types (e.g. NavigationItem and ExpandableItem). Each item was bound to a command on its corresponding ViewModel.
Now I want to:
Close the drawer before executing NavigationItem command.
Show an animation for the ExpandableItem before executing the command and do not close drawer.
I have tried the following approach:
Core Project
public class MvxInteractiveCommand : MvxCommandBase, IMvxCommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public MvxInteractiveCommand(Action execute)
: this(execute, null)
{
}
public MvxInteractiveCommand(Action execute, Func<bool> canExecute)
{
_canExecute = canExecute;
_execute = execute;
Delay = 0;
}
/// <summary>
/// Delay in milliseconds to execute the action. Default is zero means immediate execution
/// </summary>
public int Delay { get; set; }
public object InteractData { get; set; }
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute();
}
public bool CanExecute()
{
return CanExecute(null);
}
public void Execute(object parameter)
{
if (CanExecute(parameter))
{
Mvx.Resolve<IMvxMessenger>().Publish(new CommandExecutingMessage(this) { Data = InteractData });
if (Delay == 0)
_execute();
else
Task.Delay(200).ContinueWith(obj => _execute());
}
}
public void Execute()
{
Execute(null);
}
}
public class CommandExecutingMessage : MvxMessage
{
public CommandExecutingMessage(object sender)
: base(sender)
{
}
public object Data { get; set; }
}
NavigationSimpleItem.cs:
public class NavigationSimpleItem : MvxNavigatingObject
{
private readonly IMvxCommand _clickCommand;
public NavigationSimpleItem(string caption, int id, Action doCommand, Func<bool> canDoCommand = null)
{
...
if (doCommand == null)
throw new ArgumentNullException("doCommand");
_clickCommand = new MvxInteractiveCommand(doCommand, canDoCommand);
}
public override IMvxCommand ClickCommand
{
get { return _clickCommand; }
}
...
}
Droid Project
MainView.cs:
public class MainView : MvxActivity
{
private MvxSubscriptionToken _token;
public MainView()
{
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.MainView);
}
protected override void OnViewModelSet()
{
base.OnViewModelSet();
_token = Mvx.Resolve<IMvxMessenger>().SubscribeOnMainThread<CommandExecutingMessage>(e =>
{
DrawerWrapper.CloseDrawer();
});
}
}
Notes: As you can see, I use Messenger Plugin to interact between the View and the ViewModel. It works fine with my first need, which is to run a simple Action on every item click.
However, my view needs to know which item is clicked and what its state is.
I also want more complex scenarios later such as waiting for an UI action to complete before command execution (something like this answer).

Categories