Community Pick: Many members of our community have endorsed this article.
Editor's Choice: This article has been selected by our editors as an exceptional contribution.

Create a Dialog in its Own Thread

In general, it is best to do all U/I activities in your program's main thread, but there are cases where having a second U/I thread can be worth the extra bother.

In previous articles about multithreading here and here, I have recommended avoiding multithreading when possible.  When it is necessary, always isolate functionality that does not involve user-interface handling.  For instance, if you need to parse a large XML file, then do it in a worker thread... have it generate in-memory arrays of data, and when finished, let the main window populate list boxes (or do other U/I activities).  There are just too many things that can go wrong when you have a worker thread interacting with the application's main thread.

Nevertheless, it is worth exploring MFC's ability to create a I/U thread.  I hit upon a reasonably good example of this recently:  An EE Member wanted to show an animated GIF in a "splash screen" at certain times.  He had a long calculation to perform, during which the main program would become unresponsive.  And not only the main program, but all modeless dialogs would stop handling messages, as well.  The GIF animation stopped cold (it was "displaying" the frames, but not handling the WM_PAINT messages) -- and the whole point of the splash screen was to show that the program was still active.

For various reasons, he could not (or refused to) spin off a thread for the calculations -- which is always the best option.  So, I asked myself, why not look at it from the other direction?  The splash screen was just a modeless dialog -- it had basically no interaction with the rest of the program... Wouldn't it be possible to run it in its own thread?

It turned out to be surprisingly simple to implement a self-contained unit that runs a dialog in its own U/I thread.  Before we get to the code, let's take a minute and look at the differences between the two thread types:

Worker Thread
You provide a ThreadProc -- a function that does some work and then exits.  When the function exits, the thread is terminated.  See Simple Multithreading in Visual C++ for a detailed discussion and source code for a worker thread.

U/I Thread
There is no ThreadProc.  Instead, there is a message pump -- a tight loop that waits for and processes messages.  The thread only does something when it gets a message.  This is exactly how your main application works:  After the instance is set up, it just sits around waiting for something to happen -- waiting for the next message.  It processes that message and goes back to waiting.  When it get the right message (WM_QUIT) it exits from that tight loop, and the main thread is destroyed, ending your program.  A U/I thread works the same way.  It pumps messages until WM_QUIT (or you use AfxEndThread within the thread code) and the thread is terminated.

In this article, we'll create a threaded dialog object.  When the thread starts, it will create the dialog and it will then pump messages to that dialog.  When the dialog ends, it will halt the thread.  You might consider using this technique all over the place... why not make every modeless dialog run in its own thread?   I urge you to avoid that.  Modeless dialogs usually interact with the main program in various ways, often "under the hood" in ways that are not obvious.  The errors that result are very difficult to track down and harder to fix.  Often the fix (usually adding a Critical Section, Semaphore, or other synchronization object) causes its own set of headaches.  And here's the real point:   Windows handles modeless dialogs just fine without using a secondary thread.  Why swim upstream?

CThreadedDlg class
So, even though I'd never implement this in a production system, I did decide that it is worth exploring.  Here's the code:
#pragma once
                      // CThreadedDlg.h header file
                      class CThreadedDlg : public CWinThread
                          CThreadedDlg();   // protected constructor used by dynamic creation
                          virtual ~CThreadedDlg();
                          void Setup( CDialog* pDlg, UINT nIDTemplate, int nCmdShow= SW_SHOW );
                          virtual BOOL InitInstance();
                          virtual int ExitInstance();
                          CDialog* m_pDlg;
                          UINT     m_nIDTemplate;
                          int      m_nCmdShow;

Open in new window

