I'm new in Silverlight and I have some issues regarding Commands. I have a DataGrid which is bound to ObservableCollection in my ViewModel. I also have a button
<Button Command="{Binding AddCommand}">Add</Button>
which Command property is bound to command of ViewModel.
Command class looks like that
public class GenericCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private Action<object> execute;
private Func<object, bool> canExecute;
private bool previousState;
public GenericCommand(Action<object> execute, Func<object, bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (canExecute == null) return false;
bool currentState = canExecute(parameter);
if (currentState != previousState)
{
previousState = currentState;
if (CanExecuteChanged != null)
CanExecuteChanged(this, new EventArgs());
return currentState;
}
return currentState;
}
public void Execute(object parameter)
{
if (execute == null) return;
execute(parameter);
}
}
property AddCommand was created this way
AddCommand = new GenericCommand(Add,CanAdd);
public bool CanAdd(object param)
{
return SelectedItem != null;
}
public void Add(object param)
{
}
The problem is that it seems that CommandBinding don't react to changes of SelectedItem. If I run my applicatiopn none of grid rows are selected, I can see that CanAdd function is invoked. However if I click on some item CanAdd function is not invoked - despite the fact that I can see that property SelectedItem in view model has changed ?? What do I do wrong ?
Is it possible to use commands without using some external library ?
I used to write similar code in WPF however in WPF in GeneriCommand class I used
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
The problem is that in Silverlight there is no such thing like CommandManager.
Your ViewModel will need to implement the INotifyDataErrorInfo interface. Also the CanAdd method needs to be changed to something like this.
if (SelectedItem == null)
{
base.AddValidationErrorMessage("SelectedItem", "Select something....");
return false;
}
else
{
base.RemoveValidationErrorMessage("SelectedItem");
return true;
}
Also take a look at these articles, I think you will find them helpful.
http://weblogs.asp.net/fredriknormen/archive/2010/01/09/silverlight-about-validation-when-binding-to-custom-forms-part-2-using-silverlight-4.aspx
http://weblogs.asp.net/fredriknormen/archive/2009/11/22/silverlight-4-and-asynchronous-validation-with-inotifydataerrorinfo.aspx
Related
I have two menu items, "message", and "check". "Check" is Checkable and have a checkbox near the header. I want, by clicking on Check to uncheck it, and disable the "message" item.
Also, I want to do it by both clicking, and using a shortcut.
I wrote some additional classes like 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)
{
_canExecute = canExecute;
_execute = execute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
Observable object (which is analogue of INotifyOnPropertyChanged)
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
and MainViewModel class
public class MainViewModel : ObservableObject
{
private readonly MainWindow _mainWindow;
private bool _isChecked { get; set; } = true;
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
_isChecked = value;
OnPropertyChanged();
}
}
public RelayCommand Check { get; set; }
public MainViewModel(MainWindow mainwindow)
{
_mainWindow = mainwindow;
IsChecked = false;
Check = new RelayCommand(o =>
{
if (IsChecked == false)
{
_mainWindow.Message_menu_item.IsEnabled = true;
IsChecked = true;
}
else
{
_mainWindow.Message_menu_item.IsEnabled = false;
IsChecked = false;
}
});
}
}
My xaml
<MenuItem Header="File">
<MenuItem
Name="Message_menu_item"
InputGestureText="Ctrl+M"
Header="_Message"/>
<MenuItem
Name="Check_menu_item"
InputGestureText="Ctrl+C"
Command="{Binding Check}"
Header="Check"
IsCheckable="True"
IsChecked="{Binding IsChecked}"/>
<Separator />
<MenuItem Header="Exit"
InputGestureText="Ctrl+E"/>
</MenuItem>
And binding
<KeyBinding Key="C" Modifiers="Ctrl" Command="{Binding Check}"/>
I wanted to start an app with checked checkbox and available Message menu item, but it is starting unchecked, and by clicking it, it simply disabling the message, and ignoring the checkbox (it only work ones, clicking on it again doesn't change anything). It only works fine using the shortcut, BUT I can only use shortcut after clicking the menu dropdown button "file" in my case (like this, and if it is closed shortcut doesn't work )
I don't understand why is it working so weird, please help.
I wanted to start an app with checked checkbox
If so, you should change the initialization IsChecked = false; in your viewmodel constructor accordingly.
The weird behaviour of the checkbox is a result of modifying IsChecked from the Check command plus binding it to the IsChecked property of the menu item without specifying a mode (which results in a two way binding). So, when using the menu, the property is toggled twice: via command and via the binding. Using the key binding works, because it only triggers the command.
To solve this, either change the binding mode to OneWay or don't change the property value in the command.
Furthermore: You should remove the reference to the window from your viewmodel. This can be achieved by binding the IsEnabled property of the message menu item to another property on your viewmodel like this:
public bool IsMessageMenuEnabled => !this.IsChecked;
public bool IsChecked
{
get => this.isChecked;
set
{
this.isChecked = value;
this.OnPropertyChanged();
this.OnPropertyChanged(nameof(this.IsMessageMenuEnabled));
}
}
The following code is working for usercontrols but not in the Mainwindows. Setting Focusable="True" for the mainwindow.
<Window.InputBindings>
<KeyBinding Modifiers="Ctrl" Key="S" Command="{Binding SaveCommand}" />
</Window.InputBindings>
private ICommand _saveCommand;
public ICommand SaveCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand(
param => this.SaveObject(),
param => this.CanSave()
);
}
return _saveCommand;
}
}
private bool CanSave()
{
return (Project != null);
}
private void SaveObject()
{
// Code here
}
Got fixed by using the below code from the link.
Keyboard shortcuts in WPF
public YourWindow() //inside any WPF Window constructor
{
...
//add this one statement to bind a new keyboard command shortcut
InputBindings.Add(new KeyBinding( //add a new key-binding, and pass in your command object instance which contains the Execute method which WPF will execute
new WindowCommand(this)
{
ExecuteDelegate = TogglePause //REPLACE TogglePause with your method delegate
}, new KeyGesture(Key.P, ModifierKeys.Control)));
...
}
Create a simple WindowCommand class which takes an execution delegate to fire off any method set on it.
public class WindowCommand : ICommand
{
private MainWindow _window;
//Set this delegate when you initialize a new object. This is the method the command will execute. You can also change this delegate type if you need to.
public Action ExecuteDelegate { get; set; }
//You don't have to add a parameter that takes a constructor. I've just added one in case I need access to the window directly.
public WindowCommand(MainWindow window)
{
_window = window;
}
//always called before executing the command, mine just always returns true
public bool CanExecute(object parameter)
{
return true; //mine always returns true, yours can use a new CanExecute delegate, or add custom logic to this method instead.
}
public event EventHandler CanExecuteChanged; //i'm not using this, but it's required by the interface
//the important method that executes the actual command logic
public void Execute(object parameter)
{
if (ExecuteDelegate != null)
{
ExecuteDelegate();
}
else
{
throw new InvalidOperationException();
}
}
}
I am trying to attach a command and a commandparameter to a textbox on return key but without success. The parameter is the current text in the same textbox.
<TextBox x:Name="txtSearch">
<TextBox.InputBindings>
<KeyBinding Command="{Binding SearchCommand}"
CommandParameter="{Binding Path=Text, ElementName=txtSearch}" Key="Return" />
</TextBox.InputBindings>
</TextBox>
Basically I want to execute the command when user clicks on return/enter key and pass as a parameter the current text in the textbox.
I have found this link where it is said that in .NET 3.5 command parameter for keybinding is not accepting bindings. So a solution is proposed by code in code-behind but how can I pass a parameter to the command from the code?
First, you'll need to add the KeyBinding to your TextBox and set its Command on code-behind. Just add this in the constructor of your View:
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
KeyBinding kb = new KeyBinding();
kb.Command = (DataContext as MyViewModel).SearchCommand;
kb.Key = Key.Enter;
txtSearch.InputBindings.Add(kb);
}
Then, you can bind the Text property of the TextBox named txtSearch to a property of your ViewModel. This way you don't need to pass a parameter as you can use the value of that property in your ViewModel inside the code that executes your Command.
Your ViewModel should look like this:
public class MyViewModel : ObservableObject
{
private string _txtSearch;
public string TxtSearch
{
get { return _txtSearch; }
set
{
if (value != _txtSearch)
{
_txtSearch = value;
OnPropertyChanged("TxtSearch");
}
}
}
private ICommand _searchCommand;
public ICommand SearchCommand
{
get
{
if (_searchCommand == null)
{
_searchCommand = new RelayCommand(p => canSearch(), p => search());
}
return _searchCommand;
}
}
private bool canSearch()
{
//implement canExecute logic.
}
private void search()
{
string text = TxtSearch; //here you'll have the string that represents the text of the TextBox txtSearch
//DoSomething
}
}
If you have access to C# 6 (Visual Studio 2015 and later versions), you can alter the call to the OnPropertyChanged to: OnPropertyChanged(nameof(TxtSearch));. This way you get rid of the "magic string" and eventual renaming of the property won't cause any problem for you.
And then your XAML should look like this: (Notice that you need to specify that te UpdateSourceTrigger must be PropertyChanged, so that your TxtSearch property of your ViewModel stays up to date when you hit the Enter key on your TextBox.
<TextBox Text="{Binding TxtSearch, UpdateSourceTrigger=PropertyChanged}" x:Name="txtSearch"/>
Your ViewModel needs to implement INotifyPropertyChanged and you need a proper ICommand implementation. Here I'll use the RelayCommand.
Those implementations are shown below.
Since your framework is .NET 3.5, implement it like this:
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
This is a implementation of the RelayCommand:
public class RelayCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _execute;
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
{
_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; }
}
}
So I have this View:
<StackPanel>
<TextBox x:Name="Name"/>
<Button x:Name="SayHello"
Content="Click Me" />
</StackPanel>
And I have this ViewModel:
internal class ShellViewModel : PropertyChangedBase
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
NotifyOfPropertyChange(() => Name);
NotifyOfPropertyChange(() => CanSayHello());
}
}
public bool CanSayHello()
{
bool isenabled = !string.IsNullOrWhiteSpace(Name);
return isenabled;
}
public void SayHello()
{
MessageBox.Show(string.Format("Hello, {0}!", Name));
}
}
But whenever I enter some value in the textbox, I got this exception:
{"Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpressionN' to type 'System.Linq.Expressions.MemberExpression'."}
In the commercial app that I'm developing I must have the CanSayHello() member in the VM as a method, not as a property. What should I do?
You have that exception because you are calling NotifyOfPropertyChange on CanSayHello and CanSayHello is a method, not a property.
Change to:
public bool CanSayHello
{
get { return !string.IsNullOrWhiteSpace(Name); }
}
You should use commands to bind actions to UI items. There is an implementation of ICommand called RelayCommand which is typically used in most WPF projects. It allows you to use method, property or predicate.
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(nameof(execute));
_execute = execute;
_canExecute = canExecute;
}
[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);
}
}
Problem: Buttons never gets enabled.
<Button Name="btnCompareAxises"Command="{Binding CompareCommand}"
Content="{Binding VM.CompareAxisButtonLabel}"
IsEnabled="{Binding VM.IsCompareButtonEnabled}">
</Button>
ViewModel constructor:
this.CompareCommand = new DelegateCommand(CompareCommand, ValidateCompareCommand);
The problem seems to be related to the CanExecute eventhandler of the registered Command of the button.
The CanExecute handler returns false when the application loads.
This is fine, as the conditions are not met initially.
The canExecute handler only runs on application startup or when the button is clicked. You cannot click a disabled button, so the button stays disabled forever if the initial value returned form the CanExecute handler is false!
Question:
Do I have to enable the button again, only using the command bound to it.
Something like, hey command please reevaluate if the conditions for this buttons are met ?
Why sits the IsEnabled property under section Coercion and not under local?
The command:
public class DelegateCommand : ICommand
{
private readonly Func<object, bool> canExecute;
private readonly Action<object> execute;
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
public void RaiseCanExecuteChanged()
{
this.OnCanExecuteChanged();
}
protected virtual void OnCanExecuteChanged()
{
var handler = this.CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
Solved:
I had to adapt the DelegateCommand class to make it work:
I have added CommandManager.RequerySuggested to the public CanExecuteChanged Event property.
Now it will automatically re-evaluate the CanExecute method of the command when soemthing changes in the UI!
public class DelegateCommand : ICommand
{
private readonly Func<object, bool> canExecute;
private readonly Action<object> execute;
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
/// CommandManager
/// Go to the "References" part of your class library and select "Add Reference".
/// Look for an assembly called "PresentationCore" and add it.
public event EventHandler CanExecuteChanged
{
add
{
_internalCanExecuteChanged += value;
CommandManager.RequerySuggested += value;
}
remove
{
_internalCanExecuteChanged -= value;
CommandManager.RequerySuggested -= value;
}
}
event EventHandler _internalCanExecuteChanged;
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
public void RaiseCanExecuteChanged()
{
this.OnCanExecuteChanged();
}
protected virtual void OnCanExecuteChanged()
{
var handler = this._internalCanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
Removed this from the button:
IsEnabled="{Binding VM.IsCompareButtonEnabled}"
The binding here is not necessary, as the CanExecute handler will take care of the enabled/disabled state of the button!