So I have this simple application that has a button, that's it.
And the button has a command property bound to a command.
This is the command it is bound to.
public class StartAsyncCommand : ICommand
{
private Task _execute;
public StartAsyncCommand(Task Execute)
{
_execute = Execute;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_execute.Start();
}
public event EventHandler CanExecuteChanged;
}
And in the ViewModel this is what I got.
public StartAsyncCommand StartCommand { get; }
public MoveMouseModel()
{
StartCommand = new StartAsyncCommand(MoveMove());
}
public async Task MoveMove()
{
MessageBox.Show("First Message..");
await Task.Delay(2000);
MessageBox.Show("Second Message..");
}
XAML
<Grid>
<Button Width="100"
Height="25"
Content="Async?"
Command="{Binding MoveMouseModel.StartCommand}"/>
</Grid>
When I start the application, that those messageboxes show even though I didnt click the button.
What can I do to prevent this?
You are passing the result of MoveMove to your StartAsyncCommand class, not the actual method
public MoveMouseModel()
{
StartCommand = new StartAsyncCommand(MoveMove()); //<-- MoveMove() is executed
}
Should work when you actually pass the method instead and call it inside of StartAsyncCommand instead
eg. public StartAsyncCommand(Func<Task> fnc) {...} as constructor and then just executing the func when the actual command is used
wondering why your visual studio is not displaying you some "this method is not awaited" info message though ..
Related
i have one view model name as "SettingsViewModel" and in that view model I am writing the function for button click ( bUpdate() )
namespace
{
class SettingsViewModel : Notifyable
{
public Settings settings
{
get => _settings;
set
{
_settings = value;
OnPropertyChanged();
}
}
private Settings _settings = Settings.Default;
private IWindowManager _windowManager;
public SettingsViewModel(IWindowManager windowManager)
{
_windowManager = windowManager;
}
protected override void OnClose()
{
base.OnClose();
settings.Save();
}
CopyFilesRecursively(serverDirectorty, localDirectory){
// DO SOMETHING
}
public void bUpdate()
{
CopyFilesRecursively(serverDirectorty, localDirectory);
}
}
}
I want to disable button click when copying of the files is start and when copying is done I want to re enabled the button click.
Below is my XML (SettingsView.xml) for the button
<Button Content="{x:Static p:Resources.update}" HorizontalAlignment="Right" Command= "{s:Action bUpdate }" />
How can i do that with the help of Binding?
Since you need the MVVM approach, the ideal way would be to set the DataContext of the View/UserControl to the instance of the ViewModel (tell me if you want how-to in comments further, I'll explain) and then bind to a property which is an instance of an ICommand implementation like this:-
View/UserControl:
<Button Content="{x:Static p:Resources.update}"
HorizontalAlignment="Right"
Command="{Binding Update}" />
ViewModel:
public ICommand Update => new RelayCommand(HandleUpdate, CanUpdate);
private bool _isRunning = false;
private void HandleUpdate()
{
_isRunning = true;
CommandManager.InvalidateRequerySuggested();
Task.Run(() =>
{
// Update Button click logic goes here
CopyFilesRecursively(serverDirectorty, localDirectory);
Application.Current.Dispatcher.Invoke(() =>
{
_isRunning = false;
CommandManager.InvalidateRequerySuggested();
});
});
}
private bool CanUpdate()
{
return !_isRunning;
}
The _isRunning flag just maintains the current running state information and the InvalidateRequerySuggested invocation on the CommandManager forces the View to force the CanExecuteChanged event on the ICommand.
The Task.Run ensures that your long-running process doesn't block the UI thread and the current dispatcher invocation is a guard against non-UI thread manipulating Xaml elements that could potentially cause an issue.
Here is a parameterless implementation of the ICommand interface:
public class RelayCommand : ICommand
{
readonly Func<Boolean> _canexecute;
readonly Action _execute;
public RelayCommand(Action execute)
: this(execute, null)
{
}
public RelayCommand(Action execute, Func<Boolean> canexecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canexecute = canexecute;
}
public event EventHandler CanExecuteChanged
{
add
{
if (_canexecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canexecute != null)
CommandManager.RequerySuggested -= value;
}
}
public Boolean CanExecute(Object parameter)
{
return _canexecute == null ? true : _canexecute();
}
public void Execute(Object parameter)
{
_execute();
}
}
You could refactor the boolean flag and optimize your way but this is how we usually de-couple the viewmodel logic from the view code!
P.S.:
There are further ways to pass command parameters via the command binding as well, you could look into that when you need so or I could clarify in comments.
Also, there's no exception handling in the task run currently, do consider aggregate exception catching furthermore.
Well, I'm wondering a bit about your code example. Guess you will run into a "UI is blocked" issue soon. Anyhow, you can get around step by step.
Of course you can do that by binding. Note you can bind nearly any item property to a property in your VM. So for simplicity, you may do it like this
<Button IsEnabled={Binding MyButtonIsEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Content="{x:Static p:Resources.update}" HorizontalAlignment="Right" Command= "{s:Action bUpdate }" />
For the VM side, I assume you are using some MVVM framework Nuget package, and/or have Fody enabled taking care about the plumbing of the INotifyPropertyChanged event. If not, ammend the MyButtonIsEnabled property with a backing field like your other VM properties:
public bool MyButtonIsEnabled {get; set;}
public void bUpdate()
{
MyButtonIsEnabled = false;
CopyFilesRecursively(serverDirectorty, localDirectory);
MyButtonIsEnabled = true;
}
So far, so nice - but won't work as expected, because the bUpdate function is a synchronous function. It will not return until work is done. Hence, your complete UI will not be responsive and the button won't get a time slice to disable and re-enable.
Rather you should work with an ICommands resp. IYourMVVMFrameworkCommand (I'm favoring Catel) like:
(view)
<Button Command="{Binding CopyMyFilesCommand}" Content="...whatever..."/>
(VM)
public ICatelCommand CopyMyFilesCommand { get; private set; }
MyVieModel() // constructor
{
...
CopyMyFilesCommand = new TaskCommand(OnCopyMyFilesCommand);
...
}
private async Task OnCopyMyFilesCommand()
{
await Task.Run(bUpdate).ConfigureAwait(false);
}
Using Catel, the TaskCommand constructor takes a second delegate parameter deciding if the ICommand can be executed. Wiring it as
CopyMyFilesCommand = new TaskCommand(OnCopyMyFilesCommand, () => MyButtonIsEnabled);
Will disable the command which in turn disables the button without the need of binding the IsEnabled property.
I have a RoutedUI Command that is bound as a Command property for a button and an OnClick event. Whenever I evaluate some condition from the OnClick I want to prevent the command from executing. I referred to this post but dosen't help much Prevent command execution. One quick fix is to get the sender of button on click and set its command to null. But I want to know if there is an other way. Please help.
<Button DockPanel.Dock="Right"
Name="StartRunButtonZ"
VerticalAlignment="Top"
Style="{StaticResource GreenGreyButtonStyle}"
Content="{StaticResource StartARun}"
Width="{StaticResource NormalEmbeddedButtonWidth}"
Click="StartRunButton_Click"
Command="{Binding StartRunCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl},AncestorLevel=2}}"
/>
Here is the code behind
private void StartRunButton_Click(object sender, RoutedEventArgs e)
{
if(SomeCondition){
//Prevent the Command from executing.
}
}
Assuming your StartRun() method follows the async / await pattern, then replace your ICommand implementation with the following. It will set CanExecute to false while the task is running, which will automatically disable the button. You don't need to mix commands and click event handlers.
public class perRelayCommandAsync : ViewModelBase, ICommand
{
private readonly Func<Task> _execute;
private readonly Func<bool> _canExecute;
public perRelayCommandAsync(Func<Task> execute) : this(execute, () => true) { }
public perRelayCommandAsync(Func<Task> execute, Func<bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
private bool _isExecuting;
public bool IsExecuting
{
get => _isExecuting;
set
{
if(Set(nameof(IsExecuting), ref _isExecuting, value))
RaiseCanExecuteChanged();
}
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => !IsExecuting
&& (_canExecute == null || _canExecute());
public async void Execute(object parameter)
{
if (!CanExecute(parameter))
return;
IsExecuting = true;
try
{
await _execute().ConfigureAwait(true);
}
finally
{
IsExecuting = false;
}
}
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
More details at my blog post.
This simple button extension could be useful for someone.
On button click at first it is invoked ConfirmationClick event. If you set in its callback e.IsConfirmed to true, then classic click event is invoked and command is executed.
Because of command binding you have button.IsEnabled property tied up to command.CanExecute.
public class ConfirmationButton : Button
{
public event EventHandler<ConfirmationEventArgs> ConfirmationClick;
protected override void OnClick()
{
ConfirmationEventArgs e = new ConfirmationEventArgs();
ConfirmationClick?.Invoke(this, e);
if (e.IsConfirmed == true)
{
base.OnClick();
}
}
}
public class ConfirmationEventArgs : EventArgs
{
public bool? IsConfirmed = false;
}
<model:ConfirmationButton x:Name="DeleteButton"
ConfirmationClick="DeleteButton_ConfirmationClick"
Command="{Binding DeleteCommand}"/>
private void DeleteButton_ConfirmationClick(object sender, ConfirmationEventArgs e)
{
var dialogWindow = new MyDialogWindow("Title..","Message.."); //example
e.IsConfirmed = dialogWindow.ShowDialog();
}
I am writing a WPF application to learn the MVVM Design Pattern. I am fairly new to C# and WPF.
I am trying to pass some context when switching ViewModels, that then gets used in an ICommand implementation to call a method. But the ICommand won't update after receiving the context.
Basically I create an instance of an ICommand, which a button binds to and then (when passing the context) I create another instance that replaces it.
My question then: is there a way to rebind a command binding or is the state it had at the time of intialization unmodifiable.
What I'm trying to accomplish in code:
Command.cs
public class Command : ICommand
{
public Command(Action action) => this.action = action;
public event EventHandler CanExecuteChanged;
public virtual bool CanExecute(object parameter) => true;
public virtual void Execute(object parameter) => action();
Action action;
}
ObservableObject.cs
public abstract class ObservableObject : INotifyPropertyChanged
{
protected virtual void OnPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler PropertyChanged;
}
FooModel.cs
public class FooModel
{
public int Number => 10;
}
BarModel.cs
public class BarModel
{
public int Number { get; set; }
}
BarViewModel.cs
public class BarViewModel : ObservableObject
{
public BarViewModel()
{
Bar = new BarModel();
BtnCommand = new Command(Reset);
}
public void Receive(object state)
{
if (state is FooModel foo)
{
Counter = Bar.Number = foo.Number;
// this won't reset the number to 10
BtnCommand = new Command(Reset);
// neither will this, why?
Reset();
}
}
public void Reset() => Counter = Bar.Number;
int counter;
public int Counter
{
get => counter;
set
{
counter = value;
OnNotifyPropertyChanged(nameof(Counter));
}
}
Command btnCommand;
public Command BtnCommand
{
get => btnCommand;
set
{
btnCommand = value;
OnNotifyPropertyChanged(nameof(BtnCommand));
}
}
BarModel Bar { get; private set; }
}
BarView.xaml
<UserControl
<! ... namespaces and such -->
>
<UserControl.DataContext>
<local:BarViewModel />
</UserControl.DataContext>
<Grid>
<Button Content="Click" Command="{Binding BtnCommand}"/>
</Grid>
</UserControl>
The Receive Method is invoked after creating the BarViewModel and it passes an instance of FooModel. When I set a breakpoint inside the if (state ...) block it says that the Bar.Number field is 10, but when it leaves the scope it's back to 0. I get a feeling that this is how it's supposed to work, but how can I accomplish the update of the Command context?
I tried to create a MCVE of my project, here's the link to dropbox. It's a VS 2017 Project using .NET 4.5.2
Screenshot of code in MCVE:
EDIT: changed fileupload URL
EDIT2: added screenshot
EDIT3: updated code
EDIT4: changed fileupload to dropbox
You are creating a new instance of the BarViewModel in your Bar view. Remove this XAML markup:
<UserControl.DataContext>
<local:BarViewModel />
</UserControl.DataContext>
Then the commands that you create in your Receive method should be invoked as expected.
I am using MVVM in my WPF application and I have a problem with data binding. I am considering binding user actions to data operations (in my case adding record to database). If I use heavy coupling between CommandClass and ViewModelClass everything works fine. My CommandClass in this case looks like this:
public class ButtonCommand : ICommand
{
private readonly UserViewModel _userViewModel;
public ButtonCommand(UserViewModel viewModel)
{
_userViewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_userViewModel.AddUser();
}
public event EventHandler CanExecuteChanged;
}
My heavy coupling in ViewModelClass looks like this:
private readonly ButtonCommand _buttonCommand;
public UserViewModel()
{
_buttonCommand = new ButtonCommand(this);
}
public ICommand btnClick
{
get { return _buttonCommand; }
}
My XAML coupling on button click (take a look on a Command section):
<Page.Resources>
<viewModel:UserViewModel x:Key="UserObj" TxtFirstName="" TxtLastName="" TxtEmail="" TxtPassword=""/>
</Page.Resources>
....
<Button Content="Submit" HorizontalAlignment="Left" Margin="42,231,0,0" VerticalAlignment="Top" Width="75" Command="{Binding btnClick, Mode=OneWay, Source={StaticResource UserObj}}"/>
And I have such an output (take a look at Submit button): Window.
After I make changes to my CommandClass and ViewModelClass (to make them more general and reusable), but leave my XAML coupling the same the Submit button becomes unavailable after runnig my application. After changes CommandClass looks like this:
public class ButtonCommand : ICommand
{
private readonly Action _executionMethod;
private readonly Func<bool> _executeOrNot;
public ButtonCommand(Action executionMethod, Func<bool> executeOrNot)
{
_executionMethod = executionMethod;
_executeOrNot = executeOrNot;
}
public bool CanExecute(object parameter)
{
return _executeOrNot();
}
public void Execute(object parameter)
{
_executionMethod();
}
public event EventHandler CanExecuteChanged;
}
My ViewModelClass after changes:
private readonly ButtonCommand _buttonCommand;
public UserViewModel()
{
_buttonCommand = new ButtonCommand(AddUser, IsValidInputForRegistration);
}
public ICommand btnClick
{
get { return _buttonCommand; }
}
XAML I leave the same. The output I have is next (take a look at Submit button): WindowWithChanges.
Can anyone provide me with some information, why button became unavailable and where do I mess up?
First, try IsValidInputForRegistration to always return true. That will prove that your implementation of IButton (i.e. your ButtonCommand class) works fine.
If that works, what's happening to your program is the IsValidInputForRegistration passes the state for your _buttonCommand during initialization and it will stay on that state since it doesn't query if the IsValidInputForRegistration have changed states.
To achieve querying of states, you can implement the EventHandler CanExecuteChanged like so:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
You can look into msdn for what CommandManager.RequerySuggested does. But I think the description says it all. :)
Occurs when the CommandManager detects conditions that might change
the ability of a command to execute.
I have the following question.
I have the following simple xaml:
<TextBox Name="NameBox" Text ="{Binding Name}" />
<Button Content="Save" Command="{Binding SaveCommand}" CommandParameter="{Binding Entity}" />
And i bind DataContext of this Window to following View Model
public class MyViewModel
{
public SimpleModel Entity { get; set; }
private ICommand _saveCommand;
public ICommand SaveCommand { get { return _saveCommand ?? (_saveCommand = new MyCommand(OnSaveItem, parameter => CanSaveItem())); } }
public void OnSaveItem(object parameter)
{
// some code
}
public virtual bool CanSaveItem()
{
return !String.IsNullOrWhiteSpace(Entity.Name);
}
}
SimpleModel is
public class SimpleModel
{
public int Id { get; set; }
public string Name { get; set; }
}
This code works mostly correct but i can not
make method CanSaveItem to work properly. I don't know how to tell to SaveCommand that properties of ViewModel was changed. I know that i have to use CanExecuteChanged or CommandManager.InvalidateRequerySuggested and i tried to use their some times but i don't know how to do it properly and it didn't take an effect. Could you help me with this problem?
UPD.
public class MyCommand : ICommand
{
public MyCommand(Action<object> execute, Predicate<object> canExecute)
{
_canExecute = canExecute;
_execute = execute;
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
}
It appears you are on an early learning curve, and this can be confusing... and sometimes still is to me too.
Anyhow, I've made some slight changes to what you had and explain what I did to them.
public class MyViewModel
{
public SimpleModel Entity { get; set; }
private MyCommand _saveCommand;
public MyCommand SaveCommand { get { return _saveCommand ?? (_saveCommand = new MyCommand(OnSaveItem, parameter => CanSaveItem())); } }
public MyViewModel()
{
//------ You need to create an instance of your entity to bind to
Entity = new SimpleModel();
//-- I added an event handler as your "Entity" object doesn't know
//-- about the button on the view model. So when it has something
//-- change, have it call anybody listening to its exposed event.
Entity.SomethingChanged += MyMVVM_SomethingChanged;
}
void MyMVVM_SomethingChanged(object sender, EventArgs e)
{
// Tell our mvvm command object to re-check its CanExecute
SaveCommand.RaiseCanExecuteChanged();
}
public void OnSaveItem(object parameter)
{
// some code
}
public virtual bool CanSaveItem()
{
//-- Checking directly to your Entity object
return !String.IsNullOrWhiteSpace(Entity.Name);
}
}
public class SimpleModel
{
//-- Simple constructor to default some values so when you run
//-- your form, you SHOULD see the values immediately to KNOW
//-- the bindings are correctly talking to this entity.
public SimpleModel()
{
_name = "test1";
_Id = 123;
}
//-- changed to public and private... and notice in the setter
//-- to call this class's "somethingChanged" method
private int _Id;
public int Id
{
get { return _Id; }
set
{
_Id = value;
somethingChanged("Id");
}
}
private string _name;
public string Name
{ get { return _name; }
set { _name = value;
somethingChanged( "Name" );
}
}
//-- Expose publicly for anything else to listen to (i.e. your view model)
public event EventHandler SomethingChanged;
//-- So, when any property above changes, it calls this method with whatever
//-- its property is just as a reference. Then checks. Is there anything
//-- listening to our exposed event handler? If so, pass the information on
private void somethingChanged( string whatProperty)
{
// if something is listening
if (SomethingChanged != null)
SomethingChanged(whatProperty, null);
}
}
public class MyCommand : ICommand
{
public MyCommand(Action<object> execute, Predicate<object> canExecute)
{
_canExecute = canExecute;
_execute = execute;
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
//-- Change to the event handler definition, just expose it
public event EventHandler CanExecuteChanged;
//-- Now expose this method so your mvvm can call it and it rechecks
//-- it's own CanExecute reference
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, new EventArgs());
}
}
Finally, the bindings in the form. I don't know how you have set the "DataContext" of your view to your view model, but assuming that is all correct and no issue, adjust the textbox and command button to something like
<TextBox Name="NameBox" Text ="{Binding Entity.Name,
NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Save" Command="{Binding SaveCommand}" CommandParameter="{Binding Entity}" />
Notice the text binding is to the "Entity" object on your MVVM and then the ".Name" property of your Entity object. The important thing here is the UpdateSourceTrigger. This forces an update back to your data binding for every character change, so as soon as you remove the last character, or start typing the first character, the "Save" button will then be refreshed respectively.
I would try invoking CommandManager.InvalidateRequerySuggested.
http://msdn.microsoft.com/en-us/library/system.windows.input.commandmanager.invalidaterequerysuggested(v=vs.110).aspx