// ThreadedDlg.cpp : implementation file
                      #include "stdafx.h"
                      #include "ThreadedDlg.h"
                      IMPLEMENT_DYNCREATE(CThreadedDlg, CWinThread)
                      CThreadedDlg::CThreadedDlg() { }
                      CThreadedDlg::~CThreadedDlg() { }
                      void CThreadedDlg::Setup( CDialog* pDlg, UINT nIDTemplate, int nCmdShow /*=SW_SHOW*/ ) {  
                          m_pDlg=        pDlg;
                          m_nIDTemplate= nIDTemplate;
                          m_nCmdShow=    nCmdShow;
                      BOOL CThreadedDlg::InitInstance() {
                          m_pActiveWnd= m_pDlg;
                          m_pDlg->Create( m_nIDTemplate, 0 ); //parent is the desktop
                          m_pDlg->ShowWindow( m_nCmdShow );
                          return TRUE;
                      int CThreadedDlg::ExitInstance() {
                          return CWinThread::ExitInstance();

Open in new window

Note that line 17 is an important key.  It tells the message pump where to send the messages.   Line 18 is just as important.  If you try to set the parent to a window that's running under the main thread, there will be "under the hood" interactions that will cause serious problems.

To use this class, you need to create a suspended instance of it using AfxBeginThread(), then call its Setup() member, passing it information about the dialog.  Then use ResumeThread() to go live.  For instance:
CDlgSplash    m_dlgSplash;
                      CThreadedDlg* mp_ThrdDlg;
                      mp_ThrdDlg= (CThreadedDlg*)AfxBeginThread( RUNTIME_CLASS(CThreadedDlg),
                                             0,0,CREATE_SUSPENDED );
                      mp_ThrdDlg->Setup( &m_dlgSplash, m_dlgSplash.IDD, SW_SHOW );

Open in new window

To hide or show the dialog:
   m_dlgSplash.ShowWindow( SW_HIDE );
                         m_dlgSplash.ShowWindow( SW_SHOW );

Open in new window

You need to kill the thread before you exit from your program.  The normal (clean) way to do that is to post it a WM_QUIT message.  For instance:
void CMyMainWnd::OnDestroy()
                          m_dlgSplash.PostMessage( WM_QUIT );

Open in new window

Some notes about using this class:
I have put no error handling anywhere.  For instance, you should not call dlg.PostMessage() if the dialog does not exist!   Treat this as functional, explorational, code; not production code.
You can do anything with the target dialog that you would normally do with a regular CDialog -- use the DataExchange mechanism, add message handlers, window timers, and so forth.  However...
Almost anything in the threaded dialog that interacts with your main thread will cause grief.  In particular, don't call member functions of any U/I objects that were created in the main thread.  If necessary, post messages to other windows.  Or set global variables that are accessible to both threads.
With a default target dialog, you will end up having two "main" windows; for instance, you will have two icons in the taskbar.  Perhaps the easiest way to avoid that is to set the Toolbar property in the Dialog Editor.
If the user minimizes your main window, the threaded dialog will remain visible (its parent is the Desktop).  Thus, you should add a handler for WM_SYSCOMMAND/SC_MINIMIZE that will hide the threaded dialog.
For the same reason, the threaded dialog is treated as a "peer" to your main window -- it does not have the "normal" parent/child relationship of a modeless dialog.  One result of that:  Your main window can cover up the threaded dialog window if the user moves things around.  Perhaps the simplest option for that is to set its Topmost attribute, either in the dialog editor, or using ModifyStyleEx().
I always recommend avoiding the use of secondary threads.  In certain cases, using worker threads can make sense (see Multithreading -- Why and When), but an actual need to create a U/I thread is very rare.  I suggest that you avoid them.  That said, it is educational to explore these things, and I hope that you have learned something here.  I know I have :-)

=-=-=-=-=-=-=-=-=-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=
If you liked this article and want to see more from this author,  please click the Yes button near the:
      Was this article helpful?
label that is just below and to the right of this text.   Thanks!
=-=-=-=-=-=-=-=-=-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=-=- =-=-=-=

Comments (3)

Thanks. It's a very interesting article.

Once, now I think it was a mistake, I put the entire application GUI in a separate thread and the main thread does a lot of calculations and handle many other threads.
Now I'd use the idle time in the message loop as it works in many OpenGL applications for example. WTL also handles many things in OnIdle methods.

MsgWaitForMultipleObjects also can help in such cases as you say about a long loading - for example it is possible to initialize a heavy COM-object (NetMeeting) in seperate thread and use MsgWait in the main message loop.

For an info:
A Visual C++ Threads FAQ:

CodedKSenior Software Engineer

Author of the Year 2009


I have gotten a note from a reader regarding this article.  He is getting an ASSERT in a particular situation where his threaded dialog hosts a VBscript-handling COM object.  All I can say is that I'm not surprised!  

The simplistic scenario shown in this article is about as far as I'd ever go with a dialog object running on its own thread... and as I stated in the article, I did this only under experimental conditions.  In a production app, I'd find a different way to get it (even this base-minimum functionality) done.

-- Dan

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.