代码之家  ›  专栏  ›  技术社区  ›  D. VarnuÅ¡ka

MVVM,我必须在自己的类中保留每个命令吗?

  •  2
  • D. VarnuÅ¡ka  · 技术社区  · 6 年前

    1. Q: 我看到他们正在使用RelayCommand、DelegateCommand或SimpleCommand。这样地:

      public ICommand DeleteCommand => new SimpleCommand(DeleteProject);
      

    尽管我像他们一样创造了一切,但我仍然拥有这个角色 => new SimpleCommand(DeleteProject); 红色下划线。

    到目前为止,我正在通过为每个命令创建命令类来解决这个问题,但这似乎不是正确的方法。

    1. Q: 我还将张贴整个项目,我想知道我是否做了什么错误或我应该改进什么。

    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>
    

    型号:

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

    谢谢你花时间阅读,我会很感激你给我的每一个建议。

    3 回复  |  直到 6 年前
        1
  •  2
  •   A.A    6 年前

    有一些框架/库可以帮助简化命令绑定。例如,MVVMLight具有RelayCommand的通用实现,只需要创建属性并为其指定方法名即可执行。

    Here 这是如何使用Mvvmlight Relaycommand的示例。

        2
  •  1
  •   Chris Mack    6 年前

    您可以使用单个 DelegateCommand 实施而不是分开 ICommand 课程。

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

    有两个重载构造函数,一个只接受要执行的方法,另一个同时接受方法和 Predicate 对于 CanExecute .

    用法:

    public class ViewModel
    {
        public ICommand DeleteProjectCommand => new DelegateCommand(DeleteProject);
    
        private void DeleteProject(object parameter)
        {
        }
    }
    

    关于MVVM的进一步简化,实现属性更改通知功能的一种方法是通过以下方式:

    public abstract class ObservableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        internal void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

    然后在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");
                }
            }
        }
    }
    

    请注意,在筹集资金时不需要提供财产的名称 NotifyPropertyChanged 从该属性的setter内部(感谢 [CallerMemberName] ),尽管这仍然是一种选择,例如。, AnotherProperty 引发两个属性的更改通知。

    澄清

    DelegateCommand(委派命令) 适用于所有示例。传递给它的方法应具有以下签名:

    void MethodName(object parameter)
    

    这与 Execute 方法 ICommand命令 . 参数类型为 object ,因此它接受任何内容,在您的方法中,您可以将其转换为您实际传递给它的任何对象,例如:

    private void AddGallery(object parameter)
    {
        Gallery gallery = (Gallery)parameter;
    
        ...
    }
    

    如果设置否 CommandParameter 然后 null 将被传递,因此对于另一个示例,您仍然可以使用相同的签名,只是不使用参数:

    private void DeleteGallery(object parameter)
    {
        ...
    }
    

    所以你可以使用 DelegateCommand(委派命令) 对于以上所有内容。

    CanAddGallery实现

    下面应该为如何实现这一点提供一个很好的模型(我发明了两个属性, Property1 Property2 ,代表您的 TextBox 值):

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

    我发现当我使用这种方法时 CanExecuteChanged EventHandler DelegateCommand(委派命令) 总是 无效的 ,因此事件从不触发。如果 CanExecute公司 false 首先,按钮将始终被禁用-如果它是 true 首先,在命令执行与否方面,我仍然获得了准确的功能,但按钮始终处于启用状态。因此,我更喜欢上述示例中的方法,即:

    public DelegateCommand AddGalleryCommand { get; set; }
    
    public AddGalleryViewModel()
    {
        AddGalleryCommand = new DelegateCommand(AddGallery, CanAddGallery)
    
        ...
    }
    

    授权指挥专业

    以下类允许您为命令参数指定类型:

    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)
    {
        ...
    }
    

    以下内容允许您指定无参数方法:

    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();
    
        ...
    }
    
        3
  •  0
  •   D. VarnuÅ¡ka    6 年前

    好的,我尽量简化它。 我在用你的 ObservableObject DelegateCommand<T> .

    问题是 NullReferenceException 在里面 CanAddGallery 就在跑步之后。没有弹出窗口。我试图通过添加 if (parameter == null) return false . 这只会禁用按钮。我在想是否有必要禁用按钮。如果从用户的角度来看,不是更好地禁用按钮,而是在文本框下有红色文本,表示“这必须填写”(或弹出消息),当没有通过按钮单击发送参数时,会出现该文本。

    xaml:

    <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" />
        <TextBox Text="{Binding Path}" Grid.Column="0"/>
        <Button DataContext="{Binding Source={StaticResource viewModel}}"
                Command="{Binding Path=AddGalleryCommand}" 
                CommandParameter="{Binding Path=GalleryToAdd}" Content="Add"/>
    </StackPanel>
    

    型号:

    class Gallery : ObservableObject
    {
        private string _name;
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
                NotifyPropertyChanged();
            }
        }
    
    
        private string _path;
    
        public string Path
        {
            get
            {
                return _path;
            }
            set
            {
                _path = value;
                NotifyPropertyChanged();
            }
        }
    }
    

    视图模型:

    class GalleryViewModel : ObservableObject
    {
        private ObservableCollection<Gallery> _galleryList;
    
        public ObservableCollection<Gallery> GalleryList
        {
            get { return _galleryList; }
            set
            {
                _galleryList = value;
                NotifyPropertyChanged();
            }
        }
    
        private Gallery _galleryToAdd;
        public Gallery GalleryToAdd
        {
            get { return _galleryToAdd; }
            set
            {
                if (_galleryToAdd != value)
                {
                    _galleryToAdd = value;
                    NotifyPropertyChanged();
                }
            }
        }
    
        public DelegateCommand<Gallery> AddGalleryCommand { get; set; }
    
        public GalleryViewModel()
        {
            GalleryList = new ObservableCollection<Gallery>();
            AddGalleryCommand = new DelegateCommand<Gallery>(AddGallery, CanAddGallery);
            GalleryToAdd = new Gallery();
            GalleryToAdd.PropertyChanged += GalleryToAdd_PropertyChanged;
        }
    
        private void AddGallery(object parameter)
        {
            Gallery gallery = (Gallery)parameter;
    
            Gallery g = new Gallery();
            g.Name = gallery.Name;
            g.Path = gallery.Path;
            GalleryList.Add(g);
        }
    
        private bool CanAddGallery(object parameter)
        {
            Gallery gallery = (Gallery)parameter;
    
            if (string.IsNullOrEmpty(gallery.Name) || string.IsNullOrEmpty(gallery.Path))
            {
                return false;
            }
    
            return true;
        }
    
        private void GalleryToAdd_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Name" || e.PropertyName == "Path")
            {
                AddGalleryCommand.RaiseCanExecuteChanged();
            }
        }
    }