0

In a WPF application, I am outputting messages to a text box and in between these messages I am calling a function that sets up an instrument. However the messages all appear at the same time (at end of function call).

I do not really know how to explain my problem clearly. I'll try... I have a WPF application which takes data from an instrument using the serial port. The application contains some buttons and a text box for output messages. Upon pressing a button, the application sends a command to the instrument to change some parameters (through a function Set_COMM_MODE()), and returns. The change takes around 5-10 seconds. Thus what I did is: I outputted a "Please Wait" message before the call and a "Done" message after the call return. The OuputText() function in the code only calls the TextBox.AppendText() method. My Problem: All the output text is splurted out on the text box upon the call return. I expected a Please Wait... then 5-10s later a "Done" message. But it is all appearing at the same time. However, when I put a MessageBox after the 1st message (before the function call), the message appears on the textbox output (w/o the function being called). However the problem is that I have to press OK on the MessageBox in order to continue.

Q(1): How can I have the latter happening w/o having to resort to a MessageBox
Q(2): What does a MessageBox do in order to create this behaviour?

I tried: using the Dispatch.Invoke() method to run the OutputText on another thread. This (Pause a window like MessageBox.Show()) is a similar problem to what I have, but didn't seem to get a definitive answer, and I did not understand the solution well.

void StartTest_btn_Click(object sender, RoutedEventArgs e)
{
    OutputText("Please Wwait\r\n"); //<---- This should appear at once.

    MessageBox.Show("Please Wwait"); //<--without this, both messages appear at same time after 10s.

    Set_COMM_MODE("02"); //<--- This takes around 5-10s

    OutputText("Done\r\n"); //<--- This should appear 5-10s later
}

I expect a "Please wait" to show at once, then 5-10s later the "Done" message to show, after return of function Set_COMM_MODE().

Dmitriy
  • 3,305
  • 7
  • 44
  • 55
  • You might need a [Task](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=netframework-4.8) – Cid Aug 22 '19 at 10:02
  • 4
    A message box is a modal dialog which has its own message pump. Thus, your caller's message pump continues to work while the message box is in foreground. This allows the WPF Dispatcher to process the events and to update the text box. This is why you see the update when showing a message box. Without the box, you block the UI thread, so the Dispatcher cannot update the UI. For your issue, the solution is: **don't use the UI thread for anything but UI**! Perform your long-running tasks on a worker thread. – dymanoid Aug 22 '19 at 10:02
  • It's due to how dispatcher works on windows. Same issue has been explained in detail in microsoft's social forum: https://social.msdn.microsoft.com/Forums/vstudio/en-US/3f37a652-b558-4342-ade9-ad5ce68c9d01/why-does-showdialog-not-block-the-ui-thread-completely?forum=netfxbcl – Ardahan Kisbet Aug 22 '19 at 10:05
  • @dymanoid I just wanted to give microsoft's link in here since it is quite similar with this issue. I think that message box works similar in background in both forms application and wpf. I just wanted to declare when you google it it is the first site that come up with. – Ardahan Kisbet Aug 22 '19 at 10:09
  • 1
    You may take a look at [`Popup`](https://learn.microsoft.com/en-us/dotnet/framework/wpf/controls/popup-overview). It is more suitable for your case. Opposed to a `MessageBox` it doesn't block the UI while waiting for user interaction. Also make `Set_COMM_MODE` async. – BionicCode Aug 22 '19 at 10:48
  • For me the problem is not the long task. I do not mind that the UI is blocked for 10s while the instument is being setup. I know how the backgroundworker works. In fact, for the main method of my code (which is just after the code I showed here), I use a background worker (thats working fine hence why I did not include it). My issue is the I would like to show the "Please wait" in the text box first. Then wait until Set_COMM_MODE() returns before continuing and printing "Done". – Mario Farrugia Aug 22 '19 at 11:25
  • @dymanoid. Thanks for the comment. I understand that. I put the Set_Comm_mode in a worker but then it runs in background and the "Please Wait" and the "Done" messages appear at once (understandably wo allowing Set_Comm_Mode() to finish). But this gave me another idea. using the Worker.IsBusy method thus hoping that the UI thread will wait until the worker completes. However this blocked the main thread indefinitely. – Mario Farrugia Aug 22 '19 at 11:30

1 Answers1

1

As I wrote in my comment, you should really use the (main) UI thread for UI only. Perform any other long-running non-UI operations on worker threads.

You might not mind the "hang" UI for 10 seconds, but users will surely be annoyed. Also, blocking the UI thread will cause Windows to think that your app is frozen, so you'll get that nice "not responding" badge and all related stuff. This not only looks awful but also can cause various side-effects.

You should really take a look around and see what .NET offers you for such kind of problems.

Look, this is your workflow:

  1. Print a message
  2. Start initialization
  3. ???
  4. Initialization is complete --> print "done"
  5. Start operation

What is this? This is an asynchronous processing. You start an action and continue asynchronously - that means, only when the action is completed.

.NET offers you a lot of tools for that, e.g. the APM (Asynchronous Programming Model). But the most neat and powerful way for implementing asynchronous processing is the TAP - Task-based Asynchronous Programming pattern, better known as async/await.

Look, your problem can be solved with a couple of lines using the TAP:

async void StartTest_btn_Click(object sender, RoutedEventArgs e)
{
    OutputText("Please Wait\r\n");      

    // Set_COMM_MODE will be executed on a worker thread!
    // The main (UI) thread won't block. It will continue.
    // But the StartTest_btn_Click method itself will pause until the task is finished.
    await Task.Run(() => Set_COMM_MODE("02"));

    // This line will only be executed, after the Set_COMM_MODE call is completed;
    // furthermore, it will be executed on the UI thread again!
    OutputText("Done\r\n");
}

You should really learn more about the modern programming techniques, which the TAP is one of.

dymanoid
  • 14,771
  • 4
  • 36
  • 64
  • Thanks a lot for this. This is what I was looking for. I tried the async/await before, but not in the way you showed. I guess I did not understand exactly how it works. You are right, I need to update myself on modern tachniques. My issue is that I am not an IT guy, im in electronics. So everytime I have a project like this, there's always something new to learn. Thanks again! – Mario Farrugia Aug 22 '19 at 13:02