MVVM,我是否必须将每个命令保留在自己的类中? [英] MVVM, do I have to keep each command in own class?

查看:19
本文介绍了MVVM,我是否必须将每个命令保留在自己的类中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在努力适应 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.

  1. 问:我看到他们在使用 RelayCommand、DelegateCommand 或 SimpleCommand.像这样:

  1. 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.

  1. 问:我也会发布整个项目,我想知道我是否做错了什么或者我应该改进什么.

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);
    }
}

有两个重载构造函数,一个只接受要执行的方法,另一个接受方法和CanExecutePredicate.

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)

这与ICommandExecute 方法的签名相匹配.参数类型是 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 实现

以下内容应该为如何实现这一点提供了一个很好的模型(我发明了两个属性,Property1Property2,来表示您的 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上的CanExecuteChangedEventHandler总是null,所以事件永远不会触发.如果 CanExecutefalse 开头,按钮将始终被禁用 - 如果以 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