|
-
July 29th, 2008, 02:04 AM
#1
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
-
July 29th, 2008, 03:44 AM
#2
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
-
July 29th, 2008, 11:51 AM
#3
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();")
-
July 29th, 2008, 12:08 PM
#4
Re: Updating a progress bar from within a user interface thread
 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.
 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
-
July 29th, 2008, 12:37 PM
#5
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).
-
July 29th, 2008, 12:54 PM
#6
Re: Updating a progress bar from within a user interface thread
 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
-
July 29th, 2008, 01:06 PM
#7
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.
-
July 29th, 2008, 01:28 PM
#8
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
-
July 29th, 2008, 04:04 PM
#9
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.
-
July 30th, 2008, 02:05 AM
#10
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
-
July 30th, 2008, 03:08 AM
#11
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
-
July 30th, 2008, 04:25 PM
#12
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
-
July 30th, 2008, 04:32 PM
#13
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.
-
July 31st, 2008, 05:32 AM
#14
Re: Updating a progress bar from within a user interface thread
 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
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|