2

有没有办法为自定义控件创建“仅实例”ICommand 实现,而没有“后面”的静态类?

我正在尝试更新以前创建的自定义控件。

目标之一是确保多实例能力。

如果在同一应用程序中使用了同一自定义控件的两个或多个实例,则(如预期的那样)后面使用的任何静态类都会产生干扰。

我想出了如何摆脱大多数,但在 ICommand 上遇到了麻烦。

给定自定义控件上的 GUI 项目必须仅在用户控件实例中有效的命令 - 而不是现在命令干扰所有实例(例如 CanExecute 使 GUI 项目在“本地”条件不存在的用户控件实例上处于活动状态遇见)。

4

3 回答 3

2

您可以创建命令并将其作为 ViewModel 的属性公开,然后在控件中绑定到它:

在您的视图模型中:

public ICommand MyCommand {get;set;} // construct your command and set it here

在您的控制中:

<Button Command="{Binding MyCommand}"/>

如果您没有使用 MVVM 模式,那么您应该在您的DataContext(可能在您的控件代码后面)中创建相同的字段

您还可以使用依赖属性来定义您的命令,如果您在创建用户控件后更改命令,您应该使用它。

一般来说:

为了在使用 WPF/C# 编写代码时了解您的选择,我建议您阅读有关 MVVM 模式、依赖属性、DataContext 和 Binding 的内容——您可能已经知道其中的一些内容。

于 2013-06-06T08:44:31.650 回答
1

我认为您可能会因为 CanExecute 和 Execute 方法没有将它们链接到它们应该作用的对象的参数而感到困惑。

但请记住,ICommand 接口必须由类实现,并且该类的对象可以并且应该具有字段,通常在构造函数中初始化。

例如,如果您遵循 MVVM 模式(正如 Ron.BI 已经提到的),该命令通常具有对视图模型的引用。或者,您可以使用 RelayCommand 之类的东西,并在委托或 lambda 闭包对象中捕获视图模型。

于 2013-06-06T08:53:46.157 回答
1

非常感谢您的回答和澄清!

你给了我决定性的一脚,所以我想通了。我添加了我的完整示例。

按照您的建议(Ron BI 和 Dennis),我首先想了解更多有关 ViewModel 的信息。

http://msdn.microsoft.com/en-ca/magazine/dd419663.aspx下有非静态类的例子。所以解决方案只是在我的用户控件中添加新类(正如提到的网站上所示 - 图 3 - 更改了一些名称 - 版权属于 Josh Smith joshsmithonwpf.wordpress.com):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;

namespace WpfCommandControl
{
class CommandImplementation : ICommand
{
    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields

    #region Constructors

    public CommandImplementation(Action<object> execute)
        : this(execute, null)
    {
    }

    public CommandImplementation(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion // Constructors

    #region ICommand Members

    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    #endregion // ICommand Members

}
}

然后在用户控件“窗口”中

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;

namespace WpfCommandControl
{
    public partial class CommandControl : UserControl, INotifyPropertyChanged
    {
    #region [ Private Members ]
    private bool _canActivated = false;
    private int _counter = 0;
    CommandImplementation _activateCommand;

    #endregion

    #region [ Properties ]
    public int CommandCounter
    {
        get
        {
            return _counter;
        }

        set
        {
            _counter = value;
            OnNotifyPropertyChanged("CommandCounter");
        }

    }

    public bool CanActivated
    {
        get
        {
            return _canActivated;
        }

        set
        {
            _canActivated = value;
            OnNotifyPropertyChanged("CanActivated");
        }        
    }
    #endregion

    #region [ Property_Changed_Utilities ]
    public event PropertyChangedEventHandler PropertyChanged;

    private void OnNotifyPropertyChanged(String info)
    {
        // Note: Do not forget to add interface "INotifyPropertyChanged" to your class.
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
    #endregion

    # region [ Commands ]
    public ICommand ActivateCommand
    {
        get
        {
            return _activateCommand;
        }
    }
    #endregion

    #region [ Constructor ]
    public CommandControl()
    {
        InitializeComponent();
        _activateCommand = new CommandImplementation(param => this.Activate(), param => this.CanActivated);
    }
    #endregion

    #region [ Methods ]
    void Activate()
    {
        CommandCounter++;
    }
    #endregion


}
}

最重要的部分:

命令实现为属性:

    public ICommand ActivateCommand
    {
        get
        {
            return _activateCommand;
        }
    }

所以它确保它将返回与实际实例相关的命令,该命令是在用户控件的构造函数中使用 Lambda-Expression 实例化的:

    public CommandControl()
    {
        InitializeComponent();
        _activateCommand = new CommandImplementation(param => this.Activate(), param => this.CanActivated);
    }

Lambda - 表达式开始连接到逻辑:

param => this.Activate()

对于 Activate() 函数,它将在 Command 被触发时执行

    void Activate()
    {
        CommandCounter++;
    }

param => this.CanActivated

用于为 ICommand CanExecute 属性传递本地逻辑,从而让您控制何时可以执行命令。

在我的情况下,我使用了可以绑定到 CheckBox 的属性,但你也可以用另一种方式来做......

    public bool CanActivated
    {
        get
        {
            return _canActivated;
        }

        set
        {
            _canActivated = value;
            OnNotifyPropertyChanged("CanActivated");
        }        
    }

同样如 Josh Smith joshsmithonwpf.wordpress.com 所示 - 我只是将其更改为在构造函数中实例化,而不是检查私有成员是否为 null 并在命令属性的 GET 部分中根据需要提供新实例。

其余代码只是所需的 Properties 和 OnNotifyPropertyChanged 的​​实现,如 MSDN 上所示。

XAML 很简单——仅用于概念证明。

<UserControl x:Class="WpfCommandControl.CommandControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:local="clr-namespace:WpfCommandControl"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         d:DesignHeight="300"
         d:DesignWidth="300"
         mc:Ignorable="d">
<Grid>
    <StackPanel>
        <CheckBox Content="Activate" IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=CanActivated}" />
        <Button Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
                                                                 AncestorType=UserControl},
                                  Path=ActivateCommand}"
                Content="Click me"
                IsEnabled="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
                                                                   AncestorType=UserControl},
                                    Path=CanActivated}" />
        <Label Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=CommandCounter}" IsEnabled="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=CanActivated}" />
    </StackPanel>
</Grid>

如您所见,只有一个 CheckBox - Binding 将提供按钮的启用/禁用。单击按钮会触发仅增加计数器的命令 - 通过绑定再次显示在标签上。

放在一起:

只需一个简单的 XAML 表单,其中包含四个用户控件:

<Window x:Class="CommandsTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:CommandsTest"
    xmlns:uctrl="clr-namespace:WpfCommandControl;assembly=WpfCommandControl"
    Title="MainWindow"
    Width="525"
    Height="350">

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <uctrl:CommandControl Grid.Row="0" Grid.Column="0" />

    <uctrl:CommandControl Grid.Row="0" Grid.Column="1" />

    <uctrl:CommandControl Grid.Row="1" Grid.Column="0" />

    <uctrl:CommandControl Grid.Row="1" Grid.Column="1" />

</Grid>

在每个控件上触发命令完全符合元素内部的需要。

一切都以 WPF 方式解决 - 使用命令和绑定而不与 GUI 元素进行任何直接交互,因此无需更新后面的代码即可交换 GUI。

再次感谢您向我展示了在 WPF 中实现自定义命令的另一种(实例安全)方法。

于 2013-06-07T08:17:49.003 回答