0

I have a GUI in c# where you can open log files. The program have 2 main functions: setTextMessageTextBox() which updates the GUI when needed (after each operation, giving the appropriate messages)

private static void setTextMessageTextBox(){
  MyProject.mainForm update = new MyProject.mainForm();

  //messageTextBox.Text is set by default to "Open the file".

  const string msg_0 = "Open the file";
  const string msg_1 = "Press button to start reading";
  const string msg_2 = "Please wait, Reading in progress";
  const string msg_3 = "Reading finished";

  string textCurrent = update.messageTextBox.Text;

  switch (textCurrent){
    case msg_0 :
      update.messageTextBox.Text = msg_1;
      break;
    case msg_1 :
      update.messageTextBox.Text = msg_2;
      break;
    case msg_2 :
      update.messageTextBox.Text = msg_3;
      break;
    case msg_3 :
      //msg_4 is a global variable with the result
      update.messageTextBox.Text = msg_4;
      break;
  }//switch
}//method setMessageBoxTest

Then, i have the analyze() method which does all the work (the log is very big so it takes up to 1 min to finish).

private void analyze(){
  //reading log
  //cutting log
  //calls other classes to take what pieces are needed
  //calls other classes to decode pieces
  //saves the final result to msg_4
}

Finally I have the button AnalyzeButton which triggers:

private void analyzeButton_Click(object sender, EventsArgs e){

  setTextMessageTextBox();

  analyze();

  setTextMessageTextBox();

}//analyzeButton

The result is that the GUI freezes and the textBox is updated only after the analyze function finishes.

I tried:

MethodInvoker startAnalyze = new MethodInvoker(analyze);
startAnalyze.Invoke();

but the GUI still Freezes

I tried to invoke the setTextBox

MethodInvoker setMessage = new MethodInvoker(setMessageTextBox);
setMessage.Invoke();

the GUI still Freezes

I tried to use both with method invoker but the Invalid Operation Exception is thrown.

Finally I used the begin_invoke instead of Invoke to analyze:

MethodInvoker beginning = new MethodInvoker (analyze);
beginning.BeginInvoke(null, null)

The GUI doesn't freeze anymore but still the textBox is not updated.

I have read about BackgroundWorker but maybe I am missing something and it didn't work at all. Anybody has a clue of what I am missing or if there is a better way to solve? Thank you for your time.


Update: @SriramSakthivel I moved the creation of the new instance to a global variable level, but the result still the same. The GUI doesnot freeze but the textBox is not updated. It is updated just before I press the button . ex . The text changes from "Open the file" to Press button to start reading but after I press the button the textBox remains as it is. Having the new instance as global I tested in 2 ways: calling the setMessageTextBox, and assigning the new message manually before and after the appropriate action. ex. ' update.messageTextBox.Text = "Analyzing"; update.messageTextBox.Refresh(); analyze(); ' I tried with and without .Refresh() but still nothing changes.

  • "I have read about BackgroundWorker but maybe I am missing something and it didn't work at all." - Show what you have tried and include the results/errors of those attempts, threading is the correct answer here. – Sayse Jul 30 '14 at 07:29
  • 1
    In `setTextMessageTextBox` method you create new instances of your `MainForm` everytime, which is not what you intended. You need to get the instance of mainform which is already being shown, then update it. – Sriram Sakthivel Jul 30 '14 at 07:31
  • @Sayse I could not even run the BackgroundWorker because I had some invalid parameters probably which could not find a way to give the correct ones. Let me find the code which I've used the Background Worker and I will return back. – user3238433 Jul 30 '14 at 07:46
  • 1
    The correct way is to simply run the code in a new thread (e.g. using `BackgroundWorker`), rather than running it in a GUI thread. That's where `Invoke` comes in - you may need it to update the controls on the GUI thread. And of course, there's the wrong and dirty solution of just using `Application.DoEvents`, which will still leave your GUI frozen, but will allow you to change what's displayed. Still, `BackgroundWorker`, asynchronous I/O or custom threading is the way to go. – Luaan Jul 30 '14 at 08:02
  • Your variables are named really poorly. `msg_0` to `msg_3` are const strings inside a method, `msg_4` is a static global field? First of all, [you shouldn't use globals, ever](http://c2.com/cgi/wiki?GlobalVariablesAreBad). Especially if you're multithreading. Next, UI elements can only be updated from a UI thread. – vgru Jul 30 '14 at 08:39

1 Answers1

1

First of all, it doesn't make sense to create a new instance of your form on each call to setTextMessageTextBox(). I am pretty sure you don't want to have a bunch of windows popping up for each log message (and you aren't even showing these new forms, actually).

Next, GUI elements must be updated from the UI thread only. If you are doing a long running operation on a UI thread, and setting progress properties of different controls, they will not be repainted until the job is done, because setting these properties (like Text) only invalidates them and basically tells the runtime to redraw them the next time the UI thread has nothing else to do.

In other words, you must use a background worker to do this, whether it's a BackgroundWorker, a Task, or a plain Thread. This background thread should then call the progress update method, which will make sure that the update action is dispatched to the UI thread (which will now be free to repaint it also):

class MyForm : Form
{
    public void ShowMessage(string msg)
    {
        if (this.InvokeRequired)
        {
            this.Invoke(new Action<string>(ShowMessage, msg));
            return;
        }

        this.messageTextBox.Text = msg;
    }
}

And then call it from this Form instance only.

vgru
  • 49,838
  • 16
  • 120
  • 201