in VM, set ICommand like:
private RelayCommand<EventArgs> _myCommand = null;
public RelayCommand<EventArgs> MyCommand
{
get
{
if (_myCommand == null)
{
_myCommand = new RelayCommand<EventArgs>((e) =>
{
//....
}
);
}
return _myCommand;
}
}
In xaml, binding to this command like
<Button Content="Test Command" Margin="2,0,2,0" Command="{Binding Path=MyCommand}" CommandParameter="{Binding ElementName=InputTextBox, Path=Text}" />
then run the app. it say can't convert string to EventArgs.
How to set EventArgs for ICommand binding?
I'm not familiar with RelayCommand<T> I've only come across RelayCommand.
However it would seem that RelayCommand<EventArgs> can't possibly be useful. I suspect that you would at least need RelayCommand<string>.
Related
I've created RichTextBox control with Menu at the top side of Window. The MenuItems call Commands - It works perfectly. Then I try to create ContextMenu in the RichTextBox and want to call the same commands like in MenuItems.
So that, I bind the ContextMenu in the same way like MenuItems but it throws the NullReferenceException.
What can be the reason. How should I bind the Command to the ContextMenu??
Below are the parts of my code
MenuItem code:
<MenuItem Name="FontSettings" Header="Font settings" Command="{Binding FontSettingsCommand}" CommandParameter="{Binding ElementName=MainRichTbx}" />
RichTextBox code:
<RichTextBox Name="MainRichTbx" TextBlock.LineHeight="0.1" Margin="5" >
<RichTextBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Font settings" Command="{Binding FontSettingsCommand}" CommandParameter="{Binding ElementName=MainRichTbx}" />
</ContextMenu>
</RichTextBox.ContextMenu>
</RichTextBox>
That command which I want to execute:
private ICommand _FontSettingsCommand;
public ICommand FontSettingsCommand
{
get
{
if (_FontSettingsCommand == null)
{
_FontSettingsCommand = new RelayCommand(
argument => EditorFormat.SetFont(argument),
argument => true
);
}
return _FontSettingsCommand;
}
}
The method which I call within the Command:
public static void SetFont(object control)
{
FontDialog fontDialog = new FontDialog();
if (fontDialog.ShowDialog() == DialogResult.OK)
{
(control as System.Windows.Controls.RichTextBox).FontFamily = new System.Windows.Media.FontFamily(fontDialog.Font.Name);
(control as System.Windows.Controls.RichTextBox).FontSize = fontDialog.Font.Size;
(control as System.Windows.Controls.RichTextBox).FontStyle = fontDialog.Font.Italic ? FontStyles.Italic : FontStyles.Normal;
(control as System.Windows.Controls.RichTextBox).FontWeight = fontDialog.Font.Bold ? FontWeights.Bold : FontWeights.Regular;
}
}
And The 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 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);
}
}
I have a solution which I think works, binding to the PlacementTarget of the context menu.
<RichTextBox Name="MainRichTbx" TextBlock.LineHeight="0.1" Margin="5" >
<RichTextBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Font settings"
Command="{Binding FontSettingsCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}" />
</ContextMenu>
</RichTextBox.ContextMenu>
</RichTextBox>
However, the XAML designer underlines the CommandPamameter and shows the tooltip "RelativeSource is not in FindAncestor mode". Nevertheless it seems to work.
Edit
Adding Mode=FindAncestor seems to fix the error message. I don't know if it has any effect on the behavior.
<RichTextBox Name="MainRichTbx" TextBlock.LineHeight="0.1" Margin="5" >
<RichTextBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Font settings"
Command="{Binding FontSettingsCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}" />
</ContextMenu>
</RichTextBox.ContextMenu>
</RichTextBox>
This is probably because the binding can't find your text box.
This happens because the ContextMenu is not part of the visual tree so can't find your Text Box for the CommandParameter.
The best way around this is to not use the CommandParameter at all, but use a variable in your ViewModel (such as SelectedTextBox).
However, you can get a working (but slightly uglier) solution by naming your ContextMenu and setting its NameScope in the View's constructor:
NameScope.SetNameScope(myContextMenu, this);
This should then work correctly.
I have a problem with MVVM-Light. I use the version 5.3.0.0...
.XAML
<DockPanel Dock="Top">
<Button Margin="5" VerticalAlignment="Top" HorizontalAlignment="Center" Command="{Binding CancelDownloadCommand}" FontSize="20"
Background="Transparent" BorderThickness="2" BorderBrush="{StaticResource AccentColorBrush4}" ToolTip="Cancelar"
DockPanel.Dock="Right">
<StackPanel Orientation="Horizontal">
<Image Source="Images/48x48/Error.png" Height="48" Width="48"/>
<Label Content="{Binding ToolTip, RelativeSource={RelativeSource AncestorType={x:Type Button}}}" FontFamily="Segoe UI Light"/>
</StackPanel>
</Button>
<Button Margin="5" VerticalAlignment="Top" HorizontalAlignment="Center" Command="{Binding DownloadCommand}" FontSize="20"
Background="Transparent" BorderThickness="2" BorderBrush="{StaticResource AccentColorBrush4}" ToolTip="Descargar"
DockPanel.Dock="Right">
<StackPanel Orientation="Horizontal">
<Image Source="Images/48x48/Download.png" Height="48" Width="48"/>
<Label Content="{Binding ToolTip, RelativeSource={RelativeSource AncestorType={x:Type Button}}}" FontFamily="Segoe UI Light"/>
</StackPanel>
</Button>
</DockPanel>
DownloadViewModel.cs
I used a MessageBox, but in my case, call a method that reads an XML. This example does not work, the buttons are disabled, but are not reactivated at the end of execution. I need to click on the UI to activate.
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
private async void Download()
{
Reset();
await Task.Run(() =>
{
MessageBox.Show("Hello");
});
Reset();
}
private void Reset()
{
IsEnabled = !IsEnabled;
IsEnabledCancel = !IsEnabledCancel;
}
private ICommand _downloadCommand;
public ICommand DownloadCommand
{
get { return _downloadCommand ?? (_downloadCommand = new RelayCommand(Download, () => IsEnabled)); }
}
private ICommand _cancelDownloadCommand;
public ICommand CancelDownloadCommand
{
get
{
return _cancelDownloadCommand ??
(_cancelDownloadCommand = new RelayCommand(CancelDownload, () => IsEnabledCancel));
}
}
private bool _isEnabled = true;
private bool IsEnabled
{
get { return _isEnabled; }
set
{
if (_isEnabled != value)
{
_isEnabled = value;
RaisePropertyChanged();
}
}
}
private bool _isEnabledCancel;
private bool IsEnabledCancel
{
get { return _isEnabledCancel; }
set
{
if (_isEnabledCancel != value)
{
_isEnabledCancel = value;
RaisePropertyChanged();
}
}
}
By using CommandManager.InvalidateRequerySuggested(), I fixed it. But read somewhere that is not recommended because this command checks all RelayCommand. This did not happen to me before.
But if within the Task.Run not add anything. It works perfectly. Buttons are activated and deactivated again.
private async void Download()
{
Reset();
await Task.Run(() =>
{
// WIDTHOUT CODE
// WIDTHOUT CODE
// WIDTHOUT CODE
});
Reset();
}
When you update CanExecute, in your case IsEnabled and IsEnabledCancel properties, you have to raise CanExecuteChanged event.
Even more you can little bit simplify your code.
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (Set(ref _isEnabled, value))
{
DownloadCommand.RaiseCanExecuteChanged();
}
}
}
The same way update your IsEnabledCancel property.
Of course, you have to declare your command as RelayCommand and not ICommand.
private RelayCommand _downloadCommand;
public RelayCommand DownloadCommand
{
get { return _downloadCommand ?? (_downloadCommand = new RelayCommand(Download, () => IsEnabled)); }
}
You can also read about: "A smart MVVM command".
Looking at the source code for MVVM Light, it is based around the CommandManager.InvalidateRequerySuggested() (anti) pattern. Which you rightly say is a massive performance hog, due to the global nature of the (anti)pattern.
The problem lies in the constructor. public RelayCommand(Action execute, Func<bool> canExecute)
With the canExecute being a Func<bool>, it is impossible to be able to get (at runtime) the property name, and is therefore impossible to bind on the the INotifyPropertyChanged.PropertyChanged event. Thus causing the command to re-evaluate the canExecute.
#kubakista shows you how to force the re-evaluation by calling the RaiseCanExecuteChanged method. But that really breaks the single responsibility principle, and leaks the implementation of the ICommand.
My advice is to use ReactiveUI's ReactiveCommand. This allows you to do:
DownloadCommand = ReactiveCommand.Create(Download, this.WhenAnyValue(x => x.IsEnabled).Select(enabled => enabled));
CancelDownloadCommand = ReactiveCommand.Create(CancelDownload, this.WhenAnyValue(x => x.IsEnabled).Select(enabled => false == enabled));
public bool IsEnabled
{
get {return _isEnabled; }
set
{
_isEnabled = value;
OnPropertyChanged();
}
}
One thing i did notice is that your Enabled Properties (IsEnabled, IsEnabledCancel) are private when they should be public. However that doesn't fix your issue :)
A simple fix is to get rid of the CanExecute part of your Command
eg
public ICommand DownloadCommand
{
get { return _downloadCommand ?? (_downloadCommand = new RelayCommand(Download)); }
}
and bind to your property on the Button.IsEnabledproperty in xaml
eg
<Button IsEnabled="{Binding IsEnabled}" Margin="5" VerticalAlignment="Top"
HorizontalAlignment="Center" Command="{Binding DownloadCommand}"
FontSize="20" Background="Transparent" BorderThickness="2"
BorderBrush="Red" ToolTip="Descargar" DockPanel.Dock="Right">
...
</Button>
Hope that helps
Pressing the delete key is not causing the command below to fire. One can assume DelegateCommand follows interface contracts since other instances of DelegateCommand referenced in the View are properly firing within the ViewModel.
Why?
View:
<ListBox Height="132"
Name="lbFiles"
ItemsSource="{Binding LbItems, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
VerticalAlignment="Center"
SelectedValue="{Binding Path=ListBoxSelection}">
<ListBox.InputBindings>
<KeyBinding Key="Delete"
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}
,Path=DataContext.CommandInputFilesDeleteSelected}" />
</ListBox.InputBindings>
ViewModel:
private DelegateCommand commandInputFilesDeleteSelected;
public ICommand CommandInputFilesDeleteSelected {
get {
if (commandInputFilesDeleteSelected == null) {
commandInputFilesDeleteSelected = new DelegateCommand(InputFilesDeleteSelected);
}
return saveCommand;
}
}
private void InputFilesDeleteSelected() { //this never fires :(
LbItems.Remove(ListBoxSelection);
}
private DelegateCommand commandInputFilesDeleteSelected;
public ICommand CommandInputFilesDeleteSelected {
get {
if (commandInputFilesDeleteSelected == null) {
commandInputFilesDeleteSelected = new DelegateCommand(InputFilesDeleteSelected);
}
return saveCommand; //changed from saveCommand to commandInputFilesDeleteSelected whoops
}
}
I trying to use MVVM in some silverlight modal that i wrote -
I wrote the view - and the viewmodel part - but i need to make the command between them and i don't know how to do it.
In the view i have single button that will launch the command.
How to do it ?
Thanks for the help.
In View Model
private RelayCommand _Command;
public RelayCommand Command
{
get
{
if (_Command == null)
{
_Command= new RelayCommand(() =>
{
});
}
return _Command;
}
private set { }
}
USE PARAMETERS
private RelayCommand<string> _Command;
public RelayCommand<string> Command
{
get
{
if (_Command == null)
{
_Command= new RelayCommand<string>((X) =>
{
});
}
return _Command;
}
private set { }
}
In View
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:gs_cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.SL4"
<Button Grid.Row="1" Grid.Column="1" Margin="4" HorizontalAlignment="Right" Name="btnSelect" Content="..." Width="25" Height="25" TabIndex="2">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<gs_cmd:EventToCommand Command="{Binding Path=Command,Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Another version with Parameters, to add to Masoomian's anser:
private RelayCommand<MyViewModel> _Command;
public RelayCommand<MyViewModel> Command
{
get
{
if (_Command == null)
{
_Command= new RelayCommand<MyViewModel>((vm) =>
{
vm.IsBusy = true; // Set a Parameter
vm.DoSomething(); // Do some work
// Call other methods on the View Model as needed
// ...
});
}
return _Command;
}
private set { }
}
I'm using the MVVM Light Toolkit with Silverlight.
On my UserControl I have a ListBox that displays a list of files. Each file has a delete image next to the file name. In the DataTemplate for the listbox I have an image (or can use a button) and a TextBlock.
So I want to capture using the event when the user will clicks on the image(or button with image) to remove the file from the list of files.
But I cannot seem to capture the event. Maybe this is due to having the SelectedItem Event on the listbox?
public class MainViewModel : ViewModelBase
{
#region Properties
public const string SelectedListBoxFilePropertyName = "SelectedUploadFile";
private UploadFile _selectedUploadFile = null;
public UploadFile SelectedUploadFile
{
get
{
return _selectedUploadFile;
}
set
{
if (_selectedUploadFile == value)
return;
_selectedUploadFile = value;
RaisePropertyChanged(SelectedListBoxFilePropertyName);
}
}
public const string UploadFilesPropertyName = "UploadFiles";
private ObservableCollection<UploadFile> _uploadFiles = new ObservableCollection<UploadFile>();
public ObservableCollection<UploadFile> UploadFiles
{
get
{
return _uploadFiles;
}
set
{
if (_uploadFiles == value)
return;
_uploadFiles = value;
RaisePropertyChanged(UploadFilesPropertyName);
}
}
#endregion
public static ICommand BrowseCommand { get; private set; }
public static ICommand DragDropFileCommand { get; private set; }
public static ICommand RemoveCommand { get; private set; }
#region Constructor
public MainViewModel()
{
if (IsInDesignMode)
{
// Code runs in Blend --> create design time data.
UploadFiles = new UploadFileContainer().UploadFiles;
}
else
{
// Code runs "for real"
}
WireUpCommands();
}
#endregion
#region Event Handlers
private void OnBrowseFileCommand()
{
var dialog = new OpenFileDialog();
dialog.ShowDialog();
if (dialog.Files != null)
AddFiles(dialog.Files);
}
private void OnDropFileCommand(DragEventArgs e)
{
var files = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];
AddFiles(files);
}
private void OnRemoveFileCommand()
{
UploadFiles.Remove(_selectedUploadFile);
}
#endregion
#region Private Methods
private void WireUpCommands()
{
BrowseCommand = new RelayCommand(OnBrowseFileCommand);
DragDropFileCommand = new RelayCommand<DragEventArgs>(e => OnDropFileCommand(e));
RemoveCommand = new RelayCommand(OnRemoveFileCommand);
UploadCommand = new RelayCommand(OnClickUploadCommand);
}
#endregion
}
<ListBox Grid.Row="1" Height="214" HorizontalAlignment="Left" AllowDrop="True" Margin="6,26,0,0" Name="UploadFilesListBox" VerticalAlignment="Top" Width="415" ItemsSource="{Binding Path=UploadFiles}" SelectedItem="{Binding Path=SelectedListBoxFile, Mode=TwoWay}" ScrollViewer.VerticalScrollBarVisibility="Auto">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Drop">
<cmd:EventToCommand Command="{Binding DragDropFileCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.Background>
<ImageBrush ImageSource="/FileUploadApplication;component/Resources/dragdrophere.png" Stretch="None" />
</ListBox.Background>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RemoveCommand}">
<Image Source="/FileUploadApplication;component/Resources/delete.png"/>
</Button>
<Image Source="/FileUploadApplication;component/Resources/delete.png">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding RemoveCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image> <TextBlock Text=" " />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Since your ItemsSource is UploadFiles it's probably sending the event to UploadFile and not the view model the user control is bound to.
Your button is the element of the ItemTemplate. you're binding the listbox ItemsSource to the ObservableCollection. Every Itemtemplate DataContext is no MainViewModel, but UploadFile, which has no RemoveCommand.
I was solving this by adding to every item the parent object using constructor. RemoveCommand was inside the item's ViewModel and insede the remove function i was calling the parent's method to delete the item.
Not sure if that's the best solution but it worked for me.