【问题标题】:Why would a WPF Button not always fire its bound Command?为什么 WPF 按钮不总是触发其绑定的命令?
【发布时间】:2017-10-26 14:55:41
【问题描述】:

我有一个用户控件,它是使用 Prism 组成的应用程序的一部分。它有这个“返回”按钮:

<Button Content="Back"
        Command="{Binding BackCommand}"
        Padding="5 12 5 2" />

有时此按钮不执行命令。视图模型提供了这个命令:

public class BackCommand : ICommand
{
    private readonly IInterviewController _controller;

    public BackCommand(IInterviewController controller)
    {
        _controller = controller;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        _controller.MovePreviousTopic();
    }

    public event EventHandler CanExecuteChanged;
}

此外,用户控件还有一个“下一步”按钮,可根据用户在第二个控件中选择的内容,让用户通过一系列分支问题。后退按钮应该将用户带到上一个问题。后退按钮通常执行绑定命令,所以我知道绑定有效。有时用户必须在命令触发之前单击两次后退按钮。我将 CanExecute 主体替换为始终返回 true(如上所示),但按钮仍然停止工作。

我在按钮上放置了临时事件处理程序,发现当命令执行失败时:

  • 按钮已启用
  • 绑定仍然设置
  • 按钮上的 PreviewMouseDown 事件触发
  • Click 事件不会触发(即使我删除了命令)
  • 未调用命令的 Execute 方法。

我发现关于 SO 的每个问题都与未触发的命令有关,OP 遇到了一个或另一个绑定命令的问题。由于我的命令是绑定的,所以我没有发现太多用处。

编辑#2:我发现了问题的原因。组成应用程序的组件之一是尝试控制焦点。要重现此问题,请将其粘贴到窗口中:

<StackPanel>
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="Current count:"/>
        <TextBlock Text="{Binding Counter}"/>
    </StackPanel>
    <CheckBox x:Name="MyCheckbox" IsChecked="{Binding MaintainFocus}" LostFocus="MaintainFocus" >Maintain Focus</CheckBox>
    <Button Command="{Binding MyCommand}" Content="Test" Width="100" />
</StackPanel>

将其粘贴到窗口后面的代码中: 公共部分类 MainWindow : 窗口 { 私有只读 ViewModel _vm;

    public MainWindow()
    {
        DataContext = _vm = new ViewModel();
        InitializeComponent();
    }

    private void MaintainFocus(object sender, RoutedEventArgs e)
    {
        if (_vm.MaintainFocus)
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input,
                new Action(() => MyCheckbox.Focus()));
    }
}

public class ViewModel : INotifyPropertyChanged
{
    public ViewModel()
    {
        MyCommand = new TestCommand(this);
    }

    public ICommand MyCommand { get; set; }

    private int _counter;

    public int Counter
    {
        get { return _counter; }
        set
        {
            _counter = value;
            OnPropertyChanged();
        }
    }

    public bool MaintainFocus { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

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

public class TestCommand : ICommand
{
    private readonly ViewModel _viewModel;

    public TestCommand(ViewModel viewModel)
    {
        _viewModel = viewModel;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        _viewModel.Counter++;
    }

    public event EventHandler CanExecuteChanged;
}

现在,如果您选中“保持焦点”复选框,则不会触发“测试”按钮。

【问题讨论】:

  • 也许当您的按钮没有触发 Click 事件时,您点击了按钮内的空白区域?尝试在按钮的 ControlTemplate 内的边框上设置Background="#00000000"。我相信它会解决问题。

标签: c# wpf wpf-controls prism


【解决方案1】:

我的问题的解决方法是不允许按钮获得焦点:

<Button Command="{Binding MyCommand}"
        Focusable="False"
        Content="Test"
        Width="100" />

如果按钮在命令执行之前失去焦点,那么 WPF 的 ICommand 似乎是一个问题,但如果按钮从一开始就没有获得焦点,则不是。

我很幸运,这些按钮的功能已经通过控件上的选项卡导航提供,因此我不需要重构代码设置焦点。由于 UI 设计师不想在按钮上显示键盘焦点,因此在我的书中这是双赢的。

我最初遇到的一个问题是如何调试这个问题。我从来没有得到一个合适的答案,所以我将在下面总结我的过程:

  1. 不要使用断点 - 这些会干扰调试鼠标交互。
  2. 将Debug.WriteLine("&lt;something meaningful&gt;"); 添加到由于鼠标单击而调用的每个事件/方法。这告诉我哪些事件有效,哪些无效以及顺序。它并没有真正帮助。
  3. 因为这是重新发送更改的结果,所以我将最近的更改逐一注释掉,直到我确定导致问题的代码。这最终是对导致问题的组件的调用。
  4. 一旦我在我的更改中发现有问题的代码行,我就会取消提交该行并逐步执行该调用。这导致我使用 Dispatcher 进行代码设置焦点。
  5. 这行非常可疑,因此我构建了一个示例项目来模仿代码并验证问题的根源就在这里。

也许这会对将来的某人有所帮助。

【讨论】:

  • 嗨,我来自未来,这很有帮助。我在两个数据网格中有一个按钮,并且命令绑定没有触发,除非我单击了两次按钮或者它已经有了焦点。添加 Focusable=False 解决了这个问题。
【解决方案2】:

单击Image 应引发Click 事件。但是,如果您设置Padding 属性并在实际Image 之外单击,则不会引发Click 事件。

您可以通过将 Border 元素放在带有 Transparent 背景的 Grid 中来解决此问题:

<Style TargetType="Button">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid Background="Transparent">
                    <Border BorderThickness="0"
                            Background="Transparent"
                            Margin="{TemplateBinding Control.Padding}">
                        <Image Name="Image"
                               Source="{Binding NormalImage, RelativeSource={RelativeSource TemplatedParent}}" />
                    </Border>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsPressed"
                                 Value="True">
                        <Setter TargetName="Image"
                                Property="Source"
                                Value="{Binding PressedImage, RelativeSource={RelativeSource TemplatedParent}}" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

但请注意,当您提出问题时,您应该始终提供Minimal, Complete, and Verifiable example 您的问题。

【讨论】:

  • 不幸的是,设置背景对按钮的行为没有影响。即使命令可以执行,在特定情况下仍然无法执行命令
  • 我猜你应该再看一遍我回答中的最后一句话。
  • 我仍在努力隔离导致问题的代码。目前,重现此应用程序所需的最少代码是整个应用程序,我无法将其发布在 Internet 上。这就是问题的原因:“有谁知道为什么按钮不会响应被点击,或者如何解决这个问题?”在原始帖子的末尾
  • 最小可重现解决方案已经发布了一段时间
  • “组成应用程序的组件之一正在尝试控制焦点”。那么为什么不删除这个组件呢?这显然不起作用,对吧?您发布的示例代码没有多大意义。一旦 Checkbox 失去焦点,您就会再次聚焦它,这当然会阻止单击的 Button 获得焦点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-16
  • 2013-05-29
  • 1970-01-01
  • 2015-06-07
  • 1970-01-01
相关资源
最近更新 更多