Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.
28629 Discussions

Screen won't repaint after using Excel

michael_green
Beginner
1,642 Views
Hi all,
My application has a bitmap backgound and I use a series of dialogs for user input. One dialog shells out to an Excel template where some VB code ultimately writes a file before closing and returning to the dialog. At this point I'd like to see the dialog and its progress bar, but all I see is a white filled outline of it on a white background. The program still works OK and when it's done, the screen refreshes without my ever having seen the progress bar in action.
If I insert a pointless message box (click OK to continue) immediately on return from Excel, everything works just fine and I see the progress bar in operation.
What am I not doing that I should be doing?
With many thanks in advance,
Mike
0 Kudos
32 Replies
Jugoslav_Dujic
Valued Contributor II
1,183 Views
I'm not sure what exactly was wrong, but you don't seem to process messages (namely, WM_PAINT), so the repainting doesn't take place. MessageBox is a modal dialog which has a message loop of its own so it seems to fix the problem for a while; however, what happens when you hide & reveal your progress bar with another window briefly? Does it get updated? You can watch for WM_PAINT messages for your dialogs using Spy++.

Search this forum for PeekMessage loop; such a loop should be called every while from a code which does a long calculation. I'm not sure if the problem comes because of Excel -- what exactly do you mean by "Shells out Excel template"? How do you "shell it out"? If you wait for it using WaitForSingleObject, you stale your own thread -- in that case, you still need a message loop instead -- Google MsgWaitForMultipleObjects (that stuff lets you wait for a sync object handle and process messages in the meantime).

Jugoslav
0 Kudos
michael_green
Beginner
1,183 Views
Hi Jugoslav,
Thanks for your help on this. I'm a little out of my depth here but I'll try to explain the problem more carefully ...
This is a pure Windows application. The program uses a dialog to get some information from the user. It then uses CreateProcess to run an Excel session which writes a file needed by the main program. On return from Excel the dialog runs a DLL to do some number crunching and gives the user some job feedback via a progress bar. The dialog does indeed use WaitForSingleObject.
When Excel returns the parts of the screen formerly occupied by Excel and the dialog are white until the job finishes. It makes no difference if I temporarily hide the white area with another window, however, if I insert a pointless message box before running the DLL everything works fine.
It's true the dialog does not process the WM_PAINT message - something I have never had to do before in a dialog. What action should I take on WM_PAINT in a dialog?
I have had a look at MsgWaitForMultipleObjects and PeekMessage as you suggest but I have to admit I don't understand it well enough to be able to apply it. Do you have any other suggestions?
With many thanks again,
Mike
0 Kudos
anthonyrichards
New Contributor III
1,183 Views
Any messages you do not process yourself in your dialog procedure, including the WM_PAINT message, should be sent for default processing to DefDlgProc, the default dialog procedure (see the Help for this). Maybe the 'pointless' Messagebox you create sends such a message directly tothe window to which it is attached (viaits first argument, awindow handle)when it is erased, thus solving your redrawing problem.
p.s. If you go straight into your number-crunching when you detect Excel has finished, then just before you do this it may be worthsending a WM_PAINT message to the dialog procedure yourself using SendMessage or Postmessage e.g. Sendmessage (dlg%hWnd, WM_PAINT, 0,0)

Message Edited by anthonyrichards on 09-05-2005 08:11 AM

