如何使用ICommand轻松管理UpdateCanExecute / CanExecute for Multiplex模型

时间:2017-02-25 04:19:51

标签: c# xaml uwp

当使用ICommand接口并通过Command和CommandParameters(或只是命令)绑定到控件时,通常意识到当值为not时,按钮/控件不会自动变为启用/禁用(调用CanExecute)使用过的更改。

1 个答案:

答案 0 :(得分:0)

只是想让任何人感兴趣,这里有一个小仓库,迫使ICommand通过RelayCommand始终根据需要进行更新。这在视图中不需要额外的工作,这使得它成为实用的IMO。

Auto Relay Command Example

relay命令只是一个如何轻松自动化ICommand接口的例子,并不是解决所有解决方案的问题。

这是RelayCommand

public class RelayCommand : ICommand
{
    private readonly RelayCommandBindings relayCommandBindings;
    public event EventHandler CanExecuteChanged;

    internal RelayCommand(RelayCommandBindings relayCommandBindings)
    {
        this.relayCommandBindings = relayCommandBindings;

        relayCommandBindings.BindingModel.PropertyChanged += (s, e) =>
        {
            if (relayCommandBindings.BindingProperties.Any(p => p == e.PropertyName))
                CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        };
    }

    public bool CanExecute(object parameter) => (relayCommandBindings.CanExecuteChecks?.All(p => p.Invoke(parameter))).GetValueOrDefault();
    public void Execute(object parameter) => relayCommandBindings?.Execute?.Invoke(parameter);
}

这是RelayCommandBindings

internal class RelayCommandBindings
{
    public Action<object> Execute { get; set; }
    public IEnumerable<Predicate<object>> CanExecuteChecks { get; set; }
    public INotifyPropertyChanged BindingModel { get; set; }
    public IEnumerable<string> BindingProperties { get; set; }
}

这是ViewModel

public class MultiplexViewModel : INotifyPropertyChanged
{
    private const string NoName = "(no name)";
    private bool isActive;
    private bool isActiveChanging;
    private string name;
    private readonly MultiPlexModel multiPlexModel;
    private readonly SynchronizationContext synchronizationContext;

    public MultiplexViewModel()
    {
        multiPlexModel = new MultiPlexModel();
        synchronizationContext = SynchronizationContext.Current;

        var bindingProperties = new[]
        {
            nameof(IsActive),
            nameof(IsActiveChanging)
        };

        var setNewNameBindings = new RelayCommandBindings()
        {
            Execute = async (obj) => await multiPlexModel.StartMultiplexModelAsync(obj.ToString()),
            CanExecuteChecks = new List<Predicate<object>>
            {
                (obj) => IsValidName(obj?.ToString()),
                (obj) => IsActive == false,
                (obj) => IsActiveChanging == false
            },
            BindingModel = this,
            BindingProperties = bindingProperties
        };

        var stopMultiplexBindings = new RelayCommandBindings()
        {
            Execute = async (obj) => await multiPlexModel.StopMultiplexModelAsync(),
            CanExecuteChecks = new List<Predicate<object>>
            {
                (obj) => IsActive == true,
                (obj) => IsActiveChanging == false
            },
            BindingModel = this,
            BindingProperties = bindingProperties
        };

        SetNewNameCommand = new RelayCommand(setNewNameBindings);
        StopMultiplexCommand = new RelayCommand(stopMultiplexBindings);

        multiPlexModel.PropertyChanged += (s, e) => GetType().GetProperties().Where(p => p.Name == e.PropertyName).FirstOrDefault()?.SetValue(this, multiPlexModel.GetType().GetProperty(e.PropertyName).GetValue(multiPlexModel));
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void Notify([CallerMemberName] string propertyName = "") => synchronizationContext.Post((o) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)), null);

    public bool IsActive
    {
        get { return isActive; }
        private set
        {
            isActive = value;
            Notify();
        }
    }

    public bool IsActiveChanging
    {
        get { return isActiveChanging; }
        private set
        {
            isActiveChanging = value;
            Notify();
        }
    }

    public string Name
    {
        get { return string.IsNullOrEmpty(name) ? NoName : name; }
        private set
        {
            name = value;
            Notify();
        }
    }

    private bool IsValidName(string name) => (name?.StartsWith("@")).GetValueOrDefault();
    public RelayCommand SetNewNameCommand { get; private set; }
    public RelayCommand StopMultiplexCommand { get; private set; }
}

