From a production application, we notice that our WPF buttons fire the ICommand.Execute method twice on fast double click.
Now, on every Command, the application is covered with a full-screen spinner animation, preventing any further interaction with the application.
This github repo contains a minimalistic repro of the issue. Note that:
when the Button's Command fires, the "IsBusy" flag is set to true
as a consequence, the BusyIndicator overlay will be shown
as a consequence, the Button cannot be pressed again until after 300ms
However, especially on slow computers, when fast double-clicking (really fast, like gaming fast that is), it is possible to fire the command twice without the BusyIndicator blocking the second call (this can be seen if the output shows 2 'click' lines right after one another).
This is unexpected behavior to me, as the IsBusy flag is set to true right away on the UI thread.
How come a second click is able to pass through?
I would expect the IsBusy Binding to show the overlay on the UI thread, blocking any further interaction?
The github sample also contains 2 workarounds:
using the ICommand.CanExecute to block the Execute handler
using the PreviewMouseDown to prevent double clicks
I'm trying to understand what the issue is.
What work-around would you prefer?
Diagnosis
This is only my guess and not a solid and confirmed info, but it seems that when you click the mouse button, the hit-testing is done immediately, but all the mouse related events are only scheduled to be raised (using the Dispatcher I presume). The important thing is that the control that is clicked is determined at the time the click occurred, and not after the previous click has been completely handled (including all UI changes that potentially follow).
So in your case, even if the first click results in showing the BusyIndicator covering (and thus blocking) the Button, if you manage to click for the second time before the BusyIndicator is actually shown (and that does not happen immediately), the click event on the Button will be scheduled to be raised (which will happen after the BusyIndicator is shown), causing the command to be executed again even though at that point the BusyIndicator will possibly be blocking the Button.
Solution
If your goal is to prevent command execution while the previous one is still executing the obvious choice is to make the Command.CanExecute result depend on the state of the IsBusy flag. Moreover, I wouldn't even call it a workaround, but a proper design.
What you're facing here is a clear-cut example of why you shouldn't make your business logic rely on UI. Firstly, because rendering strongly depends on the machine's processing power, and secondly because covering a button with another control by far does not guarantee the button cannot be "clicked" (using for example UI Automation framework).
Related
I have an "odd" situation.
I've got a form with a binding source and a binding navigator.
In this instance, I've got 161 records (via EF6) to display.
The databinding to the controls works nicely.
But what I find is that the expected events for the binding navigator don't happen consistently. Then they settle down.
I've got event handlers (additional to the default ones, but the same thing happens when I remove the default ones as well)
I set the binding source one the navigator, and the "Position Changed" event is raised (as I'd expect)
Clicking on any of the "Move" buttons, or editing the position field will result in:
No event being raised (not the item click events, not the binding source Position Changed) roughly 3 out of 4 times.
Then the event raises, all the expected navigation occurs, and repeat.
But it doesn't seem to be permanent, because after a while almost all the records have the navigation all starts working properly.
This happens with and without the debugger connected.
The other thing I notice is that when it fails, the icon in the taskbar flashes once.
It's not something in any of my handler code, because it never gets to my code.
It might be a property setting.
It's not an exception, because even with "break on all exceptions", no exception is reported.
When you talk about "move" buttons, I take it that you mean the next/previous record navigation buttons on the BindingNavigator. Those buttons are not full-fledged Windows controls, but rather they are "lightweight" controls. I've seen issues in the past because of this.
While I do not have all the details fresh in memory, it had to do with the fact that they don't steal the focus away from other controls as regular Windows controls do, and this caused some events to not be raised.
I suggest you create your own navigation buttons, which is what I ended up doing on all my Windows Forms project. Those regular buttons can then call the BindingSource methods such as MoveNext and so on.
The title sounds like some easy well-known problem, but please check out this:
I've got some WPF window that handles a list of Threads and shows ProgressBar for each Thread. The invoked GUI refresh is working for most threads showing right number of percent and also for unknown progress the IsIndeterminate state is animated. After the thread is done some animation fades out the ProgressBar.
However, for some of my heavy tasks the ProgressBar is not visually updated. For example, the IsIndeterminate state refreshes only if I force the GUI to redraw by some mouseover events or by moving the form. The fade out animation shows the same problem.
I think this is no usual stucking because the UI thread is not blocked by further operations (there's only one invoke setting IsIndeterminate = true). Until now, I wasn't able to find out what's the different between heavy tasks that result in this behavoir and tasks that do not.
Please notice that the replacement of the Thread by a BackgroundWorker shows exactly the same problem (I'm also not sure why people say BackgroundWorker will not freeze the GUI but Threads allegedly do - this seems not entirely right). Please also notice that although the thread is paused by Thread.Suspend() (yes, I know it's obsolete but this is an essential feature I need, since the task content is unknown) the GUI will not show up the IsIndeterminate animation if no other GUI element forces the refresh of the window.
Any ideas what that problem might be and how to fix it? I just need the usual refreshing rate for drawing animated controls...
I've seen I have a problem with several users that use to double-click in buttons.
I have several buttons bound to commands that launch many actions.
For example there are two windows that communicate between them through a mediator so when I click "close the other window", the bound command sends a "CloseTheOtherWindowMessage". The problem is that when a user makes double click it tries to close the window a second time and, as expected, it crashes.
I've tried to set the window BusyIndicator as IsBusy when I press the button but my finger is quicker than MVVM and it still let me double-click before it starts showing the BusyIndicator.
I've found many examples of how to only admit double click in MVVM using interaction.Behaviors but I want just the opposite. Is there any example or other good and general solution for this problem?
Why is it "as expected" when it crashes? A crash should never be "as expected".
Your finger shouldn't be "quicker than MVVM". The Dispatcher thread always acts deterministically and sequentially. Do you use a multi-threaded approach?
In the command's Execute method or handler, raise its CanExecuteChanged event, and the binding engine will immediately call CanExecute(...). Make it so that this method will return false the second time. Maybe use a timer, or, better yet, you can logically determine by your view model state alone that the action is not possible right now (i.e. because IsOtherStuffAvailable is currently false).
We have the requirement that on some undeterministicly fired hardware events, depending on the event, the GUI changes. Due to the GUI cmnplexity this may take a few seconds.
Now the problem. If I click on a button while GUI is still busy sometimes the wrong button is invoked because after GUI layouting this button is now at the mouse coordinates where the click occured before.
Iam sure with Windows Forms the behaviour was different. Can I get around this behaviour?
You can :
Hide or cover the UI when it is refreshing (with a Busy Indicator for example)
Optimize the refresh (see MSDN Wpf Optimization category, especially Layout and design)
The quick and dirty - store a IsRefreshing variable and use it to diable the buttons or cancel your buttons handlers
EDIT
The real question is : what is your bottleneck ? the UI refresh or the logic behind ?
have a TextBox item (MyTextBox) on a TabItem control. I have code that looks as follows:
MyTextBox.Focus();
Keyboard.Focus(MyTextBox);
When I run this code through the debugger I see the following after the lines are executed:
MyTextBox.IsFocused = true
MyTextBox.IsKeyboardFocused = false
Can anyone tell me why the textbox isn't receiving keyboard focus? It's just a standard TextBox control that is enabled.
When you try to set Focus to an element besides the things enumerated above by our coleague, you must also know that WPF does not allow cross threaded operations.
In some cases this exception is not raised like in the Focus method call case. What I've done to fix this issue is to call all the code that involves Keyboards focus in an action.
This action is ran inside the control dispatcher to make sure that my code is not being executed from another thread than the UI thread (e.g. timer event or an event raised from another thread):
[UIElement].Dispatcher.BeginInvoke(
new Action(
delegate{
/// put your Focus code here
}
)
);
MyTextBox.IsKeyboardFocused is false because you are looking at it under debugger and the keyboard focus is probably in your Visual Studio... Try debugging focus without breakpoints (e.g. Debug.Write or trace brakepoints) to see actual values of MyTextBox.IsKeyboardFocused in runtime.
Also notice that Focus() method returns boolean value that indicates whether focus was successfully set. Does it return False in your case? If yes, I would suggest stepping into Focus() method in order to find out what is wrong.
3 important properties must be true: IsVisible="True", Focusable="True". IsEnabled="True".
To be focusable, Focusable and IsEnabled must both be true.
http://msdn.microsoft.com/en-us/library/system.windows.uielement.focus.aspx
The accepted answer here does not solve the problem of textboxes who dont gain focus, no matter what the debugger tells you. If you have and can write to your textbox, then you have it keyboard-focused.
I found this here solving the problem (and actually gaining focus, not just settings the values so it looks like focus in the debugger), it comes very close to Pavlov's answer but with the "Focus code" : Keyboard.Focus does not work on text box in WPF
This worked for me (had to do UpdateLayout, otherwise Focus() didn't work immediately after changing tab from script)
tabControl.SelectedIndex = 2;
this.UpdateLayout();
txtMyTextBox.Focus();
It's important where your first two lines of code are executed.
If they are in an event handler that relates to the user pressing a key, using the mouse, altering the visibility of a control, or otherwise taking an action that might have an impact on focus, I find manually calling Focus() often doesn't work.
My theory is that internally, WPF operates as follows:
User or code takes action which could have an impact on focus, e.g. a TextBox control becomes enabled inside a focus scope which previously had no focusable control.
WPF notifies various event handlers, including yours which calls Focus().
WPF updates focus based on the state changes in step 1. This overrides whatever you did in step 2.
That is why this answer suggests to call your Focus() in a queued callback which will be executed after step 3.
Side note: you don't need to call both UIElement.Focus and Keyboard.Focus since the first includes the second (at least if you trust the Microsoft docs).
In conclusion, replace your first two lines of code with this:
// using System.Windows.Threading;
Dispatcher.BeginInvoke(DispatcherPriority.Input, MyTextBox.Focus);