【问题标题】:How to trigger button command whenever my value from combobox is selected无论何时选择ComboBox的值,如何触发按钮命令
【发布时间】:2021-12-25 22:44:56
【问题描述】:

我有一个组合框和一个按钮。我希望每当我从中选择一项时,该按钮都不会被禁用

XAML:

<Border Style="{StaticResource borderMain}"
                Grid.Row="7"
                Grid.Column="0">
            <ComboBox   ItemsSource="{Binding Source={StaticResource portNames}}"
                        SelectedItem="{Binding SelectedPort, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                        x:Name="Port_Selector" Grid.Column="0"
                        Text="Port Selector" Background="White"/>
        </Border>

        <Border Style="{StaticResource borderMain}"
                Grid.Column="1"
                Grid.Row="7">
            <Button Content="Connect"
                    Command="{Binding OpenPortCommand}"
                    CommandParameter="{Binding SelectedPort}"
                    Style="{StaticResource buttonMain}"
                    Margin="5"/>
        </Border>

命令:

public class OpenPortCommand : ICommand
    {
        public OpenPortVM OpenPortVM{ get; set; }

        public OpenPortCommand(OpenPortVM OpenPortVM)
        {
            this.OpenPortVM = OpenPortVM;
        }

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

        public bool CanExecute(object? parameter)
        {
            string? portCom = parameter as string;

            if (!string.IsNullOrEmpty(portCom))
                return true;
            return false;
        }

        public void Execute(object? parameter)
        {
            OpenPortVM.ConnectPort();
        }
    }

我已经对其进行了调试,并检查了我用于绑定SelectedPort 的变量的值,它上面有一个值,但不知何故,我的按钮的CommandParameter 未检测到,因此CanExecute 方法运行不正常。我错过了什么吗?

更新

预期:

结果:

更新 2

Setter 上的断点:

CanExecute 上的断点

【问题讨论】:

    标签: c# wpf xaml


    【解决方案1】:

    您的代码有两个问题:

    1. 实际上阻止您的代码正确执行的最关键的一个是您引发INotifyPropertyChanged.PropertyChanged 事件的方式。您当前不是传入属性名称,而是传入支持字段的名称。

    代替

    OnPropertyChanged(nameof(portSelected));
    

    应该是

    OnPropertyChanged(nameof(SelectedPort));
    

    为简化事件调用,您的调用者应使用CallerMemberName 属性:

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
      => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName);
    

    然后不带任何参数调用方法OnPropertyChanged,因为现在会自动检测到属性名称:

    OnPropertyChanged();
    
    1. 方法RaiseExecuteChangedCommand 是多余的。您对CanExecuteChanged 事件的ICommand 实现将事件处理程序委托给CommandManager.RequerySuggested 事件(这是正确的)。 CommandManager.RequerySuggested 事件由 WPF 框架自动引发,并且由于事件委托,框架还将引发 ICommand.CanExecuteChanged(因为您使用 CommandManager.RequerySuggested += value 将所有 ICommand.CanExecuteChanged 处理程序附加到 CommandManager.RequerySuggested 事件)。如果您不希望框架为您引发 CanExecuteChanged 事件,则必须删除 CommandManager.RequerySuggested += value 部分,即不要将客户端处理程序附加到 CommandManager.RequerySuggested 事件。因为现在您将从RaiseExecuteChangedCommand 方法中明确提出它。所以你不能两者兼得。要么明确提出 ICommand.CanExecuteChanged,要么让 CommandManager 为你做这件事。

    【讨论】:

    • 哇,这是一个很好的解释,我会把它当作我的个人笔记。你也对,我在onPropertyChanged 上犯了一个错误,应该是SelectedPort 而不是portSelected。谢谢那里
    • 关于 2) 的旁注:我建议不要使用 CommandManager.RequerySuggested,因为它是一个黑盒,它的工作原理。它可能会导致一些意外行为,并且不必要地多次调用命令的 CanExecute 方法(这也可能很昂贵,具体取决于 CanExecute 方法实际执行的操作)。参见例如How does CommandManager.RequerySuggested work?。我个人喜欢确定性的方式,仅在确实需要时才引发 CanExecuteChange 事件。
    • @Steeeve CommandManager 明显增加了应用程序的便利性。它剥离了处理命令状态的完整逻辑。特别是在 MVVM 场景中,您不希望视图模型参与视图逻辑。 CanExecuteChanged 事件仅用于视图(ICommandSource 实现)以控制其状态。例如,它允许按钮自行禁用。视图模型不关心这种状态。
    • @Steeeve 将逻辑卸载到 CommandManager 是有意义的,这样视图模型就不能跟踪影响此状态的条件。在当今的机器上,性能影响可以忽略不计。通常您不会在委托中进行昂贵的计算。还有更多关键的 UI 相关优化需要考虑。从来没有发生过我们必须更改指挥基础设施来修复与 UI 相关的性能问题。无需避开 CommandManager 为您处理命令状态。
    • @BionicCode 我仍然不相信 :) 我不得不调试一些,我们称之为“次优”ViewModel,文本框中的每次击键都会导致延迟和 CPU 使用率偷看,使流畅打字不可能。它是由 CommandManager 调用的大量命令在 CanExecute 中进行一些昂贵的工作引起的。但正如我所写,这只是我的意见,只是在使用 CommandManager 时要牢记的一个提示。
    【解决方案2】:

    您的OpenPortCommand 缺少实际引发CanExecuteChanged 事件的可能性。我建议如下:

    public class OpenPortCommand : ICommand
    {
        public OpenPortVM OpenPortVM{ get; set; }
    
        public OpenPortCommand(OpenPortVM OpenPortVM)
        {
            this.OpenPortVM = OpenPortVM;
        }
    
        // Leave this event as is, don't do an explicit implementation        
        public event EventHandler? CanExecuteChanged;
    
        public void RaiseCanExecuteChange() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        
        public bool CanExecute(object? parameter) => !string.IsNullOrEmpty(parameter as string);
    
        // rest ommitted
    }
    

    在 ViewModel 的 SelectedPort 属性中:

    public string SelectedPort
    {
        get => _selectedPort;
        set
        {
            _selectedPort = value;
            // call your OnPropertyChanged( ... );
            OpenPortCommand?.RaiseCanExecuteChanged();
        }
    }
    

    在 XAML 中,我简化了 ComboBox.SelectedItem 属性的绑定,如下所示:

    <ComboBox ... SelectedItem="{Binding SelectedPort}"/>
    

    【讨论】:

    • 当你说“rest ommited”时,为什么我需要删除它?我的意思是,我需要调用OpenPortVM.ConnectPort(); 方法来执行另一个函数
    • 我已经省略了其余部分以保持代码简短,您当然需要其余部分。
    • 啊,我明白了,我以为你被删除了其余部分。无论如何,我尝试了你的代码,但我仍然对CanExecuteChange 感到困惑,该代码是做什么的?因为它在当前上下文中不存在
    • 打错了,应该是 CanExecuteChanged(事件的名称)。我会在答案中纠正它。
    • 嘿,它仍然没有改变按钮状态。即使我已经从组合框中选择了项目,该按钮仍然禁用
    猜你喜欢
    • 2015-10-17
    • 1970-01-01
    • 2021-09-17
    • 2021-03-04
    • 2012-10-16
    • 2020-01-13
    • 1970-01-01
    • 2012-12-16
    • 1970-01-01
    相关资源
    最近更新 更多