无论何时选择ComboBox的值,如何触发按钮命令

Posted

技术标签:

【中文标题】无论何时选择ComboBox的值,如何触发按钮命令【英文标题】:How to trigger button command whenever my value from combobox is selected 【发布时间】: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 上的断点

【问题讨论】:

【参考方案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();
    方法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(事件的名称)。我会在答案中纠正它。 嘿,它仍然没有改变按钮状态。即使我已经从组合框中选择了项目,该按钮仍然禁用

以上是关于无论何时选择ComboBox的值,如何触发按钮命令的主要内容,如果未能解决你的问题,请参考以下文章

用于ComboBox项目选择的事件处理程序(选定项目未必更改)

winform combobox自动选择问题。

触发单击单选按钮并记住页面刷新时的选择

c#如何获取comboBox当前选中的值

easyui combobox 用代码赋值不触发change事件,选择值会触发

检测何时在 Winforms 上单击 ComboBox