0

我有一个视图模型名称为“SettingsViewModel”,在该视图模型中我正在编写按钮单击功能(bUpdate()

namespace 
{
class SettingsViewModel : Notifyable
{
    public Settings settings
    {
        get => _settings;
        set
        {
            _settings = value;
            OnPropertyChanged();
        }
    }
    private Settings _settings = Settings.Default;
         private IWindowManager _windowManager;
    public SettingsViewModel(IWindowManager windowManager)
    {
        _windowManager = windowManager;
    }
    protected override void OnClose()
    {
        base.OnClose();
        settings.Save();
    }
    CopyFilesRecursively(serverDirectorty, localDirectory){
        // DO SOMETHING
    }
    public void bUpdate()
    {     
        CopyFilesRecursively(serverDirectorty, localDirectory);
    }
}

}

我想在开始复制文件时禁用按钮单击,当复制完成后我想重新启用按钮单击。下面是按钮的XML (SettingsView.xml)

<Button Content="{x:Static p:Resources.update}" HorizontalAlignment="Right" Command=  "{s:Action bUpdate }" />

我怎样才能在Binding的帮助下做到这一点?

4

2 回答 2

1

由于您需要 MVVM 方法,理想的方法是将 View/UserControl 的 DataContext 设置为 ViewModel 的实例(如果您想在评论中进一步说明操作方法,请告诉我,我会解释),然后绑定到属性,它是 ICommand 实现的一个实例,如下所示:-

查看/用户控制:

    <Button Content="{x:Static p:Resources.update}" 
            HorizontalAlignment="Right" 
            Command="{Binding Update}" />

视图模型:

    public ICommand Update => new RelayCommand(HandleUpdate, CanUpdate);

    private bool _isRunning = false;
      
    private void HandleUpdate()
    {
        _isRunning = true;
        CommandManager.InvalidateRequerySuggested();
                    
        Task.Run(() =>
        {
            // Update Button click logic goes here
            CopyFilesRecursively(serverDirectorty, localDirectory);

            Application.Current.Dispatcher.Invoke(() =>
            {
                _isRunning = false;
                CommandManager.InvalidateRequerySuggested();
            });                
        });       
    }
    private bool CanUpdate()
    {
        return !_isRunning;
    }

_isRunning 标志只维护当前运行状态信息,CommandManager 上的 InvalidateRequerySuggested 调用强制 View 强制 ICommand 上的 CanExecuteChanged 事件。

Task.Run 可确保您的长时间运行的进程不会阻塞 UI 线程,并且当前调度程序调用可防止非 UI 线程操作可能导致问题的 Xaml 元素。

这是 ICommand 接口的无参数实现:

public class RelayCommand : ICommand
{
    readonly Func<Boolean> _canexecute;
    readonly Action _execute;

    public RelayCommand(Action execute)
        : this(execute, null)
    {
    }

    public RelayCommand(Action execute, Func<Boolean> canexecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");
        _execute = execute;
        _canexecute = canexecute;
    }

    public event EventHandler CanExecuteChanged
    {
        add
        {
            if (_canexecute != null)
                CommandManager.RequerySuggested += value;
        }
        remove
        {
            if (_canexecute != null)
                CommandManager.RequerySuggested -= value;
        }
    }

    public Boolean CanExecute(Object parameter)
    {
        return _canexecute == null ? true : _canexecute();
    }

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

您可以重构布尔标志并优化您的方式,但这就是我们通常将视图模型逻辑与视图代码分离的方式!

PS:还有其他方法可以通过命令绑定传递命令参数,您可以在需要时查看,或者我可以在评论中澄清。

此外,当前运行的任务中没有异常处理,请进一步考虑聚合异常捕获。

于 2022-01-05T14:31:47.630 回答
0

好吧,我想知道您的代码示例。猜你很快就会遇到“用户界面被阻止”的问题。无论如何,您可以逐步解决。

当然,你可以通过绑定来做到这一点。请注意,您几乎可以将任何项目属性绑定到 VM 中的属性。所以为了简单起见,你可以这样做

<Button IsEnabled={Binding MyButtonIsEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Content="{x:Static p:Resources.update}" HorizontalAlignment="Right" Command=  "{s:Action bUpdate }" />

对于 VM 方面,我假设您正在使用一些 MVVM 框架 Nuget 包,和/或启用了 Fody 来处理 INotifyPropertyChanged 事件的管道。如果不是,请使用与其他 VM 属性类似的支持字段修改 MyButtonIsEnabled 属性:

public bool MyButtonIsEnabled {get; set;}
public void bUpdate()
    {
      MyButtonIsEnabled = false;   
      CopyFilesRecursively(serverDirectorty, localDirectory);
      MyButtonIsEnabled = true;
    }

到目前为止,一切都很好——但不会像预期的那样工作,因为 bUpdate 函数是一个同步函数。在工作完成之前它不会返回。因此,您的完整 UI 将不会响应,并且按钮不会获​​得时间片来禁用和重新启用。

相反,您应该使用 ICommands resp。IYourMVVMFrameworkCommand(我更喜欢 Catel)喜欢:(查看)

<Button Command="{Binding CopyMyFilesCommand}" Content="...whatever..."/>

(虚拟机)

public ICatelCommand CopyMyFilesCommand { get; private set; }

MyVieModel() // constructor
{ 
 ...
    CopyMyFilesCommand = new TaskCommand(OnCopyMyFilesCommand);
 ...
}

private async Task OnCopyMyFilesCommand()
{
    await Task.Run(bUpdate).ConfigureAwait(false);
}

使用 Catel,TaskCommand 构造函数采用第二个委托参数来决定是否可以执行 ICommand。接线为

CopyMyFilesCommand = new TaskCommand(OnCopyMyFilesCommand, () => MyButtonIsEnabled);

将禁用该命令,该命令反过来禁用按钮,而无需绑定 IsEnabled 属性。

于 2022-01-05T14:44:15.237 回答