RedrawWindow() problems...
I wrote a program that displays information about the program while it does other things. But, when the application's window becomes inactive the display stops working until the application is finished. Below I put my program with only a counter (I gutted all of the fun stuff). If you can help I would be so grateful. This used to work on a Windows 95 system, but now doesn't work on a Windows XP system. Also, all of the beginning stuff MSVC++ automatically did, my stuff is only the last few lines at the bottom (the Listen() function). thanks so much.
Code:
// rfcontrolDlg.cpp : implementation file
//
#include "stdafx.h"
#include "rfcontrol.h"
#include "rfcontrolDlg.h"
#include <fstream.h>
#include <math.h>
///////////////////////////////////////////////////////////////////////////////////////
#include <stdlib.h>
#include <string>
#include <stdio.h>
#include <iostream>
#include <windows.h>
#include "Decl-32.h"
#include <ctime>
#include <sys/timeb.h> //Time header that gives ms resolution.
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CRfcontrolDlg dialog
CRfcontrolDlg::CRfcontrolDlg(CWnd* pParent /*=NULL*/)
: CDialog(CRfcontrolDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CRfcontrolDlg)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CRfcontrolDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CRfcontrolDlg)
DDX_Control(pDX, IDC_MSCOMM1, m_comm);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CRfcontrolDlg, CDialog)
//{{AFX_MSG_MAP(CRfcontrolDlg)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(ID_Load, OnLoad)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CRfcontrolDlg message handlers
BOOL CRfcontrolDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
return TRUE; // return TRUE unless you set the focus to a control
}
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CRfcontrolDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}
// The system calls this to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CRfcontrolDlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
}
void CRfcontrolDlg::OnLoad()
{
Listen();
return;
}
void CRfcontrolDlg::Listen()
{
CEdit *pSetText = (CEdit*)GetDlgItem(IDC_EDIT2);
CString sDisplay;
int j=0, i; //for debugging purposes
for(i=0;i<20;++i) { //debugging code
// for(;;) {
// debugging code:
sDisplay.Format("counter= %i", j);
pSetText->SetWindowText(sDisplay);
//pSetText->RedrawWindow(0,0,RDW_UPDATENOW|RDW_ALLCHILDREN);
// this->RedrawWindow();
// CPaintDC dc(this); // device context for painting
// pSetText->RedrawWindow();
// Invalidate();
pSetText->UpdateWindow();
// CDialog::OnPaint();
j++; //for debugging purposes
Sleep(1000); //waits 10 ms before checking the Comm Port, trying to decrease load on processor
}
}
Re: RedrawWindow() problems...
You have an infinite loop in a button click handler. So, you application is trapped inside the handler, which does not give it time to do anything else, such as processing WM_PAINT messages and the like.
Think about multi-threading or pumping messages. See these threads:
http://www.codeguru.com/forum/showthread.php?t=358149
http://www.codeguru.com/forum/showthread.php?t=361093
Are you certain that the exact code you posted worked previously in Win 95? It doesn't look like the code should work in any flavor of Windows.
Mike
Re: RedrawWindow() problems...
The code definately worked before in Windows 95. The infinite loop is there on purpose. I need it to monitor the comm port for signals (not in this code), but it does exit the infinite monitoring in certain situations. I know that sleep() makes the processor pause before it proceeds, but is there a function that makes sure all paint commands have been processed while the program waits to proceed? I think it's so strange that it goes on listening before it paints the window. Is there some issue about the priority of functions? I'm still new at c++.
Re: RedrawWindow() problems...
I don't know anything about threads, but after looking at the postings you referenced you're right, I'm having the same problem and need to use a thread (one of my side affects of the sleep() is also that I can't move the window once I run it, but I thought they were unrelated). But I don't know anything about a user thread. Could you point me in the right direction about instructions on how to use them?
Re: RedrawWindow() problems...
Quote:
Originally Posted by tribesman1693
The infinite loop is there on purpose. I need it to monitor the comm port for signals (not in this code), but it does exit the infinite monitoring in certain situations. I know that sleep() makes the processor pause before it proceeds, but is there a function that makes sure all paint commands have been processed while the program waits to proceed? I think it's so strange that it goes on listening before it paints the window. Is there some issue about the priority of functions? I'm still new at c++.
No, such an infinite loop in the main thread of a Windows application can never work. It will always block the UI thread from receiving messages - paint messages as well as other messages, so the application will not respond to user actions any more. Calling Sleep() will not make the situation any better: It will just concede CPU time to other threads (which you don't have), but still block your own main UI thread from doing anything.
The various commented-out RedrawWindow(), Invalidate() and OnPaint() calls in your button handler show that you are rather desperately guessing and trying until something at least seems to work, without really understanding what's going on. You never call OnPaint() yourself - it's always called in reaction to WM_PAINT messages. Likewise, you shouldn't create a CPaintDC anywhere else than in a WM_PAINT handler. Invalidate() is the correct call to inform Windows that something has changed and you would like to receive WM_PAINT calls to update your UI. However, this will only work if your thread is not trapped inside an infinite loop, preventing it from processing any messages. And UpdateWindow() only has an effect if the invalid region is not empty, it will then force an immediate WM_PAINT message. So while the combination of Invalidate() and UpdateWindow() might seem to work in your example and at least draw something to the Window, your app will still be blocked and not respond to any user action (like clicking or moving a window).
The only valid solution to your problem is to create a worker thread for collecting the data, and have that thread send user-defined messages to your window whenever there's something to redraw.
Re: RedrawWindow() problems...
You are competely correct gstercken, I am rather desperately trying everything I can find to fix the problem. MikeAThon's references to creating threads and the need for them explains all the symptoms I've ever had with my program. But, I don't know anything about threads, how to make them, arguments, etc. I looked on MSDN.com about them, but their explanations are less than satisfying for me. Could you give me an example of how, for instance, to make a calculation (in my loop: j+j) and display the outcome of the calculation every time the loop executes.
By the way I don't think my for(i=0;i<20;++i) loop is infinite. It only executes 20 times and then ends. But, I do want an infinite loop so everything you've both said is very pertinent to me.
Re: RedrawWindow() problems...
Quote:
Originally Posted by tribesman1693
The code definately worked before in Windows 95. The infinite loop is there on purpose. I need it to monitor the comm port for signals (not in this code), but it does exit the infinite monitoring in certain situations.
I truly find it hard to believe that this code ever worked on any version of Windows. Are you certain that there have not been any changes? An infinite loop in a button click handler will always freeze the UI, unless you're pumping messages somewhere inside the loop.
Quote:
Originally Posted by tribesman1693
I know that sleep() makes the processor pause before it proceeds...
Calling sleep() is usually a bad idea, and calling sleep() inside a UI thread is almost always a bad idea. Its presence usually indicates that there's a problem with the code that was never resolved, but the symptoms can be hidden, sorta, most of the time but not all the time, when the call to sleep() is included.
Quote:
Originally Posted by tribesman1693
but is there a function that makes sure all paint commands have been processed while the program waits to proceed?
There's no simple API function, but you can periodically pump messages while waiting, from a loop that's bounded by PeekMessage(). That's what I meant above when I mentioned "pumping messages". However, pumping messages is a kind of "poor man's" multithreading that dates back to Windows 3.0 (i.e., before Windows 95 which finally implemented pre-emptive multi-threading into the OS) and should not be used in the situation you describe here, if possible.
Quote:
Originally Posted by tribesman1693
I think it's so strange that it goes on listening before it paints the window. Is there some issue about the priority of functions? I'm still new at c++.
I don't understand what you mean by this, "goes on listening"
Quote:
Originally Posted by tribesman1693
But I don't know anything about a user thread. Could you point me in the right direction about instructions on how to use them?
Search this forum (use the "search" button above) for "serial comm port thread" and you will see others that have tried to do what you're trying to do. The API WaitForCommEvent() will be helpful to you inside a worker thread; see http://msdn.microsoft.com/library/en..._functions.asp
Also, PJ NAughter's web site has a nice wrapper class CSerialPort for serial port communication: http://www.naughter.com/serialport.html . Study his code.
Mike
Re: RedrawWindow() problems...
I'm going to go follow your references to learn about multi-threading. I'll let you know how things go...
Re: RedrawWindow() problems...
I'm working with tribesman on this, I hope you all can keep helping.
I think we can figure out the worker thread business, and that does seem like the right solution. But here is the difficulty:
What we're trying to do is write a program that monitors the comm channel for a signal (consisting of a single character). When it gets a signal, it is supposed to display what it got, and run a routine keyed on what it got.
So we can start a worker thread to monitor the comm port. And I see how to make it post a message that it has received data, using the ::PostMessage function. The user thread could then update the display and run the keyed routine.
But how do we get the worker thread to communicate the value of the character received? The only examples I've seen use the pParam object to return data, but it seems like we need to set pParam to hwnd in order for the message posting to work. Could we just return the key value and use ::GetExitCodeThread(), or is there a better way?
Re: RedrawWindow() problems...
Quote:
Originally Posted by csackett
So we can start a worker thread to monitor the comm port. And I see how to make it post a message that it has received data, using the ::PostMessage function. The user thread could then update the display and run the keyed routine.
Yes, although you don't need to post the message (asyncronously) using PostMessage(). SendMessage() will work as well and give you an immediate display update.
Quote:
Originally Posted by csackett
But how do we get the worker thread to communicate the value of the character received? The only examples I've seen use the pParam object to return data, but it seems like we need to set pParam to hwnd in order for the message posting to work. Could we just return the key value and use ::GetExitCodeThread(), or is there a better way
Not sure what you mean here: Both PostMessage() and SendMessage() expect an HWND (the handle of the target window) plus a WPARAM and an LPARAM, which you can use for whatever you like. For example, you could pass a pointer to any arbitrary, complex data structure. Your case is extremely simple: Since you need to pass only a character, use either wParam or lParam, cast the character value, and cast it back accordingly when you receive and handle the message.
Re: RedrawWindow() problems...
Ah. So that is what those are for. I think I see how it should work then, thanks.
Re: RedrawWindow() problems...
Maybe I'll figure this out myself before someone replies, but is there an obvious reason why, when I call
Code:
AfxBeginThread(ListenThreadProc, dlg);
where I have defined the function
Code:
UINT CRfcontrolDlg::ListenThreadProc(LPVOID pvParam)
I am getting an error:
Code:
error C2665: 'AfxBeginThread' : none of the 2 overloads can convert parameter 1 from
type 'unsigned int (void *)'
Thanks.
Re: RedrawWindow() problems...
static UINT CRfcontrolDlg::ListenThreadProc(LPVOID pvParam)
Is your thread function a "static"member of the class?
Mike
Re: RedrawWindow() problems...
Heh, I got it about 5 minutes ago :) Thanks again, though!
Re: RedrawWindow() problems...
okay, I seem to be further from understanding this stuff than I hoped.
I have something that compiles. The three main functions are
Code:
void CRfcontrolDlg::OnLoad()
{
// this is a button handler. It reads a name
// from an edit box, opens a file, loads some data, then:
AfxBeginThread (ListenThreadProc, this);
return;
}
and
Code:
UINT CRfcontrolDlg::ListenThreadProc(LPVOID pvParam)
{
CRfcontrolDlg *ts = (CRfcontrolDlg*)pvParam;
// eventually will get data from COM1, for now fake:
char key = 'z';
::PostMessage(ts->m_hWnd,WM_MYDATA_RX,(WPARAM)key,0);
return 0;
}
and
Code:
void CRfcontrolDlg::OnDataRX(LPARAM lParam, WPARAM wParam)
{
char key = (char)wParam;
CString sOut;
sOut.Format("In OndataRX, key = %c",key);
m_status.SetWindowText ( "Ready" );
return;
}
When I run it, I get my dialog box, but as soon as I click in the first box to enter the filename, the status box displays "In OnDataRX, key=". I haven't even hit the Load button yet, so I don't see why the worker thread would be starting. Any help? I'm not really clear how to use the debugger for something like this.
Edit:
Maybe this is relevant too, my message map is
Code:
#define WM_MYDATA_RX WM_USER
BEGIN_MESSAGE_MAP(CRfcontrolDlg, CDialog)
//{{AFX_MSG_MAP(CRfcontrolDlg)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(ID_Load, OnLoad)
//}}AFX_MSG_MAP
ON_MESSAGE(WM_MYDATA_RX, OnDataRX)
END_MESSAGE_MAP()
Re: RedrawWindow() problems...
Quote:
Originally Posted by csackett
When I run it, I get my dialog box, but as soon as I click in the first box to enter the filename, the status box displays "In OnDataRX, key=". I haven't even hit the Load button yet, so I don't see why the worker thread would be starting.
That can't be with the code you posted. You are creating a CString with that text, but setting the status text to "Ready". Please post your actual code.
Quote:
Originally Posted by csackett
I'm not really clear how to use the debugger for something like this.
It's essential that you learn using the debugger.
Re: RedrawWindow() problems...
Very sorry about that. Here an exact listing of the code I'm trying (except for comments).
Code:
#define WM_MYDATA_RX WM_USER
BEGIN_MESSAGE_MAP(CRfcontrolDlg, CDialog)
//{{AFX_MSG_MAP(CRfcontrolDlg)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(ID_Load, OnLoad)
//}}AFX_MSG_MAP
ON_MESSAGE(WM_MYDATA_RX, OnDataRX)
END_MESSAGE_MAP()
void CRfcontrolDlg::OnLoad()
{
m_status.SetWindowText("Listening");
AfxBeginThread (ListenThreadProc, this);
return;
}
UINT CRfcontrolDlg::ListenThreadProc(LPVOID pvParam)
{
CRfcontrolDlg *ts = (CRfcontrolDlg*)pvParam;
char key = 'z';
::PostMessage(ts->m_hWnd,WM_MYDATA_RX,(WPARAM)key,0);
return 0;
}
void CRfcontrolDlg::OnDataRX(LPARAM lParam, WPARAM wParam)
{
char key = (char)wParam;
CString sOut;
sOut.Format("In OndataRX, key = %c",key);
m_status.SetWindowText(sOut);
return;
}
When I run it, my dialog box comes up. If I click on any control in the window, the status box displays ""In OndataRX, key =". It does this even if the AfxBeginThread command is commented out, so I must be doing something wrong with the message handling.
I will try to read up on how to use the debugger with threads and messages.
Re: RedrawWindow() problems...
Quote:
Originally Posted by csackett
When I run it, my dialog box comes up. If I click on any control in the window, the status box displays ""In OndataRX, key =". It does this even if the AfxBeginThread command is commented out, so I must be doing something wrong with the message handling.
I can't tell from your code why the message handler function would be called - maybe WM_USER is sent from some other place in your app?
You shouldn't use WM_USER, but add some constant to it. WM_USER is just the lower bound of a range if user-defined message ids. Also, you should be using WM_APP rather then WM_USER for messages which are private to your application. The WM_USER range should be used for communicating between applications. So your message ID definition should be something like
Code:
#define WM_MYDATA_RX (WM_APP + 1)
Besides that, your message handler is incorrectly typed. The correct prototype for your message handlers would be
Code:
afx_msg LRESULT OnDataRX(WPARAM, LPARAM);
Yours is returning void, not LRESULT.
Re: RedrawWindow() problems...
That part is working now, thanks so much.
Re: RedrawWindow() problems...
OK, I have a working skeleton that seems to do everything I want. I thought I'd post it to make sure I'm not doing something wrong that will come back to bite me.
I'm using Ashish Dhar's SerialCommHelper class, that I got from here.
Here's the class declaration:
Code:
#include "SerialCommHelper.h"
class CComanderDlg : public CDialog
{
// Construction
public:
CComanderDlg(CWnd* pParent = NULL); // standard constructor
// Stuff I added:
CSerialCommHelper m_theCommPort;
static UINT ListenThreadProc( LPVOID pvParam );
afx_msg LRESULT OnDataRX(WPARAM, LPARAM);
HANDLE hStopEvent;
// Dialog Data
//{{AFX_DATA(CComanderDlg)
enum { IDD = IDD_COMANDER_DIALOG };
CEdit m_status;
CEdit m_filename;
//}}AFX_DATA
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CComanderDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
// Implementation
protected:
HICON m_hIcon;
// Generated message map functions
//{{AFX_MSG(CComanderDlg)
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnButtonLoad();
afx_msg void OnButtonStop();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
And here's the code:
Code:
#define WM_MYDATA_RX (WM_APP + 1)
BEGIN_MESSAGE_MAP(CComanderDlg, CDialog)
//{{AFX_MSG_MAP(CComanderDlg)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON_LOAD, OnButtonLoad)
ON_BN_CLICKED(IDC_BUTTON_STOP, OnButtonStop)
//}}AFX_MSG_MAP
ON_MESSAGE(WM_MYDATA_RX, OnDataRX)
END_MESSAGE_MAP()
BOOL CComanderDlg::OnInitDialog()
{
CDialog::OnInitDialog();
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
m_theCommPort.Init ("COM1",2400,0,1,8);
m_theCommPort.Start ();
m_filename.SetWindowText ( "default.txt" );
m_status.SetWindowText ( "Ready" );
hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
return TRUE;
}
void CComanderDlg::OnButtonLoad()
{
m_status.SetWindowText("Listening");
AfxBeginThread (ListenThreadProc, this);
return;
}
// The worker thread:
UINT CComanderDlg::ListenThreadProc(LPVOID pvParam)
{
CComanderDlg *ts = (CComanderDlg*)pvParam;
std::string szJunk, szData;
// Delete any data received before start:
ts->m_theCommPort.ReadAvailable (szJunk);
DWORD dw;
while(szData.length()==0) {
dw = WaitForSingleObject(ts->hStopEvent,10);
if(dw == WAIT_OBJECT_0) // True if Stop button pushed
return 1;
ts->m_theCommPort.ReadAvailable (szData);
}
::PostMessage(ts->m_hWnd,WM_MYDATA_RX,(WPARAM)(szData[0]),0);
return 0;
}
afx_msg LRESULT CComanderDlg::OnDataRX(WPARAM wParam, LPARAM lParam)
{
char key = (char)wParam;
CString sOut;
sOut.Format("In OndataRX, key = %c",key);
m_status.SetWindowText(sOut);
AfxBeginThread(ListenThreadProc, this); // Go back to listening
return 0;
}
void CComanderDlg::OnButtonStop()
{
SetEvent(hStopEvent); // worker thread checks for this
m_status.SetWindowText("Ready");
}
Any comments are appreciated, but in any case thanks again for all the help.
Re: RedrawWindow() problems...
At a first glance, I'm wondering about three things:- Why are all those members (theCommPort, hStopEvent etc.) public?
- You should adhere to a consistant naming scheme: Either prefix all of your member variables with m_, or none.
- Why are you mixing CString and std::string in an MFC application?
Re: RedrawWindow() problems...
Quote:
Originally Posted by gstercken
Why are all those members (theCommPort, hStopEvent etc.) public?
I'll change it. Though I'll confess I don't understand the importance of the public/private distinction for a class that's for your own personal use. (Ouch, there I just slapped my hand with a ruler.)
Quote:
You should adhere to a consistant naming scheme: Either prefix all of your member variables with m_, or none.
OK, I'll fix that too. So it would be "m_hStopEvent"?
Quote:
Why are you mixing CString and std::string in an MFC application?
Actually, I'd appreciate your advice on that. Ashish Dhar's class uses std::string for it's inputs, but I'm used to using CString. I assume CString is preferred in MFC? Is there a better way to handle it? I'd just as soon not start tinkering with Dhar's class. I guess I could try PJ Naughter's instead, but it was a bit shorter on explanation.
Re: RedrawWindow() problems...
Quote:
Originally Posted by csackett
OK, I'll fix that too. So it would be "m_hStopEvent"?
Yes.
Quote:
Actually, I'd appreciate your advice on that. Ashish Dhar's class uses std::string for it's inputs, but I'm used to using CString. I assume CString is preferred in MFC? Is there a better way to handle it? I'd just as soon not start tinkering with Dhar's class. I guess I could try PJ Naughter's instead, but it was a bit shorter on explanation.
Usually try to stick with one: either CString or std::string. If it's a MFC application, I would tend to use CString, if it's not std::string.
Re: RedrawWindow() problems...
Quote:
Originally Posted by csackett
Though I'll confess I don't understand the importance of the public/private distinction for a class that's for your own personal use.
Well, it doesn't depend on who uses the class - except of course if you prefer to suffer from trouble that you would like to save other developers from. Designing your classes in a way that doesn't break up encapsulation helps preventing errors - even your own. Or would you build a car with defective brakes - just for your own, personal use?