Weird problem where Button does not get re-enabled unless the mouse is clicked
Asked Answered
A

3

23

My app is written using the MVVM pattern in WPF, and all of my Buttons use Command bindings to execute code in my model. All commands have code in CanExecute to determine the bound Button's Enabled state. The logic works perfectly, but in all cases, the GUI remains in a disabled state unless I click somewhere else in the GUI.

For example, I have a button called Discard Candy. When I click this button, it launches a process in a threadpool thread, which sets a bool property called Running to true. Since the CanExecute method for Discard Candy's command looks something like this

public bool CanExecute(object parameter)
{
  return !Running;
}

the button will be disabled once the process starts. The problem is that when the process is done, Running gets set to false, but the GUI doesn't update, i.e. Discard Candy doesn't get re-enabled.

However, if I click anywhere in the GUI, like on the window or title bar, the Discard Candy button all of a sudden gets enabled. So the logic works, but something is going on that I just don't understand. Can someone please explain this behavior to me?

EDIT -- so far, it sounds like CommandManager.InvalidateRequerySuggested hasn't helped people. I am going to give it a shot, but at the moment am a little wary of it. I did follow the recommended links, and in doing so decided to read more about the MVVM light toolkit. It sounds very nice -- has anyone here used it and been able to confirm that it does not exhibit the problem I've been seeing so far? Although I plan to try the MVVM light toolkit in the next major rev. of my application, I don't want to redo all of the commanding that I currently have in place, which is why I'll likely start with CommandManager.InvalidateRequerySuggested so we can all get another data point here regarding it's usefulness.

EDIT #2 -- very interesting, the MVVM light toolkit actually relies on CommandManager.InvalidateRequerySuggested in order to support the UI's ability to disable / re-enable commands. The author says:

"Strictly speaking, in WPF, and if your command is bound to a control that is watched by the CommandManager, you shouldn’t have to raise the CanExecuteChanged event yourself. You can let the CommandManager handle the situation. That said, external events might also change the state of the UI. Let’s imagine that the UI should be enabled from 9AM to 5PM, and then disabled for the night. The user is not triggering the UI, so the code should request (politely) that the CommandManager requeries the state of the commands. This is done by calling the method InvalidateRequerySuggested on the CommandManager. And as you guessed, the method RaiseCanExecuteChanged of the RelayCommand class does just that."

Appellate answered 25/2, 2010 at 4:24 Comment(7)
can you show the code that sets Running to false? Is it in the callback after the thread completes?Lehrer
Are you raising CanExecuteChanged for the command when you set Running?Logarithm
@John: Running is set to false after the workflow has completed, which is known when the specified event gets set. @itowlson: I was under the impression that everything would "just magically work". When I was learning this stuff, I put a breakpoint in CanExecute and it was always getting hit... but I imagine that this is only because when I hit F5, the GUI repaints, which then causes CanExecute to get called again. Silly me!Appellate
I've experienced the same behavior and executing CommandManager.InvalidRequerySuggested did not help. I'll be curious how you solve this. We ended up manually disabling because of this issue.Cords
The only time I've ever seen InvalidateRequerySuggested not work was when it was executed from another thread other than that of the Dispatcher. Other than that, it works fine.Meiny
Also I've been using Prism's DelegateCommand<T>.RaiseCanExecuteChanged() and that's been very convenient, rather than reaching out to the CommandManager.Meiny
I realize this is about 9 years old, but...based on Anderson's comment above, I was able to fix a similar issue (with the MVVM Light Toolkit) by calling DispatcherHelper.CheckBeginInvokeOnUI(CommandManager.InvalidateRequerySuggested); Not sure why I didn't think of that before (as I know I've read this post several times. Usually my problem is that I am referencing the wrong RelayCommand - need to be using CommandWpf and not Command.Fostoria
S
22

WPF doesn't update command bound controls unless it has a reason to. Clicking on the GUI causes WPF to refresh so the update then works.

You can manually cause a refresh of any command bound controls by calling CommandManager.InvalidateRequerySuggested.

Semasiology answered 25/2, 2010 at 4:57 Comment(6)
Thanks, Cameron, I'll give this a shot. Is it lame to put this into a Timer with a reasonable Interval?Appellate
Probably. Can you just call it after you've finished processing?Semasiology
I've seen the same behavior Dave is and executing CommandManager.InvalidateRequerySuggested did not help. I'm hoping someone here can provide some insight on why this sometimes happens.Cords
Be very wary of CommandManager.InvalidateRequerySuggested. Take a look at a similar problem I posted a few months ago: #1752466Selfabnegation
@unforgiven3: I'll give this a try. You should submit it as an answer. I hadn't come across your post when I was looking, but it was such a strange topic to search for.Appellate
I was hoping to find a better way to solve this problem, but in the end I just called CommandManager.InvalidateRequerySuggested in a slow-tick timer in the main thread, and this solved the problem for me. Thanks again for the tip!Appellate
I
5

My problem seemed to be bound to the Command Binding - I used the RelayCommand as I frequently do but the rendering of a button just wasn't correct until I clicked a window.

Removing the CanExecute code from the CommandBinding and using an IsEnabled property instead resolved my problem without a head ache - it just took forever until I tried this among so many other things that could have been the problem.

Intone answered 9/2, 2017 at 21:48 Comment(1)
This worked for me and was way simpler than any of the other "solutions", you da real MVP!Maniac
E
0

Sometimes, setting the focus on the parent control makes the CommandManager trigger CanExecute. Try the following after setting Running to false:

...
Running = false;
parentControl.Focusable = true;
parentControl.Focus();
Epileptic answered 20/4, 2016 at 10:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.