0 Kudos
michael_green
Beginner
1,183 Views
Hi Anthony,
I think I have tried all the combinations you suggested but without success - possibly because I didn't apply them correctly.
I have attached a stripped down version of the dialog box procedure, complete with pointless message box, which, by the way, forces the screen to repaint as soon as it appears - before I click OK.
I guess there's something missing but I don't know what.
Many thanks in advance for any suggestions.
Mike
0 Kudos
anthonyrichards
New Contributor III
1,183 Views
To your "select case(message).... end select" sequence you should add
CASE DEFAULT
DgntoFMISDlgProc=DefDlgProc(hDlg,message,uParam,lParam)
You should also add, after the return from EXCEL,
iret=SendMessage(hDlg, WM_PAINT, 0,0)
or, possibly
iret=PostMessage(hDlg, WM_PAINT, 0,0)
and see if this works. PostMessage returns immediately, SendMessage returns after the message has been dealt with. I cannot try it for you as I do not have all the code needed to compile the dialog procedure in your attachment into a working program.
0 Kudos
michael_green
Beginner
1,183 Views
I did exactly as you said and found that, on entry, the dialog box procedure goes immediately to CASE DEFAULT where a stack overflow occurs. This didn't surprise me because I had read up on DefDlgProc following your earlier advice and found ...
"The DefDlgProc function must not be called by a dialog box procedure; doing so results in recursive execution. "
Thanks again for your help, I really appreciate the time you've taken over my problem.
Mike
0 Kudos
anthonyrichards
New Contributor III
1,183 Views
Sorry about that! I am used to using DefWindowProc.
Then perhaps try removing the CASE DEFAULT and still send the WM_PAINT message (after commenting out the commands to create the 'redundant' message box). If your dialog is repainted OK whenclosing the 'redundant' messagebox, something must be handlinga WM_PAINT or similar message and redrawing the dialog box window for you. This can only be the default window procedure, I think. So it's a case of getting a WM_PAINT message through to it.
0 Kudos
michael_green
Beginner
1,183 Views
Hi Anthony,
Sending the WM_PAINT to the dialog on return from Excel does absolutely nothing. It's not the closing of the redundant message box that does the trick, it's its appearance in the first place - as soon as it appears, the rest of the screen (both the main window's bitmap and the dialog box I'm concerned with) repaints behind it.
Many thanks again
Mike
0 Kudos
anthonyrichards
New Contributor III
1,183 Views
I created a small dialog program with two buttons: one starts EXCEL using your CreateProcess code (including the WaitForSingleObject code), one starts EXCEL using ShellExecuteEx.
With Createprocess, if EXCEL is sized and moved over the dialog, the newly-exposed parts of the dialog window fails to be updated. This is as you would expect as the dialog is waiting for WaitForSingleObject to return and its message loop is suspended. With Shellexecuteex, the dialog window updates correctly, as it is not waiting on a function return.
All your EXCEL creation and subsequent number-crunching occurs on receipt of a WM_COMMAND IDOK message. I can suggest the following: why not return from this after you are satisfied with the output from EXCEL (positive outcome), but just before doing this,post an application-specific message WM_USER+1 to the dialog using PostMessage. This will enable it to get back to its message loop and deal with repaint messages if there are any. It will also put the WM_USER+1 message into the message loop, which you can subsequently process after you haveadded a CASE(WM_USER+1) section to your dialog process, containing the codewhich then does all the other stuff that you have put in yourdo while(.not.OK)...end do loop. You could also usea WM_USER+2 message to indicate other processing based on a negative result from EXCEL, if you wish. Hope this helps.
(P.S. In the above,I had to change Sendmessage to Postmessage, as the Postmessage function returns immediately, whereas SendMessage returns only after the message has been processed, which defeats the whole point).

Message Edited by anthonyrichards on 09-08-2005 06:46 AM

0 Kudos
michael_green
Beginner
1,183 Views
Anthony,
With your help I have solved the problem - thankyou very much. However I still don't fully understand what was going on - by trying many different methods I discovered something that seems significant and would be glad of an explanation. I realised that, on exiting from Excel, the screen really was being repainted all along - it was obvious really because all signs of the excel spreadsheet were being erased. The trouble was, both the main window and the dialog were being repainted white, although I could see the outline and title bar of both.
Anyhow, I split the IDOK action along the lines you suggested, putting the number crunching in a WM_USER+1 section. This initially made no difference, but eventually I found I could force the issue by calling on RedrawWindow immediately before crunching numbers. I had to call it twice, once for the main window and again for the dialog. Now, as the DLL crunches away, it continually updates the progress bar on the dialog as intended.
I'd just love to get a better theoretical understanding of what's going on with regard to the white painting of the main window and dialog box. Any suggestions?
With many thanks for all you help and ideas.
Mike
0 Kudos
anthonyrichards
New Contributor III
1,183 Views
I am glad you have found a work-around. It sure is a can of worms. I note you have a main window that you fill with your own background (a bitmap I believe) so you have its message loop, as well as the dialog's message loop and redrawing to consider. I recommend going back to Jugoslav's earlier suggestions, he knows of which he speaks as is worth listening to.
Have you tried InvalidateRect, specifying the whole window (get the window dimensionsusing GetClientRect and supplying the window handle) ?. From the Help for InvalidateRect:
" hWnd Handle to the window whose update region has changed. If this parameter is NULL, the system invalidates and redraws all windows, and sends the WM_ERASEBKGND and WM_NCPAINT messages to the window procedure before the function returns"

and from the WM_ERASEBKGND help

"The DefWindowProc function erases the background by using the class background brush specified by the hbrBackground member of the WNDCLASS structure. If hbrBackground is NULL, the application should process the WM_ERASEBKGND message and erase the background.

An application should return nonzero in response to WM_ERASEBKGND if it processes the message and erases the background; this indicates that no further erasing is required. If the application returns zero, the window will remain marked for erasing. (Typically, this indicates that the fErase member of the PAINTSTRUCT structure will be TRUE.) "

Since you say yours is a true Windows app., you must specify a background brush when you create the window class, or else a default (probably white) brush is used. Since you use a bit-map background, I guess you have to reload it into the window device context (or into a compatible one in memory and swap it into the window device context). Anyway, good luck!

0 Kudos
Jugoslav_Dujic
Valued Contributor II
1,183 Views
Hi guys,

(As you might have guessed, I was absent for a while :-). ).

Here's an explanation of what is (was?) going on:

You have encountered a common problem of screen updating while performing a long task. Usually, while the task is done, you want the user just to sit and watch what's going on, but because of the way how Windows does window repainting, your own screen can vanish for a while. Namely, in a GUI application you're supposed to run a message loop one way or another, and everything you do in the message processing is supposed to be short. That provides the appearance of a smooth GUI without glitches. The problems emerge when you do need to execute longish things.

First, have in mind that you're (unless you did your own CreateThread) always in a single-threaded application, so things don't happen automagically. Thus, first:

iret = WaitForSingleObject(pi%hProcess,INFINITE)


will do just that -- sit and wait. While it's waiting, you don't do anything else. WM_PAINT messages sitting in the queue (well, not 100% true but close*) will get processed/window redrawn only when you either
a) return from your DlgProc or
b) call GetMessage/DispatchMessage yourself one way or another.
c) I think that InvalidateRect+UpdateWindow will call the dialog procedure directly, but I'm not certain**.

It is the DispatchMessage which actually calls your DlgProc eventually. However, modal dialogs (DialogBox and MessageBox) have embedded message loops. Thus, your call to MessageBox did initiate a message processing, so your DlgProc actually got called from there (yes, recursively) for a WM_PAINT and whatever else got accumulated in the message queue***.

Second, your call rtn_designtofmis(hProgress, hDlg, IDC_task, ghWndMain,...) apparently does some progress bar updating. I'm not sure if the entire dialog is correctly redrawn though.

*) WM_PAINT is actually generated by GetMessage when (a) message queue is otherwise empty and (b) the window has an invalid region. Note the concept of "invalid region", i.e. an area that needs updating.

**) In any case, SendMessage(WM_PAINT) is always futile, as the window has no invalid region. InvalidateRect will invalidate appropriate area, and UpdateWindow will (maybe?) actually call the window procedure with WM_PAINT. RedrawWindow should also work, but I could have never swallowed its documentation so I don't use it.

***) Thus, Tony's PostMessage would also not help in this case, as it will be processed only after you return from DlgProc.

To wait for Excel to complete and redraw the window, you have to use MsgWaitForMultipleObjects loop. Of course, you should take care to disable the OK button and other controls and/or adjust program's logic (see here)

