I am trying to get used to MVVM and WPF for a month. I am trying to do some basic stuff, but I am constantly running into problems. I feel like I solved most of them by searching online. But now there comes the problem with Commands.
Q: I saw that they are using RelayCommand, DelegateCommand or SimpleCommand. Like this:
public ICommand DeleteCommand => new SimpleCommand(DeleteProject);
Even though I create everything like they did, I am still having the part => new SimpleCommand(DeleteProject);
redly underlined.
So far I am working around it by creating command class for every command, but that does not feel like the right way to go.
- Q: I will also post whole project and I would like to know if I am doing anything wrong or what should I improve.
xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="380" Width="250">
<StackPanel DataContext="{Binding Source={StaticResource gallery}}" Margin="10">
<ListView DataContext="{Binding Source={StaticResource viewModel}}"
SelectedItem="{Binding SelectedGallery}"
ItemsSource="{Binding GalleryList}"
Height="150">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="100" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Path" Width="100" DisplayMemberBinding="{Binding Path}"/>
</GridView>
</ListView.View>
</ListView>
<TextBlock Text="Name" Margin="0, 10, 0, 5"/>
<TextBox Text="{Binding Name}" />
<TextBlock Text="Path" Margin="0, 10, 0, 5" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="40"/>
</Grid.ColumnDefinitions>
<TextBox Text="{Binding Path}" Grid.Column="0"/>
<Button Command="{Binding Path=ShowFolderClick, Source={StaticResource viewModel}}"
CommandParameter="{Binding}"
Content="..." Grid.Column="1" Margin="10, 0, 0, 0"/>
</Grid>
<Grid Margin="0, 10, 0, 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Command="{Binding Path=AddClick, Source={StaticResource viewModel}}"
CommandParameter="{Binding}" Content="Add" Grid.Column="0" Margin="15,0,0,0" />
<Button Command="{Binding Path=DeleteClick, Source={StaticResource viewModel}}"
Content="Delete" Grid.Column="2" Margin="0,0,15,0" />
</Grid>
</StackPanel>
Model:
class Gallery : INotifyPropertyChanged
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged("Name");
}
}
private string _path;
public string Path
{
get
{
return _path;
}
set
{
_path = value;
OnPropertyChanged("Path");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(params string[] propertyNames)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
foreach (string propertyName in propertyNames) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
handler(this, new PropertyChangedEventArgs("HasError"));
}
}
}
ModelView:
class GalleryViewModel : INotifyPropertyChanged
{
public GalleryViewModel()
{
GalleryList = new ObservableCollection<Gallery>();
this.ShowFolderClick = new ShowFolderDialog(this);
this.AddClick = new AddGalleryCommand(this);
this.DeleteClick = new DeleteGalleryCommand(this);
}
private ObservableCollection<Gallery> _galleryList;
public ObservableCollection<Gallery> GalleryList
{
get { return _galleryList; }
set {
_galleryList = value;
OnPropertyChanged("GalleryList");
}
}
private Gallery _selectedGallery;
public Gallery SelectedGallery
{
get { return _selectedGallery; }
set {
_selectedGallery = value;
OnPropertyChanged("SelectedGallery");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(params string[] propertyNames)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
foreach (string propertyName in propertyNames) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
handler(this, new PropertyChangedEventArgs("HasError"));
}
}
public AddGalleryCommand AddClick { get; set; }
public void AddGalleryClick(Gallery gallery)
{
Gallery g = new Gallery();
g.Name = gallery.Name;
g.Path = gallery.Path;
GalleryList.Add(g);
}
public DeleteGalleryCommand DeleteClick { get; set; }
public void DeleteGalleryClick()
{
if (SelectedGallery != null)
{
GalleryList.Remove(SelectedGallery);
}
}
public ShowFolderDialog ShowFolderClick { get; set; }
public void ShowFolderDialogClick(Gallery gallery)
{
System.Windows.Forms.FolderBrowserDialog browser = new System.Windows.Forms.FolderBrowserDialog();
string tempPath = "";
if (browser.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
tempPath = browser.SelectedPath; // prints path
}
gallery.Path = tempPath;
}
}
Commands:
class AddGalleryCommand : ICommand
{
public GalleryViewModel _viewModel { get; set; }
public AddGalleryCommand(GalleryViewModel ViewModel)
{
this._viewModel = ViewModel;
}
public bool CanExecute(object parameter)
{
/*if (parameter == null)
return false;*/
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
this._viewModel.AddGalleryClick(parameter as Gallery);
}
}
class DeleteGalleryCommand : ICommand
{
public GalleryViewModel _viewModel { get; set; }
public DeleteGalleryCommand(GalleryViewModel ViewModel)
{
this._viewModel = ViewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
this._viewModel.DeleteGalleryClick();
}
}
class ShowFolderDialog : ICommand
{
public GalleryViewModel _viewModel { get; set; }
public ShowFolderDialog(GalleryViewModel ViewModel)
{
this._viewModel = ViewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
this._viewModel.ShowFolderDialogClick(parameter as Gallery);
}
}
Thanks for your time reading so far, I will appreciate every advice I will get.
Ok, I have tried to simplify it as much as possible. I am using yours
ObservableObject
andDelegateCommand<T>
.Problem is
NullReferenceException
inCanAddGallery
right after run. No window pop up. I tried to solve it by addingif (parameter == null) return false
. That will just disable the button. I am thinking if it is necessary to disable button. If wouldn't it be better from the user's point of view insted of disabling button, to have red text under textbox saying "this must be filled" (or pop up message), that will appear when no parameters are send through button click.xaml:
Model:
ViewModel:
There are frameworks/library that help with simplifying command binding. For example MVVMLight has generic implementation of RelayCommand that only needs you to create property and assign method name for it execute.
Here's an example of how Mvvmlight Relaycommand is used.
You can do this by using a single
DelegateCommand
implementation rather than separateICommand
classes.There are two overloaded constructors, one accepting only the method to execute, and one accepting both the method and a
Predicate
forCanExecute
.Usage:
With regards to further simplification along the lines of MVVM, one way to implement property change notification functionality is via the following:
And then in the ViewModel:
Notice that you don't need to provide the name of the property when raising
NotifyPropertyChanged
from within that property's setter (thanks to[CallerMemberName]
), although it's still an option to do so, e.g.,AnotherProperty
raises change notifications for both properties.Clarification
DelegateCommand
will work for all of your examples. The method you pass to it should have a signature of:This matches the signature of the
Execute
method ofICommand
. The parameter type isobject
, so it accepts anything, and within your method you can cast it as whatever object you've actually passed to it, e.g.:If you set no
CommandParameter
, thennull
will be passed, so for your other example, you can still use the same signature, you just won't use the parameter:So you can use a
DelegateCommand
for all of the above.CanAddGallery Implementation
The following should provide a good model for how to implement this (I've invented two properties,
Property1
andProperty2
, to represent yourTextBox
values):A note on the following implementation:
I find that when I use this method, the
CanExecuteChanged
EventHandler
on theDelegateCommand
is alwaysnull
, and so the event never fires. IfCanExecute
isfalse
to begin with, the button will always be disabled - if it'strue
to begin with, I still get accurate functionality in terms of the command executing or not, but the button will always be enabled. Therefore, I prefer the method in the above example, i.e.:DelegateCommand Specialisations
The following class allows you to specify a type for your command parameter:
Usage:
The following allows you to specify a parameterless method:
Usage: