floppsb
floppsb

Reputation: 696

WinForm Disappears in Multithreaded C++/CLI Application

I'm currently working on an application that needs to utilize a form to block the user from using the main form for a few seconds. Although running the code on the main thread seems to be okay, the labels on the pop up form don't render for about a second when the form first appears. I thought that if I ran that form on a separate thread, the rendering would be a lot smoother. The rendering is now so smooth that the form disappears immediately after rendering. The timer is set for five seconds, with a label on the screen counting down. Here's the relevant code calling the new thread and form:

System::Void MainScreen::runGame(int playerTurn) {
    Thread ^ t = gcnew Thread(gcnew ThreadStart(gcnew MainScreen(),
        &MainScreen::showModalDialog));
        t->Start();
        t->Join();

        InitializeDice();
        startTimer();
}

System::Void MainScreen::showModalDialog() {
    GetReady ^ gr = gcnew GetReady();
    gr->showModalPopup();
}

And here's the code inside the form:

public:
    GetReady(void)
    {
        InitializeComponent();
    }

    System::Void showModalPopup() {
            this->Show();
            startTimer();

        }
private: System::Void timerPrep_Tick(System::Object^  sender, System::EventArgs^  e) {
         ts = ts->Subtract(TimeSpan(0, 0, 1));
         if (ts->TotalSeconds <= 0) {
             finishTimer();
         } else {
             lblTimer->Text = ts->ToString("mm\\:ss");
         }
    }

    System::Void startTimer() {
         array<String ^>^ minSec = gcnew array<String ^>(2);
         minSec = lblTimer->Text->Split(':');
         ts = gcnew TimeSpan(0, Convert::ToInt32(minSec[0]), Convert::ToInt32(minSec[1]));
         Thread::Sleep(900);
         timerPrep->Start();
     }

     System::Void finishTimer() {
         timerPrep->Stop();
         lblTimer->Text = "GO!!!";
         Thread::Sleep(900);
         this->Close();
     }

My ideal solution would be to use a thread to generate the new form, so that rendering in both the main form and the pop up form is smooth.

Things I've tried:

  1. Moving this->Show() every where I can think to put it.
  2. I've added t->Join() to see if the main thread was trying to bring the main window back into focus. Thread t still executes and the timer still runs appropriately with the Join, blocking user input for five seconds - but there is nothing to block the screen.
  3. I've read a few questions on SO, I think the most relevant one is WinForms multithreading issue - although I feel like that might be overkill for this situation.

If you have any ideas on what I need to do to have smooth rendering for both forms, please let me know. Thanks!

Upvotes: 0

Views: 408

Answers (2)

Hans Passant
Hans Passant

Reputation: 942257

Displaying UI on a worker thread is filled with traps and surprises. The chief problem you are having here is that the thread doesn't pump a message loop. Required first of all to ensure that the window can receive Windows messages and to ensure that the thread doesn't immediately terminate. Beyond Jairo's recommendation to use ShowDialog(), the boilerplate solution is:

System::Void MainScreen::showModalDialog() {
    Application::Run(gcnew GetReady);
}

There's more, a thread that displays windows should always be an STA thread. An implementation detail for COM and required to get stuff like the clipboard, drag+drop and shell dialogs operating correctly:

Thread ^ t = gcnew Thread(gcnew ThreadStart(this, &MainScreen::showModalDialog));
t->SetApartmentState(ApartmentState::STA);    
t->Start();

And you generally have a Z-order problem with windows that are displayed on the main thread. With a nasty habit of your window disappearing behind a window owned by the main thread. No good solution for that.

Upvotes: 1

Jairo
Jairo

Reputation: 886

The problem is showModalPopup uses Show method instead ShowDialog. The thread starts, shows the form (it doesnt block execution), starts the timer, ends and joins to the main thread. The thread where the form was created has been finished and it's here where the winforms multithreading issues comes.

Start the timer first and then show the modal window with ShowDialog. Something like this:

System::Void showModalPopup() 
{            
   startTimer();
   this->ShowDialog();
}

Upvotes: 1

Related Questions