1

I have a similar problem than here : WPF MVVM Light: Command.RaiseCanExecuteChanged() doesn't work, using commands with WPF and have my GUI not working until I click somewhere in the scren. I don't use MVVM Light.

I call an external DLL to do some action, by calling ExternalDLL.Start(), and call GetStatus() to know if the action started. If I get the correct status in return, I change the actual action, and it have to activate a button on my GUI.

The button don't activate himself until I click somewhere.

I checked for the thread, but it seems it's on the same thread, I tried to put it in the GUI thread to, by using Application.Current.Dispatcher.BeginInvoke, but it didn't work too.

Here is my code :

private async void StartScanCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{  
    ExternalDLL.Start();  
    WaitForStarting();
}
private async void WaitForStarting()
{
    Waiting();
    Stopwatch chrono = new Stopwatch();
    chrono.Start();
    bool started = false;
    while (chrono.ElapsedMilliseconds < 20000)
    {
        if (ExternalDLL.GetStatus() != ExternalDLL.Status.Started)
        {
            await Task.Delay(100);
        }
        else
        {
            started = true;
            chrono.Stop();
            StartedAction();
            break;
        }
    }
    if (!started)
    {
        MessageBox.Show("Error");
    }
}

The Waiting() method call activate a button in the GUI and work. but the StartedAction() have to activate a button too, and doesn't work.

Here is the code for started action :

private void StartedAction()
{
    _actualAction = ActualAction.DoingAction;
}

And here is the button's can execute method :

private void SomeButtonCommand_CanExecute(object sender, 
CanExecuteRoutedEventArgs e) 
{ 
    e.CanExecute = _actualAction == ActualAction.DoingAction; 
}

What am I doing wrong ?

betsou
  • 143
  • 3
  • 16
  • *"doesn't work"* means what exactly? Did you put a breakpoint at the StartedAction line to see if it gets hit? How long does it take to get there? – Clemens Sep 08 '18 at 20:36
  • doesn't work means the button status is disabled, instead of enabled, but StartedAction get hit. Don't take too long to be hitten. – betsou Sep 08 '18 at 21:17
  • You might have seen the discussion on the post meant as an answer. So, is it true that `ExternalDLL.Start()` is a more or less instant action, then WaitForStarting is called, which immediately activates one button via `Waiting`. Then your wait loop is entered, which after a while ends at `StartedAction`? Please answer that, and also show the implementation of the StartedAction method. – Clemens Sep 08 '18 at 21:21
  • It's exactly this. Here is the StartedAction : `_actualAction = ActualAction.DoingAction;`. Depending the _actualAction, the command of the button can execute or not. I tried to wrap this action it the `Application.Current.Dispatcher.BeginInvoke` too. Just to be sure. Same problem. – betsou Sep 08 '18 at 21:26
  • Dispatcher is completely unnecessary here. Your code does already run in the UI thread, which is fine. I am however not sure what `_actualAction = ActualAction.DoingAction;` is supposed to do. How would that activate any Button? – Clemens Sep 08 '18 at 21:28
  • I know, it was just to be sure. _actualAction is an enumerator. I have a command behind the button with this in the can execute : `private void SomeButtonCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = _actualAction == ActualAction.DoingAction; }`. Working fine for other commands ! – betsou Sep 08 '18 at 21:31
  • Please edit your question. Code in comments is unreadable. – Clemens Sep 08 '18 at 21:32
  • Besides that, depending on the ICommand implementation you're using, you may call the command's RaiseCanExecuteChanged method. Or try `CommandManager.InvalidateRequerySuggested`. – Clemens Sep 08 '18 at 21:34
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/179690/discussion-between-betsou-and-clemens). – betsou Sep 08 '18 at 21:39
  • Did you try what I suggested? And is ScanningAction the same as StartedAction? – Clemens Sep 08 '18 at 21:40
  • How you use RaiseCanExecuteChanged ? I'm trying with InvalidateRequerySuggested. Yes it's the same, I corrected it. – betsou Sep 08 '18 at 21:43
  • If your command implementation is the usual RelayCommand or DelegateCommand, it should have a RaiseCanExecuteChanged method. – Clemens Sep 08 '18 at 21:44
  • it's custom commands like [here](https://www.wpf-tutorial.com/commands/implementing-custom-commands/) – betsou Sep 08 '18 at 21:47
  • See here: https://stackoverflow.com/q/1340302/1136211 – Clemens Sep 08 '18 at 21:50
  • Thank's a lot ! It worked with `CommandManager.InvalidateRequerySuggested`. Fell free to add an answer now, I will mark you as the correct answer. – betsou Sep 08 '18 at 21:50
  • Yes, it's seems to be similar. Ok, so thank you very much for your help ! – betsou Sep 08 '18 at 21:53