这是模型

public class MultiPlexModel : INotifyPropertyChanged
{
    private bool isActive;
    private bool isActiveChanging;
    private string name;

    public event PropertyChangedEventHandler PropertyChanged;
    public void Notify([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    public bool IsActive
    {
        get { return isActive; }
        private set
        {
            isActive = value;
            Notify();
        }
    }

    public bool IsActiveChanging
    {
        get { return isActiveChanging; }
        private set
        {
            isActiveChanging = value;
            Notify();
        }
    }

    public string Name
    {
        get { return name; }
        private set
        {
            name = value;
            Notify();
        }
    }

    public async Task StartMultiplexModelAsync(string newName)
    {
        await Task.Run(async () =>
        {
            if (IsActiveChanging)
                return;

            IsActiveChanging = true;
            await Task.Delay(2000);

            Name = newName;
            IsActive = true;
            IsActiveChanging = false;
        });
    }

    public async Task StopMultiplexModelAsync()
    {
        await Task.Run(async () =>
        {
            if (IsActiveChanging)
                return;

            IsActiveChanging = true;
            await Task.Delay(2000);

            Name = string.Empty;
            IsActive = false;
            IsActiveChanging = false;
        });
    }

}

这是视图

<UserControl.DataContext>
    <ViewModels:MultiplexViewModel />
</UserControl.DataContext>

<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <TextBlock Text="Auto Relay Command Example"
               Margin="12, 40, 12, 12"
               FontSize="32"
               TextWrapping="Wrap" />

    <TextBlock Text="The purpose of this application is to demonstrate a method for automatically activating CanExecute in ICommand.  This automatically triggers buttons to become enabled / disabled based on various binding properties at once."
               Margin="12"
               TextWrapping="Wrap" />

    <TextBlock Text="The name of the multiplex can only be set once if it starts with the @ symbol and the multiplex is not active."
               Margin="12"
               TextWrapping="Wrap" />

    <TextBlock Text="The multiplex is started when the name is being properly set.  At that time the multiplex cannot be altered... once it is set it cannot be reset until it has been stopped."
               Margin="12"
               TextWrapping="Wrap" />

    <TextBlock Text="There is no code behind, triggers, converters, or other tricks used to make this work.  This is purely binding to the commands in the ViewModel.  The magic is in the RelayCommand and RelayCommandBindings in the ViewModels namespace."
               Margin="12"
               TextWrapping="Wrap" />

    <TextBox Name="TextBoxName"
             Text="{Binding Name, Mode=OneWay}"
             Margin="12" />

    <Button Content="Set New Name"
            Margin="12"
            Command="{Binding SetNewNameCommand}"
            CommandParameter="{Binding Text, ElementName=TextBoxName}" />

    <Button Content="Stop Multiplex"
            Margin="12"
            Command="{Binding StopMultiplexCommand}" />

    <StackPanel Margin="12">
        <TextBlock Text="Multiplex Changing" />
        <TextBlock Text="{Binding IsActiveChanging}" />
    </StackPanel>

    <StackPanel Margin="12">
        <TextBlock Text="Multiplex Active" />
        <TextBlock Text="{Binding IsActive}" />
    </StackPanel>

    <StackPanel Margin="12">
        <TextBlock Text="Multiplex Name" />
        <TextBlock Text="{Binding Name}" />
    </StackPanel>

</StackPanel>

图片---------

Start

Name Edit

此处启用了设置新名称的按钮...

Setting Name

此处按钮在激活时再次被禁用...

Name Set

此处设置名称并启用“停止”按钮...

Stopping Multiplex

此处,一旦停止,视图就会重新开始。

Stopped

在这个例子中,您可以看到各种属性设置视图的色调,并在视图中以直观的方式绑定,IMO。只是觉得这会有所帮助......