C#WPF非静态命令实现可能吗?

时间:2013-06-06 08:39:24

标签: c# wpf

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

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

其中一个目标是确保多实例功能。

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

我想出了如何摆脱最多,但却遇到了ICommand的麻烦。

鉴于自定义控件上的GUI项具有必须仅在用户控件实例中有效的命令 - 而不是现在命令干扰所有实例(例如,CanExecute使GUI项在UserControl实例上处于活动状态,其中“本地”条件不符合。)

3 个答案:

答案 0 :(得分:2)

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

在ViewModel中:

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

在你的控制中:

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

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

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

一般情况下:

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

答案 1 :(得分:1)

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

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

例如,如果您遵循MVVM模式(如Ron.B.I.已经提到的那样),则该命令通常具有对viewmodel的引用。或者您可以使用类似RelayCommand的东西,并在委托或lambda闭包对象中捕获viewmodel。

答案 2 :(得分:1)

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

你给了我所有的决定权,所以我明白了。我在purpouse上添加了我的例子。

根据您的建议(Ron B I&amp; 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


}
}

最重要的部分:

命令实现为Property:

    public ICommand ActivateCommand
    {
        get
        {
            return _activateCommand;
        }
    }

因此它确保它将返回与用户控件的构造函数中使用Lambda-Expression实例化的实际实例相关的Command:

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

Lambda - Expression建立与逻辑的连接:

param => this.Activate()

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

    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中还有另一种(实例安全)方式实现自定义命令。