为什么WPF Button不会总是触发它的绑定命令?

时间:2017-05-25 20:27:15

标签: c# wpf wpf-controls prism

我有一个用户控件,它是使用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>

将其粘贴到后面的窗口代码中:     public partial class MainWindow:Window     {         私有只读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;
}

现在,如果您选中Maintain Focus复选框,则不会触发Test按钮。

2 个答案:

答案 0 :(得分:2)

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

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

如果按钮在执行命令之前失去焦点,那么对于WPF的ICommand来说似乎是一个问题,但是如果按钮永远不会在第一时间获得焦点则不会。

我很幸运,通过控件上的Tab Navigation提供了这些按钮的功能,因此我不需要重构代码设置焦点。由于UI设计师不希望在按钮上显示键盘焦点,因此这是我书中的双赢。

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

  1. 不要使用断点 - 这会干扰调试鼠标的交互。
  2. Debug.WriteLine("<something meaningful>");添加到因鼠标单击而调用的每个事件/方法。这告诉我什么事件在起作用,哪些事件没有和顺序。它没有真正的帮助。
  3. 因为这是重新发生的变化的结果,所以我逐一评论了最近的变化,直到我找到导致问题的代码。这最终成为导致问题的组件的调用。
  4. 一旦我在更改中发现了有问题的代码行,我就不提交该行并逐步执行该调用。这导致我使用Dispatcher进行代码设置焦点。
  5. 这条线非常可疑,所以我构建了一个模拟代码的示例项目,并验证问题的根源在这里。
  6. 也许这将有助于将来。

答案 1 :(得分:0)

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

您可以将Border元素放在Grid背景为Transparent的情况下解决此问题:

<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个问题。