2 Answers2

1

The problem is simply that the bound Command's CanExecute status is not re-evaluted when the ActualAction value changes.

Call CommandManager.InvalidateRequerySuggested() to force re-evaluation.

private void StartedAction()
{
    _actualAction = ActualAction.DoingAction;
    CommandManager.InvalidateRequerySuggested();
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
-2

You are doing the background work on the UI thread. Don't do it there, do it in another thread, and use polling, events or other callback methods to update the UI (on the UI thread).

For example you can do:

Task.Run(() => { OtherDll.DoWork(); };

This will kick off the other work on the external thread.

If you need more control you can wrap the functionality of the other dll in a thread all by itself.

Public Class OtherDLLThread 
{
    Thread _internalThread;

    public OtherDLLThread()
    {
         _internalThread = new Thread(ThreadMainLoop);
    }

    public void ThreadMainLoop()
    {
        OtherDLL.DoWork();
    }

    public static void Start() 
    {
       _internalThread.Start();
    }

}

Use it like this:

OtherDLLThread other = new OtherDLLThread();
other.Start();

Here is another function for bumping code to the UI thread:

    /// <summary>
    ///     Runs the action on UI thread.
    /// </summary>
    /// <param name="action">The action.</param>
    public static void RunOnUIThread(Action action)
    {
        try
        {
            if (Application.Current != null)
                Application.Current.Dispatcher.Invoke(action);
        }
        catch (Exception ee)
        {
            _logger.Fatal("UI Thread Code Crashed. Action detail: " + action.Method, ee);
            //SystemManager.Instance.SendErrorEmailToCsaTeam("Kiosk Application Crashed", "UI Thread Code Crashed. Action detail: " + action.Method);
            throw;
        }
    }

Use it like this:

RunOnUITHread(() => lblStatus.Text = "Working...");
user230910
  • 2,353
  • 2
  • 28
  • 50
  • I'm just looking for a bit of code I used before to make pushing updates to the UI thread easier in WPF land.. – user230910 Sep 08 '18 at 20:35
  • How do you know that OP is "*doing the background work on the UI thread*"? `ExternalDLL.Start();` sounds like an instant action that just starts some not further explained background processing which is later waited for. – Clemens Sep 08 '18 at 20:37
  • This is my best guess based on the description here, unresponsive ui as soon as external action is started.. – user230910 Sep 08 '18 at 20:38
  • 1
    But just a guess. Answers here should be based on facts. – Clemens Sep 08 '18 at 20:39
  • Feel free to provide a better one :) – user230910 Sep 08 '18 at 20:40
  • It can't be answered without a more detailed problem description. When OP says that "*the Waiting() method call activate a button in the GUI **and work**"* it indicates that the UI thread is not blocked. – Clemens Sep 08 '18 at 20:40
  • Like Clemens said, it's a an instant action, but the stopwatch waiting for the status is on the UI thread. Should I run it on another thread ? Thanks guys – betsou Sep 08 '18 at 21:21
  • I think so, you should do almost nothing on the UI thread except updating the UI, as far as is reasonable. – user230910 Sep 08 '18 at 21:23
  • As I read the code above it is going to block the UI thread until the external DLL is done. – user230910 Sep 08 '18 at 21:23
  • @betsou Please ignore that advice. Your code with `await Task.Delay` is ok. You should however declare the method as `private async Task WaitForStarting()` and call it like `await WaitForStarting();` – Clemens Sep 08 '18 at 21:23
  • Tried this before. Same problem. – betsou Sep 08 '18 at 21:27
  • @betsou If you mean the Task and await, you should do that in any case. `async void` is only valid for event handlers. – Clemens Sep 08 '18 at 21:29
  • I never use async void for methods. I'm using async Task. But in this case, it's putting all my code in other thread, so it's normal that the UI freeze no ? – betsou Sep 08 '18 at 21:32
  • *"so it's normal that the UI freeze"* - not in your case. Except if `ExternalDLL.GetStatus()` is a blocking, time-consuming operation, which it doesn't seem to be. – Clemens Sep 08 '18 at 21:36
  • Thank you. GetStatus is an instant status returning operation. – betsou Sep 08 '18 at 21:44