CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 14 of 14
  1. #1
    Join Date
    Apr 2005
    Posts
    41

    Updating a progress bar from within a user interface thread

    Hi all,

    I have set up a user interface thread which I call from my main dialog. The user interface thread I invoke starts a lengthy calculation which can take many minutes / hours to complete. Therefore I would like to show the progress by updating a progress bar belonging to the main dialog.

    At the moment I have the following:

    In the main dialog / class, I have the following function which calls my user interface thread:

    void CFCMDlg::OnStartCalculateThread()
    {
    CalcThread* pThread;
    pThread = new CalcThread();
    pThread->CreateThread();
    pThread->PostThreadMessage(WM_MYTHREADMESSAGE,NULL,NULL);
    }

    This calls the following function in another class called CalcThread:

    void CalcThread::OnCalculate(WPARAM, LPARAM)
    {
    for(int year=0;year<a_big_number;year++)
    {
    // Many many many lines of code!
    }
    }

    I want to somehow update a progress bar (defined in the main dialog window CFCMDlg with the variable name m_progress) from the function CalcThread::OnCalculate(..) as its loops through the loop as shown above. I have tried many things however I must admit I am not that good at C++ so any help would be much appreciated!

    Thanks for looking,

    Robbie

  2. #2
    VictorN's Avatar
    VictorN is offline Super Moderator Power Poster
    Join Date
    Jan 2003
    Location
    Hanover Germany
    Posts
    20,430

    Re: Updating a progress bar from within a user interface thread

    You should PostMessage a user defined message (with id from WM_APP range or registered Windows message) from a secondary thread to the main GUI thread with the data to show in progress bar.
    And in the message handler for this message in the main GUI thread you will update this progress bar.
    Victor Nijegorodov

  3. #3
    Join Date
    Apr 2005
    Posts
    41

    Re: Updating a progress bar from within a user interface thread

    Hi thanks for your reply. However I would like a bit more adivce please.

    After reading the post, I have added another message to the stdafx.h file:

    #define WM_PROGRESS_BAR (WM_USER+2)

    In the CalcThread class (CalcThread.cpp) I have defined a new function which is "supposed" to post a message to the main dialog (however this is wrong and don't know what to do about it!!):

    void CalcThread::ProgressBar()
    {
    CFCMDlg* pThread;
    pThread = new CFCMDlg();
    pThread->CFCMDlg();
    pThread->PostMessage(WM_PROGRESS_BAR,NULL,NULL);
    }

    Back in the FCMDlg class ( the main dialog FCMDlg.cpp) I have defined an additional line in the message map:

    ON_MESSAGE (WM_PROGRESS_BAR, On_Progress)

    which runs the On_Progress function which in turn runs the progress bar StepIt() function.


    I have got absolutely no idea what to do to get this code working and any help you can offer would be greatly appreciate.

    Thanks for viewing the post

    Robbie

    (ps when I compile the VC6 complains about the '->' operator in the CalcThread::ProgressBar() function (the line "pThread->CFCMDlg();")

  4. #4
    VictorN's Avatar
    VictorN is offline Super Moderator Power Poster
    Join Date
    Jan 2003
    Location
    Hanover Germany
    Posts
    20,430

    Re: Updating a progress bar from within a user interface thread

    Quote Originally Posted by robbiegregg
    Hi thanks for your reply. However I would like a bit more adivce please.

    After reading the post, I have added another message to the stdafx.h file:

    #define WM_PROGRESS_BAR (WM_USER+2)
    Why WM_USER? Didn't you read my previous post?
    You should use WM_APP range! (or registered Windows message).
    Besides , using WM_ prefix for user defined messages is a bad style. This prefix is reserved for Windows messages.

    Quote Originally Posted by robbiegregg
    In the CalcThread class (CalcThread.cpp) I have defined a new function which is "supposed" to post a message to the main dialog (however this is wrong and don't know what to do about it!!):

    void CalcThread::ProgressBar()
    {
    CFCMDlg* pThread;
    pThread = new CFCMDlg();
    pThread->CFCMDlg();
    pThread->PostMessage(WM_PROGRESS_BAR,NULL,NULL);
    }
    Well, this code is completely wrong (or, at least doesn't make any sense)!
    You should learn how to use worker and UI threads. Checi it out:
    Using User-Interface Threads
    Using Worker Threads
    Victor Nijegorodov

  5. #5
    Join Date
    Jul 2008
    Location
    Croatia
    Posts
    52

    Re: Updating a progress bar from within a user interface thread

    Using WM_USER is ok for creating messages inside app. It is used to define user messages that will be used to send messages between app windows.
    I pase'd this from MSDN:

    The RegisterWindowMessage function defines a new window message that is guaranteed to be unique throughout the system. The message value can be used when sending or posting messages. Only use RegisterWindowMessage when more than one application must process the same message. For sending private messages within a window class, an application can use any integer in the range WM_USER through 0x7FFF. (Messages in this range are private to a window class, not to an application. For example, predefined control classes such as BUTTON, EDIT, LISTBOX, and COMBOBOX may use values in this range.)

    So the point is use WM_USER+n.

    I don't use PostMessage because it can flood GUI with messages. When there is no more space in GUI for new message PostMessage must wait until first message is executed resulting in low thread performence. Better way off doing this is with timer. Create timer in main GUI thread to read data from worker thread every few miliseconds(I use 100 miliseconds).

  6. #6
    VictorN's Avatar
    VictorN is offline Super Moderator Power Poster
    Join Date
    Jan 2003
    Location
    Hanover Germany
    Posts
    20,430

    Re: Updating a progress bar from within a user interface thread

    Quote Originally Posted by Borg_ri
    Using WM_USER is ok for creating messages inside app. It is used to define user messages that will be used to send messages between app windows.
    I pase'd this from MSDN:
    ...
    So the point is use WM_USER+n.
    No, it not so.
    WM_APP

    The WM_APP constant is used by applications to help define private messages, usually of the form WM_APP+X, where X is an integer value.

    #define WM_APP 0x8000
    Remarks
    The WM_APP constant is used to distinguish between message values that are reserved for use by the system and values that can be used by an application to send messages within a private window class. The following are the ranges of message numbers available.

    Range Meaning
    0 through WM_USER – 1
    Messages reserved for use by the system.
    WM_USER through 0x7FFF
    Integer messages for use by private window classes.
    WM_APP through 0xBFFF
    Messages available for use by applications.
    0xC000 through 0xFFFF
    String messages for use by applications.
    Greater than 0xFFFF
    Reserved by the system for future use.

    Message numbers in the first range (0 through WM_USER – 1) are defined by the system. Values in this range that are not explicitly defined are reserved for future use by the system.

    Message numbers in the second range (WM_USER through 0x7FFF) can be defined and used by an application to send messages within a private window class. These values cannot be used to define messages that are meaningful throughout an application, because some predefined window classes already define values in this range. For example, predefined control classes such as BUTTON, EDIT, LISTBOX, and COMBOBOX may use these values. Messages in this range should not be sent to other applications unless the applications have been designed to exchange messages and to attach the same meaning to the message numbers.

    Message numbers in the third range (0x8000 through 0xBFFF) are available for application to use as private messages. Message in this range do not conflict with system messages.

    Message numbers in the fourth range (0xC000 through 0xFFFF) are defined at run time when an application calls the RegisterWindowMessage function to retrieve a message number for a string. All applications that register the same string can use the associated message number for exchanging messages. The actual message number, however, is not a constant and cannot be assumed to be the same between different sessions.

    Message numbers in the fifth range (greater than 0xFFFF) are reserved for future use by the system.
    Victor Nijegorodov

  7. #7
    Join Date
    Jul 2008
    Location
    Croatia
    Posts
    52

    Re: Updating a progress bar from within a user interface thread

    OK will go with WM_APP. I was always using WM_USER. Guy learns something every day.

    But timer is better way then PostMessage.

  8. #8
    John E is offline Elite Member Power Poster
    Join Date
    Apr 2001
    Location
    Manchester, England
    Posts
    4,867

    Re: Updating a progress bar from within a user interface thread

    As Victor points out, your whole strategy of trying to create a new instance of the main dialog is never going to work (even if you switch to WM_APP). One way to proceed might be that when you set up the worker thread, you pass it a HWND corresponding to the main dialog's window handle. However, the way that I normally do this (for no particular reason) is to find the parent dialog by knowing its title. Therefore, within the worker thread I might have a function that looks like this:-

    Code:
    void MyWorkerThread::UpdateProgress(/*some parameters here, if necessary */)
    {
    	 // Find the main window and tell it to display our feedback message.
    	HWND hWnd = ::FindWindowEx(NULL, NULL, _T("#32770"), _T("Your main window's title"));
    	if (hWnd)
    		::PostMessage(hWnd, UM_UPDATE_FEEDBACK, (WPARAM)0 /* some param, if necessary */, (LPARAM)0 /* some param, if necessary */);
    }
    Then, within the main dialog, you need to set up a function to receive the message, like this:-

    Code:
    BEGIN_MESSAGE_MAP(CMyProgressDlg, CDialog)
    	//{{AFX_MSG_MAP(CMyProgressDlg)
    	ON_MESSAGE(UM_PROGRESS_BAR, OnUpdateProgress)
    	//}}AFX_MSG_MAP
    END_MESSAGE_MAP()
    
    
    LRESULT CMyProgressDlg::OnUpdateProgress(WPARAM wParam, LPARAM lParam)
    {
    	// whatever you need to update the progress bar
    
    	return 0;
    }
    "A problem well stated is a problem half solved.” - Charles F. Kettering

  9. #9
    Arjay's Avatar
    Arjay is offline Moderator / EX MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    13,490

    Re: Updating a progress bar from within a user interface thread

    See the post Sharing a thread safe std::queue w/progress bar update in my subject line.

    Contained within the post is a sample app that creates two worker threads that push data onto a queue. The UI thread pops the data off the queue and puts the data into a listbox. Each thread Posts a message to the UI thread to update a progress bar for each thread.

    This sample allows starting, pausing, resuming, and stopping the threads. It also creates a class that shows how to encapsulate the thread creation/synchronization related operations.

  10. #10
    Join Date
    Apr 2005
    Posts
    41

    Talking Re: Updating a progress bar from within a user interface thread

    Thanks John_E

    I have done what you suggested and the code works well now. For completeness (just in case anyone else had the same question as me), here is the function I wrote to communicate with the main dialog was as follows (note the main dialog window title is "F.C.M."):

    void CalcThread::ProgressBar()
    {
    HWND hWnd = :: FindWindowEx(NULL,NULL,_T("#32770"),_T("F.C.M."));
    if(hWnd) ::PostMessage(hWnd,WM_PROGRESS_BAR,NULL,NULL);
    }

    I then included a new "message map" item in the main dialog class which then runs the function On_Progress in CFCMDlg (FCMDlg.cpp):

    BEGIN MESSAGE_MAP(CFCMDlg, CDialog)
    ON_MESSAGE ( WM_PROGRESS_BAR, On_Progress )
    // {{AFX_MSG_MAP(CFCMDlg)
    END_MESSAGE_MAP()

    Many thanks

    Robbie

  11. #11
    VictorN's Avatar
    VictorN is offline Super Moderator Power Poster
    Join Date
    Jan 2003
    Location
    Hanover Germany
    Posts
    20,430

    Re: Updating a progress bar from within a user interface thread

    Note that using ::FindWindow(Ex) is not good and is usually unsafe, since there might be more than one dialog windows with the same title.
    Besides, ::FindWindow(Ex) implicitely calls GetWindowText which:
    ... If the target window is owned by the current process, GetWindowText causes a WM_GETTEXT message to be sent to the specified window or control. If the target window is owned by another process and has a caption, GetWindowText retrieves the window caption text. If the window does not have a caption, the return value is a null string. This behavior is by design. It allows applications to call GetWindowText without hanging if the process that owns the target window is hung. However, if the target window is hung and it belongs to the calling application, GetWindowText will hang the calling application.
    So, passing target window handle to the thread would be, IMHO, the best way.
    Victor Nijegorodov

  12. #12
    Join Date
    Apr 2005
    Posts
    41

    Re: Updating a progress bar from within a user interface thread

    Although the current solution works quite adequately, I can see potential issues with it under certain cicrumstances.

    I would like to do what you say but would not know where to start with the coding.

    If it is not too much bother, could you supply a short bit of code to replace the function void CalcThread::ProgressBar()?

    Thanks Victor

  13. #13
    Join Date
    Jul 2008
    Location
    Croatia
    Posts
    52

    Re: Updating a progress bar from within a user interface thread

    I have created small project to demonstrate how I work with threads. I would like to know is there any simpler, faster, shorter code. Also I would like to know are there any bugs in this code. So here it goes.

    Creating project
    1. New Project
    2. Under Name write WorkerThreadDemo and click OK
    3. Click Next, select Dialog based and click Next
    4. Uncheck About box and click Finish

    Editing dialog
    1. Right click on TODO: Place dialog ... and select Add variable, under Name write m_lblCounter and click Finish
    2. Right click on TODO: Place dialog ... and select properties, under ID write IDC_COUNTER and delete text under Caption
    3. Change Caption for OK to Start and change caption for Cancel to Stop
    4. In dialog editor double click button Start
    5. Return in editor and double click button Stop

    Adding CMyThread class
    1. Go to Project / Add Class...
    2. Select MFC / MFC Class and click Add
    3. Under Class name write CMyThread
    4. Under Base class select CWinThread and click Finish

    Adding CMyThreadData class
    1. Go to Project / Add Class...
    2. Select C++ / C++ Class and click Add
    3. Under Class name write CMyThreadData and click Finish

    Writeing code for CMyThreadClass
    With access functions we can protect variables from reading and writeing simultaneously. This is done by MFC object CCriticalSection which is part of "afxmt.h". To make our life a litle easyer we are going to define a macro which will write functions Get and Set automaticly. In order for this to work you must not use arrays and pointers. If you use poiners, you are accessing object pointed by that pointer directy whitout protection of CCriticalSection. So because off that use class with overloaded operator=. If class don't have overloaded operator= then when class object is assigned to another object (like in our function Get), pointers are copyed, and then both classes point to the same place in memory, and that is always a bad thing. Class should overload operator=, allocate new memory and copy memory from one pointer to another.

    1. In file "MyThreadData.h" below "#pragma once" write:

    Code:
    #include "stdafx.h"
    #include <afxmt.h>
    
    #define MAKE_CS_PROTECTED(TYPE, VAR_NAME)								\
    	private:											\
    		TYPE m_##VAR_NAME;									\
    	public:												\
    		TYPE Get##VAR_NAME(){ TYPE VAR_NAME; m_cs.Lock();	VAR_NAME = m_##VAR_NAME;	\
    			m_cs.Unlock(); return VAR_NAME; }						\
    		void Set##VAR_NAME(TYPE VAR_NAME){ m_cs.Lock(); m_##VAR_NAME = VAR_NAME;		\
    			m_cs.Unlock(); }	
    
    #define GETDATA(MEMBER)							Get##MEMBER()
    #define SETDATA(MEMBER, VALUE)						Set##MEMBER(VALUE)
    2. Now in class cMyThreadData after default constructor and destructor define some varables.
    Code:
    	MAKE_CS_PROTECTED(LONG,		Number)
    	MAKE_CS_PROTECTED(CString,	String) //CString have overloaded operator=
    private:
    	CCriticalSection m_cs;
    Writing code for CMyThread
    A thread have to do four things. Do some work, exit when finished, terminate on demand and send message when it finishes.

    1. Open file "MyThread.h" and after "#pragma once" include "MyThreadData.h"
    2. In class CMyThread write
    Code:
    private:
    	void DoSomeWork();
    public:
    	CMyThreadData m_Data;
    	HWND m_hWnd;
    	HANDLE m_FinishNow;
    	UINT m_FinishMessage;
    
    public:
    	CMyThread();
    	virtual ~CMyThread();
    3. Open file "CMyThread.cpp", find "InitInstance" function and change it to look like this
    Code:
    BOOL CMyThread::InitInstance()
    {
    	// TODO:  perform and per-thread initialization here
    	DoSomeWork();
    
    	return FALSE;
    }
    4. Find "ExitInstance" function and add before "return" this
    Code:
    PostMessage(m_hWnd, m_FinishMessage, NULL, NULL);
    5. Add function DoSomeWork()
    Code:
    void CMyThread::DoSomeWork()
    {
    	BOOL FinishNow = FALSE;
    	LONG i = m_Data.GETDATA(Number);
    	CString str = m_Data.GETDATA(String);
    	CString tmpStr = _T("");
    
    	do
    	{
    		tmpStr.Format(_T("%s %u"), str, i++);
    
    		m_Data.SETDATA(String, tmpStr);
    		m_Data.SETDATA(Number, i);
    
    		switch (WaitForSingleObject(m_FinishNow, 0))
    		{
    		case WAIT_TIMEOUT:
    			FinishNow = FALSE;
    			break;
    		case WAIT_OBJECT_0:
    			FinishNow = TRUE;
    			break;
    		default:
    			FinishNow = TRUE;
    			break;
    		}
    
    	} while ((i != 0) && !FinishNow);
    }
    Writeing code for CWorkerThreadDemoDlg
    1. Open file "WorkerThreadDemoDlg.h" and after #include "stdafx.h" add this
    Code:
    #include "MyThreadData.h"
    #include "MyThread.h"
    
    #define ON_MY_THREAD_FINISH						WM_APP+1
    2. In class CWorkerThreadDemoDlg add this
    Code:
    private:
    	void CreateThread();
    	void TerminateThread();
    	void DeleteThread();
    
    	CMyThread *m_pMyThread;
    	UINT_PTR m_Timer;
    public:
    	afx_msg void OnTimer(UINT_PTR nIDEvent);
    	afx_msg void OnClose();
    	afx_msg LRESULT OnEndThread(WPARAM wparam, LPARAM lparam);
    3. Open file "WorkerThreadDemoDlg.cpp"
    4. Find class's CWorkerThreadDemoDlg constructor function and change it to look like this
    Code:
    CWorkerThreadDemoDlg::CWorkerThreadDemoDlg(CWnd* pParent /*=NULL*/)
    	: CDialog(CWorkerThreadDemoDlg::IDD, pParent)
    {
    	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    	m_pMyThread = NULL;
    }
    5. Find "BEGIN_MESSAGE_MAP(CWorkerThreadDemoDlg, CDialog)" and change it to look like this
    Code:
    BEGIN_MESSAGE_MAP(CWorkerThreadDemoDlg, CDialog)
    	ON_WM_PAINT()
    	ON_WM_QUERYDRAGICON()
    	//}}AFX_MSG_MAP
    	ON_BN_CLICKED(IDOK, &CWorkerThreadDemoDlg::OnBnClickedOk)
    	ON_BN_CLICKED(IDCANCEL, &CWorkerThreadDemoDlg::OnBnClickedCancel)
    	ON_WM_TIMER()
    	ON_WM_CLOSE()
    	ON_MESSAGE(ON_MY_THREAD_FINISH, &CWorkerThreadDemoDlg::OnEndThread)
    END_MESSAGE_MAP()
    6. Find OnBnClickedOk function and add CreateThread();
    7. Find OnBnClickedCancel function and add TerminateThread();
    8. Add this code to the end
    Code:
    afx_msg void CWorkerThreadDemoDlg::OnClose()
    {
    	TerminateThread();
    
    	CDialog::OnOK();
    }
    
    afx_msg LRESULT CWorkerThreadDemoDlg::OnEndThread(WPARAM wparam, LPARAM lparam)
    {
    	OnTimer(0);
    
    	DeleteThread();
    	
    	return 0;
    }
    
    afx_msg void CWorkerThreadDemoDlg::OnTimer(UINT_PTR nIDEvent)
    {
    	if (m_pMyThread) 
    	{
    		m_lblCounter.SetWindowText(m_pMyThread->m_Data.GetString());
    	}
    }
    
    void CWorkerThreadDemoDlg::CreateThread()
    {
    	if (m_pMyThread) TerminateThread();
    
    	m_pMyThread = new CMyThread;
    	if (!m_pMyThread) return;
    
    	m_pMyThread->m_bAutoDelete = FALSE;
    	m_pMyThread->m_hWnd = this->m_hWnd;
    	m_pMyThread->m_FinishMessage = ON_MY_THREAD_FINISH;
    	m_pMyThread->m_FinishNow = CreateEvent(NULL, TRUE, FALSE, NULL);
    
    	//We are sending thread some start data
    	m_pMyThread->m_Data.SetNumber(1000);
    	m_pMyThread->m_Data.SetString(_T("Thread is Counting. Numer is "));
    	
    	m_pMyThread->CreateThread();
    
    	m_Timer = SetTimer(NULL, 40, NULL);
    }
    
    void CWorkerThreadDemoDlg::TerminateThread()
    {
    	MSG msg;
    
    	if(m_pMyThread)
    	{
    		SetEvent(m_pMyThread->m_FinishNow);
    
    		//Waiting for FinishMessage from thread
    		do
    		{
    			GetMessage(&msg, NULL, 0, 0);
    
    			TranslateMessage(&msg); 
    			DispatchMessage(&msg);
    		} while (m_pMyThread);
    	}
    }
    
    void CWorkerThreadDemoDlg::DeleteThread()
    {
    	if(m_pMyThread)
    	{
    		SetEvent(m_pMyThread->m_FinishNow);
    		
    		//Waiting for thread to finish
    		WaitForSingleObject(m_pMyThread->m_hThread, INFINITE);
    		
    		delete m_pMyThread; m_pMyThread = NULL;
    		
    		KillTimer(m_Timer);
    	}
    }
    I didn't expect for this to turnout so big. It was sopouse to be small. Sorry.

  14. #14
    VictorN's Avatar
    VictorN is offline Super Moderator Power Poster
    Join Date
    Jan 2003
    Location
    Hanover Germany
    Posts
    20,430

    Re: Updating a progress bar from within a user interface thread

    Quote Originally Posted by robbiegregg
    I would like to do what you say but would not know where to start with the coding.
    I already gave you two very useful links. See the post#4.
    Victor Nijegorodov

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured