MVVM,我是否必须将每个命令保留在自己的类中? [英] MVVM, do I have to keep each command in own class?
问题描述
我正在努力适应 MVVM 和 WPF 一个月.我正在尝试做一些基本的事情,但我经常遇到问题.我觉得我通过在线搜索解决了大部分问题.但是现在命令出现了问题.
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.
问:我看到他们在使用 RelayCommand、DelegateCommand 或 SimpleCommand.像这样:
Q: I saw that they are using RelayCommand, DelegateCommand or SimpleCommand. Like this:
public ICommand DeleteCommand => new SimpleCommand(DeleteProject);
即使我像他们一样创造了一切,我仍然拥有 =>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.
- 问:我也会发布整个项目,我想知道我是否做错了什么或者我应该改进什么.
xml:
<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>
型号:
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"));
}
}
}
模型视图:
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;
}
}
命令:
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.
推荐答案
您可以通过使用单个 DelegateCommand
实现而不是单独的 ICommand
类来完成此操作.
You can do this by using a single DelegateCommand
implementation rather than separate ICommand
classes.
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public DelegateCommand(Action<object> execute) : this(execute, null) { }
public virtual bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
有两个重载构造函数,一个只接受要执行的方法,另一个接受方法和CanExecute
的Predicate
.
There are two overloaded constructors, one accepting only the method to execute, and one accepting both the method and a Predicate
for CanExecute
.
用法:
public class ViewModel
{
public ICommand DeleteProjectCommand => new DelegateCommand(DeleteProject);
private void DeleteProject(object parameter)
{
}
}
为了进一步简化 MVVM,实现属性更改通知功能的一种方法是通过以下方式:
With regards to further simplification along the lines of MVVM, one way to implement property change notification functionality is via the following:
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
internal void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
然后在 ViewModel 中:
And then in the ViewModel:
public class ViewModel : ObservableObject
{
private object _myProperty;
public object MyProperty
{
get { return _myProperty; }
set
{
if (_myProperty != value)
{
_myProperty = value;
NotifyPropertyChanged();
}
}
}
private object _anotherProperty;
public object AnotherProperty
{
get { return _anotherProperty; }
set
{
if (_anotherProperty != value)
{
_anotherProperty = value;
NotifyPropertyChanged();
NotifyPropertyChanged("MyProperty");
}
}
}
}
请注意,当从该属性的 setter 中提升 NotifyPropertyChanged
时,您不需要提供该属性的名称(感谢 [CallerMemberName]
),尽管它仍然是这样做的一个选项,例如,AnotherProperty
会引发两个属性的更改通知.
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.
澄清
DelegateCommand
将适用于您的所有示例.你传递给它的方法应该有一个签名:
DelegateCommand
will work for all of your examples. The method you pass to it should have a signature of:
void MethodName(object parameter)
这与ICommand
的Execute
方法的签名相匹配.参数类型是 object
,所以它接受任何东西,在你的方法中你可以将它转换为你实际传递给它的任何对象,例如:
This matches the signature of the Execute
method of ICommand
. The parameter type is object
, so it accepts anything, and within your method you can cast it as whatever object you've actually passed to it, e.g.:
private void AddGallery(object parameter)
{
Gallery gallery = (Gallery)parameter;
...
}
如果你没有设置CommandParameter
,那么null
将被传递,所以对于你的另一个例子,你仍然可以使用相同的签名,你只是不会使用参数:
If you set no CommandParameter
, then null
will be passed, so for your other example, you can still use the same signature, you just won't use the parameter:
private void DeleteGallery(object parameter)
{
...
}
因此您可以对上述所有内容使用 DelegateCommand
.
So you can use a DelegateCommand
for all of the above.
CanAddGallery 实现
以下内容应该为如何实现这一点提供了一个很好的模型(我发明了两个属性,Property1
和 Property2
,来表示您的 TextBox
代码>值):
The following should provide a good model for how to implement this (I've invented two properties, Property1
and Property2
, to represent your TextBox
values):
public class Gallery : ObservableObject
{
private string _property1;
public Gallery Property1
{
get { return _property1; }
set
{
if (_property1 != value)
{
_property1 = value;
NotifyPropertyChanged();
}
}
}
private Gallery _property2;
public Gallery Property2
{
get { return _property2; }
set
{
if (_property2 != value)
{
_property2 = value;
NotifyPropertyChanged();
}
}
}
public Gallery() { }
}
public class AddGalleryViewModel : ObservableObject
{
private Gallery _galleryToAdd;
public Gallery GalleryToAdd
{
get { return _galleryToAdd; }
set
{
if (_galleryToAdd != value)
{
_galleryToAdd = value;
NotifyPropertyChanged();
}
}
}
public DelegateCommand AddGalleryCommand { get; set; }
public AddGalleryViewModel()
{
AddGalleryCommand = new DelegateCommand(AddGallery, CanAddGallery)
GalleryToAdd = new Gallery();
GalleryToAdd.PropertyChanged += GalleryToAdd_PropertyChanged
}
private void AddGallery(object parameter)
{
Gallery gallery = (Gallery)parameter;
...
}
private bool CanAddGallery(object parameter)
{
Gallery gallery = (Gallery)parameter;
if (string.IsNullOrEmpty(gallery.Property1) || string.IsNullOrEmpty(gallery.Property2))
{
return false;
}
return true;
}
private void GalleryToAdd_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Property1" || e.PropertyName == "Property2")
{
AddGalleryCommand.RaiseCanExecuteChanged();
}
}
}
关于以下实现的说明:
public DelegateCommand AddGalleryCommand => new DelegateCommand(AddGallery, CanAddGallery);
我发现当我使用这个方法时,DelegateCommand
上的CanExecuteChanged
EventHandler
总是null
,所以事件永远不会触发.如果 CanExecute
以 false
开头,按钮将始终被禁用 - 如果以 true
开头,我仍然可以获得准确的功能命令执行与否,但按钮将始终启用.因此,我更喜欢上面例子中的方法,即:
I find that when I use this method, the CanExecuteChanged
EventHandler
on the DelegateCommand
is always null
, and so the event never fires. If CanExecute
is false
to begin with, the button will always be disabled - if it's true
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.:
public DelegateCommand AddGalleryCommand { get; set; }
public AddGalleryViewModel()
{
AddGalleryCommand = new DelegateCommand(AddGallery, CanAddGallery)
...
}
DelegateCommand 专业化
以下类允许您为命令参数指定类型:
The following class allows you to specify a type for your command parameter:
public class DelegateCommand<T> : ICommand
{
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public DelegateCommand(Action<T> execute) : this(execute, null) { }
public virtual bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
用法:
public DelegateCommand<Gallery> AddGalleryCommand { get; set; }
public AddGalleryViewModel()
{
AddGalleryCommand = new DelegateCommand<Gallery>(AddGallery, CanAddGallery)
}
private void AddGallery(Gallery gallery)
{
...
}
private bool CanAddGallery(Gallery gallery)
{
...
}
以下允许您指定无参数方法:
The following allows you to specify a parameterless method:
public delegate void ParameterlessAction();
public delegate bool ParameterlessPredicate();
public class InternalDelegateCommand : ICommand
{
private readonly ParameterlessPredicate _canExecute;
private readonly ParameterlessAction _execute;
public event EventHandler CanExecuteChanged;
public InternalDelegateCommand(ParameterlessAction execute) : this(execute, null) { }
public InternalDelegateCommand(ParameterlessAction execute, ParameterlessPredicate canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute();
}
public void Execute(object parameter)
{
_execute();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
用法:
public InternalDelegateCommand CreateGalleryCommand { get; set; }
public CreateGalleryViewModel()
{
CreateGalleryCommand = new InternalDelegateCommand(CreateGallery)
}
private void CreateGallery()
{
Gallery gallery = new Gallery();
...
}
这篇关于MVVM,我是否必须将每个命令保留在自己的类中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!