Having different buttons on a view, how could I control the IsEnabled state of the buttons when a command is being executed?
If I execute this command:
public ICommand DoSomethingCommand
{
get
{
if (doSomethingCommand == null)
{
doSomethingCommand = new RelayCommand(
(parameter) =>
{
this.IsBusy = true;
this.someService.DoSomething(
(b, m) =>
{
this.IsBusy = false;
}
);
},
(parameter) => !this.IsBusy);
}
return this.doSomethingCommand ;
}
}
I set the IsBusy property which fires the OnPropertyChanged event. All the other buttons check this property in their command's CanExecute. But the buttons do not get disabled, when the above command is executed.
How do I do this right?
CanExecuteChanged needs to fire in order for the Command Source to know to invoke CanExecute.
A simple way to do this is to invoke CommandManager.InvalidateRequerySuggested().
bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
if (value == _isBusy)
return;
_isBusy = value;
//NotifyPropertyChanged("IsBusy"); // uncomment if IsBusy is also a Dependency Property
System.Windows.Input.CommandManager.InvalidateRequerySuggested();
}
}
why not implement INotifyPropertyChanged on your own custom version of the RelayCommand class. This will conform to MVVM:
public class RelayCommand : ICommand, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly Action execute;
private readonly Func<bool> canExecute;
private bool isBusy;
public bool IsBusy
{
get { return isBusy; }
set
{
isBusy = value;
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs("IsBusy"));
}
}
}
public event EventHandler CanExecuteChanged
{
add
{
if (this.canExecute != null)
{
CommandManager.RequerySuggested += value;
}
}
remove
{
if (this.canExecute != null)
{
CommandManager.RequerySuggested -= value;
}
}
}
public RelayCommand(Action execute) : this(execute, null)
{
}
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
this.execute = execute;
this.canExecute = canExecute;
}
public void Execute(object parameter)
{
this.IsBusy = true;
this.execute();
this.IsBusy = false;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null ? true : this.canExecute();
}
}
then use the command like this:
<Button Command="{Binding Path=DoSomethingCommand}"
IsEnabled="{Binding Path=DoSomethingcommand.IsBusy,
Converter={StaticResource reverseBoolConverter}}"
Content="Do It" />
Related
I've created a base view model that implements ICommand. The commands bind just fine, execute as expected, and even begin in the correct states, but as properties that influence whether a command can execute or not change, the CanExecute for those commands doesn't seem to be updating.
In my code below, I can click the Run button and everything works as expected EXCEPT for the fact that when the ProgramStatus changes to Running it should be disabled.
In my base view model:
public class RelayCommand : ICommand
{
#region ICommand Member Variables
private Action<object> _execute;
private Predicate<object> _canExecute;
private event EventHandler _canExecuteChangedInternal;
#endregion // ICommand Member Variables
#region Constructors
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public RelayCommand(Action<object> execute) : this(execute, DefaultCanExecute)
{
}
#endregion // Constructors
#region ICommand Members
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
_canExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
_canExecuteChangedInternal -= value;
}
}
public bool CanExecute(object parameter)
{
return _canExecute != null && _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void OnCanExecuteChanged()
{
EventHandler eventHandler = _canExecuteChangedInternal;
if (eventHandler != null)
{
eventHandler.Invoke(this, EventArgs.Empty);
}
}
private static bool DefaultCanExecute(object parameter)
{
return true;
}
#endregion // ICommand Members
In my view model:
RelayCommand _runCommand;
public RelayCommand RunCommand
{
get
{
if (_runCommand == null)
{
_runCommand = new RelayCommand(param => Run(), param => CanRun);
}
return _runCommand;
}
}
public bool CanRun
{
get
{
bool result = false;
if (Machine.ProgramStatus != ProgramStatus.Running && Machine.ProgramStatus != ProgramStatus.TestRunning)
{
result = true;
}
return result;
}
}
In my view:
<Button Content="Run" Command="{Binding Path=RunCommand}" />
You have to invoke the CanExecuteChanged EventHandler. So when ProgramStatus changes to Running you can call the OnCanExecuteChanged helper method for the command you want to update (e.g. RunCommand.OnCanExecuteChanged();).
If CanExecute is bound to a property you can use the OnCanExecuteChanged helper method in the property's setter.
private bool _myProperty;
public bool MyProperty
{
get { return _myProperty; }
set
{
_myProperty= value;
RunCommand.OnCanExecuteChanged();
}
}
I'm struggling with using SaveFileDialog in MVVM.
I'm using RelayCommand class and launch SaveAsFileCommand. Within the SaveAsFileCommand, using lambda expression I split two arguments to have:
Instance of RichTextBox control and targeted path (filePath)
Then I call DataIO.SaveAsFile(arguments[0], arguments[1]), using above arguments.
To create the SaveDialogBox in the View Layer I'm using 3 classes:
DialogBox, FileDialogBox and SaveFileDialogBox
In the XAML I create the SaveDialogBox and try to call SaveAsFileCommand using MultiBinding to pass those two Command Parameters.
To show the SaveDialogBox I use the button which is bindded to SaveDialogBox
The problem is: At this place the compiler complains that it can't execute multibinding for my SaveDialogBox because of being non-DependencyObject and non-DependencyProperty.
How can I solve that problem and correctly save the file using that DialogBox being in accordance with MVVM in my case???
XAML parts of code:
<Button Command="{Binding ElementName=SaveFileDB, Path=Show}" >
<Button.ToolTip>
<ToolTip Style="{StaticResource styleToolTip}" >
<TextBlock Text="Save" Style="{StaticResource styleTextBlockTP}" />
</ToolTip>
</Button.ToolTip>
<Image Source="Icon\Save.png"/>
</Button>
<local:SaveFileDialogBox x:Name="SaveFileDB" Caption="Save content to file..."
Filter="Text files (*.txt)|*.txt|All files(*.*)|*.*"
FilterIndex="0" DefaultExt="txt"
CommandFileOK="{Binding SaveAsFileCommand}" >
<local:SaveFileDialogBox.CommandParemeter>
<MultiBinding Converter="{StaticResource parametersConverter}">
<Binding ElementName="MainRichTextBox" />
<Binding ElementName="SaveFileDB" Path="Show" />
</MultiBinding>
</local:SaveFileDialogBox.CommandParemeter>
</local:SaveFileDialogBox>
SaveAsFileCommand:
private ICommand _SaveAsFileCommand;
public ICommand SaveAsFileCommand
{
get
{
if (_SaveAsFileCommand == null)
{
_SaveAsFileCommand = new RelayCommand(
argument =>
{
var arguments = (object[])argument;
DataIO.SaveAsFile(arguments[0], arguments[1]);
}
);
}
return _SaveAsFileCommand;
}
}
DataIO.SaveAsFile method:
public static void SaveAsFile(object argument0, object argument1)
{
System.Windows.Controls.RichTextBox richTB = argument0 as System.Windows.Controls.RichTextBox;
string path = (string)argument1;
using (FileStream fileStream = new FileStream(path, FileMode.Create))
{
TextRange textRange = new TextRange(richTB.Document.ContentStart, richTB.Document.ContentEnd);
textRange.Save(fileStream, DataFormats.Text);
}
}
RelayCommand class:
class RelayCommand : ICommand
{
private readonly Action<object> _Execute;
private readonly Func<object, bool> _CanExecute;
public RelayCommand(Action<object> execute, Func<object, bool> canExecute)
{
if (execute == null) throw new ArgumentNullException("execute");
_Execute = execute;
_CanExecute = canExecute;
}
public RelayCommand(Action<object> execute)
{
if (execute == null) throw new ArgumentNullException("execute");
_Execute = execute;
}
public bool CanExecute(object parameter)
{
return _CanExecute == null ? true : _CanExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
if (_CanExecute != null) CommandManager.RequerySuggested += value;
}
remove
{
if (_CanExecute != null) CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
_Execute(parameter);
}
}
DialogBox class:
public abstract class DialogBox : FrameworkElement, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string parameter)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(parameter));
}
protected Action<object> execute = null;
public string Caption { get; set; }
protected ICommand show;
public virtual ICommand Show
{
get
{
if (show == null)
show = new RelayCommand(execute);
return show;
}
}
}
FileDialogBox class:
public abstract class FileDialogBox : CommandDialogBox
{
public bool? FileDialogResult { get; protected set; }
public string FilePath { get; set; }
public string Filter { get; set; }
public int FilterIndex { get; set; }
public string DefaultExt { get; set; }
protected Microsoft.Win32.FileDialog fileDialog = null;
protected FileDialogBox()
{
execute =
o =>
{
var values = (object[])o;
RelayCommand relCom1 = (RelayCommand)values[1];
fileDialog.Title = Caption;
fileDialog.Filter = Filter;
fileDialog.FilterIndex = FilterIndex;
fileDialog.DefaultExt = DefaultExt;
string filePath = "";
if (FilePath != null) filePath = FilePath; else FilePath = "";
//if (o != null) filePath = (string)o;
//if (o != null) filePath = (string)values[1];
if (o != null) filePath = relCom1.ToString();
if (!String.IsNullOrWhiteSpace(filePath))
{
fileDialog.InitialDirectory = System.IO.Path.GetDirectoryName(filePath);
fileDialog.FileName = System.IO.Path.GetFileName(filePath);
}
FileDialogResult = fileDialog.ShowDialog();
OnPropertyChanged("FileDialogResult");
if (FileDialogResult.HasValue && FileDialogResult != null)
{
FilePath = fileDialog.FileName;
OnPropertyChanged("FilePath");
ExecuteCommand(CommandFileOK, FilePath);
};
};
}
public static DependencyProperty CommandFileOKProperty =
DependencyProperty.Register("CommandFileOK", typeof(ICommand), typeof(FileDialogBox));
public ICommand CommandFileOK
{
get { return (ICommand)GetValue(CommandFileOKProperty); }
set { SetValue(CommandFileOKProperty, value); }
}
}
SaveFileDialogBox class:
public class SaveFileDialogBox : FileDialogBox
{
public SaveFileDialogBox()
{
fileDialog = new SaveFileDialog();
}
}
The way I handle a requirement for user input in a dialog is to use a control which goes in the view but has no UI.
I split the command to be done into two pieces.
Essentially these are show the dialog and invoke a command when you finish.
The control shows a dialog which grabs data and then invokes a command you give it via binding.
Since this is control you can bind fine and it's in the visual tree so it can get a reference to the window.
Please see confirmationrequestor in this:
https://gallery.technet.microsoft.com/WPF-User-Notification-MVVM-98940828
That's intended for confirming the deletion of a record but the same principle can be extended to a file picker with a little modification.
From that, the command invoked once the user clicks and closes the dialogue can capture any variables you need. If you bind them:
private RelayCommand _confirmCommand;
public RelayCommand ConfirmCommand
{
get
{
return _confirmCommand
?? (_confirmCommand = new RelayCommand(
() =>
{
confirmer.Caption = "Please Confirm";
confirmer.Message = "Are you SURE you want to delete this record?";
confirmer.MsgBoxButton = MessageBoxButton.YesNo;
confirmer.MsgBoxImage = MessageBoxImage.Warning;
OkCommand = new RelayCommand(
() =>
{
// You would do some actual deletion or something here
UserNotificationMessage msg = new UserNotificationMessage { Message = "OK.\rDeleted it.\rYour data is consigned to oblivion.", SecondsToShow = 5 };
Messenger.Default.Send<UserNotificationMessage>(msg);
});
RaisePropertyChanged("OkCommand");
ShowConfirmation = true;
}));
}
}
From the confirmationrequestor, invoking that command:
public static readonly DependencyProperty ShowConfirmDialogProperty =
DependencyProperty.Register("ShowConfirmDialog",
typeof(bool?),
typeof(ConfirmationRequestor),
new FrameworkPropertyMetadata(null
, new PropertyChangedCallback(ConfirmDialogChanged)
)
{ BindsTwoWayByDefault = true }
);
private static void ConfirmDialogChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool?)e.NewValue != true)
{
return;
}
ConfirmationRequestor cr = (ConfirmationRequestor)d;
Window parent = Window.GetWindow(cr) as Window;
MessageBoxResult result = MessageBox.Show(parent, cr.Message, cr.Caption, cr.MsgBoxButton, cr.MsgBoxImage);
if (result == MessageBoxResult.OK || result == MessageBoxResult.Yes)
{
if (cr.Command != null)
{
cr.Command.Execute(cr.CommandParameter);
}
}
cr.SetValue(ShowConfirmDialogProperty, false);
}
Can Execute of a ICommand while a Context menu open
With the continuation of the above query, Still its not able to achieve.
Is there any way to raise context menu command's CanExcute while open the context menu. This need to be done in the context menu itself and here am not able to access the viewmodel.
Any idea on this?
public static BaseCommand SaveCommand
{
get
{
if (saveCommand == null)
saveCommand = new BaseCommand(OnSaveCommandClicked, OnSaveCommandCanExcute);
return saveCommand;
}
}
where BaseCommand is derived from ICommand.
public class BaseCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _method;
public event EventHandler CanExecuteChanged;
public BaseCommand(Action<object> method)
: this(method, null)
{
}
public BaseCommand(Action<object> method, Predicate<object> canExecute)
{
_method = method;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_method.Invoke(parameter);
}
}
Inside BaseCommand, replace the CanExecuteChanged to look like the following. And it will work the way you want it.
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
I have a text box for file name with path. After user locates a file using OpenFileDialog, this text box should be populated with filename. This text should also work when user enters filename with path directly instead of selecting from file dialog box.
Since I am learning MVVM, I am getting hard time to figure out how to update text box with filename/path. I tried everything i can think of.
I was expecting onPropertyChanged(“FilenameWithPath”) should take care this issue. Can somebody show me how to deal with this issue?
See code below
FileBrowseView.xaml
<TextBox Height="23" HorizontalAlignment="Left" Margin="113,22,0,0"
Name="txtFilenameWithPath"
Text="{Binding Path=FilenameWithPath,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
VerticalAlignment="Top" Width="300" />
<Button
Content="Browse..."
Height="30"
HorizontalAlignment="Left"
Margin="433,20,0,0"
Name="btnBrowse"
VerticalAlignment="Top"
Width="142"
Command="{Binding Path=BrowseCommand}" />
FileBrowseView.xaml.cs
public partial class FileBrowseView : Window
{
public FileBrowseView()
{
InitializeComponent();
DataContext = new FileBrowseViewModel();
}
}
FileBrowseModel
public class FileBrowseModel
{
private string _filenameWithPath = string.Empty;
public string FilenameWithPath
{
get { return _filenameWithPath; }
set
{
if (value == _filenameWithPath)
return;
else
_filenameWithPath = value;
}
}
}
FileBrowseViewModel
public class FileBrowseViewModel : INotifyPropertyChanged
{
private string _filenameWithPath = string.Empty;
public string FilenameWithPath
{
get { return _filenameWithPath; }
set
{
if (value == _filenameWithPath)
return;
else
_filenameWithPath = value;
OnPropertyChanged("FilenameWithPath");
}
}
private ICommand _browseCommand;
public ICommand BrowseCommand
{
get
{
if (_browseCommand == null)
_browseCommand = new DoBrowse();
return _browseCommand;
}
set
{
_browseCommand = value;
OnPropertyChanged("FilenameWithPath");
}
}
private class DoBrowse : ICommand
{
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
var filedialog = new System.Windows.Forms.OpenFileDialog();
DialogResult fresult = filedialog.ShowDialog();
if (fresult == System.Windows.Forms.DialogResult.OK)
{
FilenameWithPath = filedialog.FileName;
//I am trying to assign value i got from OpenFileDialog to
// FilenameWithPath property
//complier says "Cannot access non static member of outer type
'MyProject.FileBrowseViewModel' via
nested type 'MyProject.FileBrowseViewModel.DoBrowse
onPropertyChanged(“FilenameWithPath”);
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You just need to set FileNameWithPath in your command's Execute function. And the setter for FileNameWithPath ought to be calling OnPropertyChanged. You shouldn't have to call that from your command's Execute function.
EDIT: Make sure that you are setting your data context to be the viewmodel and not the model since both have FilenameWithPath properties. If you were doing this the bind wouldn't fail because there is still a property to bind to. Otherwise:
Make the following changes:
public string FilenameWithPath
{
get { return _filenameWithPath; }
set
{
if (value == _filenameWithPath)
return;
else
{
_filenameWithPath = value;
OnPropertyChanged("FilenameWithPath");
}
}
}
and
if (fresult == System.Windows.Forms.DialogResult.OK)
{
FilenameWithPath = filedialog.FileName;
}
This should fix your problem. Additionally, consider changing which dialog box you use since this is WPF (as suggested in my comment).
Finally I am able to resolve this issue by adding new class called RelayCommand. I have modified the get block of _browseCommand use relay command as below.
private ICommand _browseCommand;
public ICommand BrowseCommand
{
get{
if (_browseCommand == null){
_browseCommand = new RelayCommand(
a => this.DoBrowseFolder(),
p => this.CheckCondition());
}
return _browseCommand;
}
set
{ _browseCommand = value;
OnPropertyChanged("FilenameWithPath");
}
}
public bool CheckCondition () {
//Check condition here if needed
return true;
}
private void DoBrowseFolder(){
var filedialog = new System.Windows.Forms.OpenFileDialog();
DialogResult fresult = filedialog.ShowDialog();
if (fresult == System.Windows.Forms.DialogResult.OK)
{
FilenameWithPath = filedialog.FileName;
OnPropertyChanged("FilenameWithPath ");
}
}
Relay Command Class
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute, Predicate<object> canExecute){
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter) {
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter) {
_execute(parameter);
}
#endregion // ICommand Members
}
I've started using databinding with MVVM pattern in Silverlight. There's one problem for me.
I have a listbox, when I select an item there one property in the View-Model have to change it's value. I implemented DelegateCommand and made custom CommandListBox class as follows
public class DelegateCommand<T> : ICommand
{
private readonly Action<T> _execute;
private readonly Predicate<T> _canExecute;
public DelegateCommand(Action<T> execute)
: this(execute, x => true)
{
}
public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
{
if (canExecute == null) throw new ArgumentNullException("canExecute");
if (execute == null) throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public bool CanExecute(object parameter)
{
return _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged.DynamicInvoke(this);//.Raise(this);
}
}
public class DelegateCommand : DelegateCommand<object>
{
public DelegateCommand(Action execute)
: base(execute != null ? x => execute() : (Action<object>)null)
{
}
public DelegateCommand(Action execute, Func<bool> canExecute)
: base(execute != null ? x => execute() : (Action<object>)null,
canExecute != null ? x => canExecute() : (Predicate<object>)null)
{
}
}
public class CommandListBox : ListBox
{
public CommandListBox()
{
SelectionChanged += (sender, e) =>
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
};
}
public static DependencyProperty CommandProperty =
DependencyProperty.Register("Command",
typeof(ICommand), typeof(CommandListBox),
new PropertyMetadata(null, CommandChanged));
private static void CommandChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
{
var treeList = source as CommandListBox;
if (treeList == null) return;
treeList.RegisterCommand(args.OldValue as ICommand, args.NewValue as ICommand);
}
private void RegisterCommand(ICommand oldCommand, ICommand newCommand)
{
if (oldCommand != null)
oldCommand.CanExecuteChanged -= HandleCanExecuteChanged;
if (newCommand != null)
newCommand.CanExecuteChanged += HandleCanExecuteChanged;
HandleCanExecuteChanged(newCommand, EventArgs.Empty);
}
private void HandleCanExecuteChanged(object sender, EventArgs args)
{
if (Command != null)
IsEnabled = Command.CanExecute(CommandParameter);
}
public ICommand Command
{
get { return GetValue(CommandProperty) as ICommand; }
set { SetValue(CommandProperty, value); }
}
public static DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter",
typeof(object), typeof(CommandListBox),
new PropertyMetadata(null));
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
}
I'm catching the event, when the selected item in listbox is changed but I don't know how to take it's value.
Could you help me please?
Your CommandListBox derives from a ListBox, it exposes a SelectionChanged event, you're attaching to that event already.
SelectionChanged += (sender, e) =>
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
};
In order to obtain the currently selected item you can do something like this :
SelectionChanged += (sender, e) =>
{
CommandListBox source = sender as CommandListBox; // This is the sender
if(source != null) // just to be sure
{
var value = source.SelectedItem;
}
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
};
Does this help ?
You can bind your listbox's SelectedItem property to an appropriate property in your ViewModel.