Jugoslav
0 Kudos
michael_green
Beginner
1,183 Views
Hi Jugoslav,
Thanks for your reply. I have made some significant progress but am now faced with another mystery.
I am calling Excel in the following manner:
if(CreateProcess(null,'excel.exe '//fullname1,null,null,false,0,null,null,si,pi)& ==0)then
write(errcode,'(I6)')GetLastError()
call prgerr(0,'Error creating Excel process, error code: '//errcode)
else
ncount = 1
handles(1) = pi%hprocess
iret = MsgWaitForMultipleObjects(ncount,loc(handles),0,INFINITE, & QS_ALLEVENTS)
end if

and this does indeed repaint the screen as required on return from Excel. However, the last thing Excel does before it exits is write a small .csv file which the main program needs for its number crunching. The first thing that the main program does after excel returns is to check that the csv file exists...
call run_dtf_excel(jobname,dgnfile,tlevel,FirstTime,row) !Run Excel
FirstTime = .false.
csvname = trim(fmis$work)//''//jobname//'1.csv'//char(0) !Output from Excel
inquire(file=csvname,exist=FileExists)
When I run the program "at normal speed" the csv file does not exist when the inquire statement is executed, although I can see afterwards from Windows Explorer that it does exist. If I set a debug break on the inquire statement I get the same result, the file does not exist. But if I place the debug break on the FirstTime = .false. statement, then step over the next couple of lines, the file does exist. Is this a timing thing?
If I revert back to WaitForSingleObject and make no other changes, then apart from the screen not updating, the job runs perfectly.
Any suggestions?
With many thanks for your help
Mike
0 Kudos
Jugoslav_Dujic
Valued Contributor II
1,183 Views
But it's not sufficient just to call MsgWaitFor... once -- basically, you need to make a message-loop of it, with a special-case when the sync object (Excel process handle) is raised:
do while (.TRUE.)
iret = MsgWaitForMultipleObjects(1, LOC(handles), TRUE, &
INFINITE, QS_ALLEVENTS)
if (iret .EQ. WAIT_OBJECT_0) then
!Excel is terminated
exit
else
!A message was posted
if (PeekMessage(msg, NULL, 0, 0, PM_REMOVE) .ne. WM_QUIT) then
!An ordinary message
if (.not. IsDialogMessage(hDlg, msg)) then
TranslateMessage(msg)
DispatchMessage(msg)
end if
else
!WM_QUIT message, i.e. your application is closed
!(if it's possible)
exit
end if
end if
end do
Jugoslav
0 Kudos
michael_green
Beginner
1,183 Views
Hi Jugoslav,
I hate to be a pain but it still doesn't work. The following is exactly how I have coded it ...
if(CreateProcess(null,'excel.exe '//fullname1,null,null,false,0,null,null,si,pi)==0)then
write(errcode,'(I6)')GetLastError()
call prgerr(0,'Error creating Excel process, error code: '//errcode)
else
handles(1) = pi%hprocess
loop_a: do while(.true.)
iret = MsgWaitForMultipleObjects(1,loc(handles),TRUE,INFINITE,QS_ALLEVENTS)
if(iret==WAIT_OBJECT_0)then
exit loop_a
else
if(PeekMessage(msg,null,0,0,PM_REMOVE)/=WM_QUIT)then
if(.not.IsDialogMessage(hDlg,msg))then
iret = TranslateMessage(msg)
iret = DispatchMessage(msg)
end if
else
exit loop_a
end if
end if
end do loop_a
end if
and it produces the same result as with WaitForSingleObject, namely a dialog box with title bar and border, but white interior.
Many thanks again for your help
Mike
0 Kudos
Jugoslav_Dujic
Valued Contributor II
1,183 Views
My bad -- PeekMessage does not return the message code, but 0 or "non-zero". Thus, it's never equal to WM_QUIT and code never does a DispatchMessage.

I think it should read:
...
else
do while (PeekMessage(msg,null,0,0,PM_REMOVE).NE.0)
!Special case msg%message for WM_QUIT if you want here.
if(.not.IsDialogMessage(hDlg,msg))then
iret = TranslateMessage(msg)
iret = DispatchMessage(msg)
end if
end do
end if


Jugoslav
0 Kudos
michael_green
Beginner
1,183 Views
Hi Jugoslav,
Still no change. I think it's because the call to MsgWaitForMultipleObjects always returns 0 (i.e. WAIT_OBJECT_0) and therefore the loop always exits immediately.
iret = MsgWaitForMultipleObjects(1,loc(handles),TRUE,INFINITE,QS_ALLEVENTS)
if(iret==WAIT_OBJECT_0)then
exit loop_a
else
if(PeekMessage(msg,null,0,0,PM_REMOVE)/=0)then
if(.not.IsDialogMessage(hDlg,msg))then
iret = TranslateMessage(msg)
iret = DispatchMessage(msg)
end if
else
exit loop_a
end if
end if
Thanks again for your help
Mike
0 Kudos
aliho
Beginner
1,183 Views
In MSDN there is an example for waiting in a message loop.

Look at MsgWaitForMultipleObjects or this link directly :
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/waiting_in_a_message_loop.asp
0 Kudos
Jugoslav_Dujic
Valued Contributor II
1,183 Views
Here's a version which works. The problem seemed to be TRUE/FALSE for bWaitAll in MsgWait...

Jugoslav
0 Kudos
michael_green
Beginner
1,115 Views

Hi Jugoslav,

Many thanks for your example - yes it works, but unfortunately it does not represent the situation I have been trying to describe. In my case, it is not just a matter of running another process, finishing it, then coming back to the calling process. The Excel that I run uses a VB macro to output a file which the calling process needs in order to continue. If that file exists, the number crunching swings into action, and it's during this phase (20 seconds?) that no screen repainting occurs and the user gets no progress reports.

In order to demonstrate this I have attached a zip file containing a totally stripped down version of my code. I have hardwired all necessary data in the Excel template - the clicking of an OK button is all that is required, but the demo works best if you delay clicking the OK button for about 10 seconds.

Many thanks once again for all your help.

Mike

0 Kudos
Reply