0
votes

I have a user control which is part of an app that is composed using Prism. It has this 'Back' Button:

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

Sometimes this button doesn't execute the command. The view model provides this command:

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;
}

In addition the user control has a 'Next' button that moves the user though the a series of branching questions based on what the user selects in a second control. The back button is supposed to take the user to the previous question. The back button usually executes the bound command so I know the binding works. Sometimes the user has to click the back button twice before the command will fire. I replaced the CanExecute body to always return true (as shown above) still the button stops working.

I put temporary event handlers on the button and discovered that when the command fails to execute:

  • the button is enabled
  • the binding is still set
  • the PreviewMouseDown event on the button fires
  • the Click event does not fire (even if I remove the command)
  • the command's Execute method is not called.

Every question on SO I'm finding about commands not firing, the OP was having one issue or another binding the command. Since my command is bound, I'm not finding much useful.

Edit #2: I have discovered the cause of the issue. One of the components composed into the application is trying to control focus. To reproduce the issue, paste this into a window:

<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>

Paste this into the window's code behind: public partial class MainWindow : Window { private readonly 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;
}

Now, if you check the Maintain Focus checkbox, the Test button will not fire.

2
Maybe when your button doesn't fire Click event you click on the empty space inside the button? Try set Background="#00000000" on the border inside button's ControlTemplate. I'm sure it will solve the problem.Maxim

2 Answers

3
votes

The fix for my issue is to not allow the Button to take focus:

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

It seems to be a problem for WPF's ICommand if the button looses focus before the command executes but not if the button never gets focus in the first place.

I was fortunate that the functionality for these buttons has been provided though Tab Navigation on the control so I didn't need to refactor the code setting focus. Since the UI designer doesn't want to show keyboard focus on the button, this is a Win-Win in my book.

One of the initial questions I had was how to debug this problem. I never got a suitable answer for this, so I'll summarize my process below:

  1. Don't use break points - these interfere with debugging mouse interactions.
  2. Add Debug.WriteLine("<something meaningful>"); to each event/method that is invoked as a result of the mouse click. This told me what what events were working, which ones weren't and the order. It didn't really help.
  3. Because this was a result of resent changes, I commented out the recent changes, 1 by 1, until I identified the code causing the issue. This ended up being a call into the component causing the issue.
  4. Once I identified the problematic line of code in my changes, I uncommitted that line and stepped through that call. This led me to the code setting focus using the Dispatcher.
  5. This line was highly suspect, so I built a sample project that mimicked the code and verified that the source of the problem was here.

Maybe this will help someone in the future.

0
votes

Clicking on the Image should raise the Click event. If you however set the Padding property and click outside of the actual Image, the Click event won't be raised.

You could fix this by putting the Border element inside a Grid with a Transparent background:

<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>

But please note that you should always provide a Minimal, Complete, and Verifiable example of your issue when you ask a question.