|
I have some drawing code that seems to work *most of the time*. Trying to use some of this code today, I've encountered all sorts of odd behavior, and I'm questioning how I originally implemented this code. What I need from people who are deeper into Windows than I is the "why" of how painting/drawing in Windows actually works.
My app is dialog based. Some of the dialogs have to update graphics as fast as reasonably possible. So, early in my Windows development curve, I crafted my solution like so:
myclassdlg::OnInitDlg()
{
// set everything up...
// start a timer...
SetTimer (999, 500, NULL); // every 500ms
}
myclassdlg::OnTimer()
{
// do all of my drawing.
}
Now, based on various bits and pieces I've pulled together, this is just the wrong way to go about it. Unfortunately, it worked the first time, for whatever reason, and I've continued on my perilous ways. Well, now I've run into a problem, my new drawing code (based on the above style) doesn't work, so in my research, it appears that the correct way to do things is to start the timer but in the OnTimer loop, add an UpdateWindow() call to trigger the WM_PAINT message. The OnPaint handler than executes whatever drawing is necessary.
First question: do I have this right?
Second question: assuming I have this correct, what does the "draw in the OnPaint handler" apprach imply? It would seem that "this is the way you do it", but why? Does Windows do something special such that the graphics are guaranteed to draw?
Still getting my arms around this beast called Windows.
Charlie Gilley
Will program for food...
Whoever said children were cheaper by the dozen... lied.
My son's PDA is an M249 SAW.
|
|
|
|
|
Im no expert but I think using the timer is incorrect. Have a look at these functions /messages in MSDN
InvalidateRgn
WM_ERASEBKGND
WM_PAINT
The idea is that when something changes that requires the graphics to change you call invalidate region to tell windows that this area of the screen/window in now out of date. Windows sends the WM_PAINT and WM_ERASEBKGND message as part of the screen painting process but if you attempt to paint to the screen outside of this process unexpected results can occur.
Objects in mirror are closer than they appear
|
|
|
|
|
You're right in that OnTimer() shouldn't do drawing, but should call an API that will trigger a repaint. RedrawWindow() will cause the whole window to be redrawn, that's the quickest way, although not the best if your painting takes a while.
The reason why is because Windows keeps some data about each window that indicates which part is "invalid" - meaning it needs to be redrawn. BeginPaint /EndPaint - which you call indirectly by using a CPaintDC object - do the bookkeeping that updates that data. If you just paint in some other random message handler, what you draw will get wiped out if another window covers yours.
So it's not so much that Windows guarantees that graphics will be drawn, but it guarantees that you will be notified (via WM_ERASEBKGND and WM_PAINT ) when some part of the window has been uncovered, which is when you decide what to draw.
--Mike--
Visual C++ MVP
LINKS~! Ericahist | PimpFish | CP SearchBar v3.0 | C++ Forum FAQ
|
|
|
|
|
Josh/Mike,
Okay, pieces are coming together. But, to belabor the point (because I really want to understand), Josh, could you elaborate on "but it guarantees that you will be notified" in the context of dialog/window creation? My perception is that sometimes my OnTimer approach works just fine, while at rare times I get nothing... or the wrong results.
Charlie Gilley
Will program for food...
Whoever said children were cheaper by the dozen... lied.
My son's PDA is an M249 SAW.
|
|
|
|
|
You could try this experiment. Set the timer interval to a larger value, say 5 seconds SetTimer (999, 5000, NULL); . Then take a smaller window and move across your window a few times.
When your window is being uncovered as you drag that smaller window, windows will notify you using WM_PAINT that you need to redraw a portion of your window (the invalidated portion). Windows doesn't remember how your window is painted (ie. doesn't store what you painted previously), so it has to ask you.
|
|
|
|
|
hfry,
I don't seem to be communicating clearly - sleep deprevation most likely. I understand your test case. As I move windows, paint events should arrive. I understand this. What I'm trying to get a handle on is the dialog creation / paint notification sequence of events. Why such a restrictive situation you ask? The answer lies in my target application - it is a touchscreen and all window navigation is under program control.
In my specific situation, I create my dialog and initiate periodic updates to the screen. 99% of the time, this works like a charm. I happen to be in the 1% zone, and the window just won't paint correctly. I've reduced my code to attempting to blit a single, red-filled bitmap. The code is below. What I see on the dialog is WHITE when the red should be. If I change the FillRect call to go directly to the dialog DC, it draws correctly.
In my dialog, I do this:
OnPaint()
{
//CPaintDC dc(this); // don't need this, let drawing class deal with it.
UpdateDisplayData(); // Render the graphics.
}
....
In the drawing code (no, the class' name is not this silly...), I have culled out 99% of my code, reducing it to a single bitmap fill and blt:
CDisplayData::UpdateDisplayData()
{
CPaintDC dcDialog(m_pParentWindow);
CDC dcBitmap;
BOOL bStatus = dcBitmap.CreateCompatibleDC(&dcDialog);
ASSERT (bStatus);
CBitmap buffer_bm;
CBitmap *old_bm;
// Create an offscreen bitmap for antiflicker buffering...
bStatus = buffer_bm.CreateCompatibleBitmap(&dcBitmap, 100, 100);
ASSERT (bStatus);
old_bm = dcBitmap.SelectObject(&buffer_bm);
// Fill the bitmap with an arbitrary color, and copy it to the screen.
// "RED" is a macro...
dcBitmap.FillSolidRect(0, 0, 500, 100, RED);
bStatus = dcDialog.BitBlt(rc.left,
rc.top,
rc.Width(),
rc.Height(),
&dcBitmap, 0, 0, SRCCOPY);
dcBitmap.SelectObject(old_bm);
}
-- modified at 7:17 Wednesday 26th July, 2006
|
|
|
|
|
charlieg wrote: I'm trying to get a handle on is the dialog creation / paint notification sequence of events
I'm actually not too sure what you mean by this.
charlieg wrote: What I see on the dialog is WHITE when the red should be.
I suspect that that is because you are not handling WM_ERASEBKGND and the default handler is drawing the background (using the background brush which is usually COLOR_WINDOW which is usually white). And that it's not painting red because that region was not invalidated. This is of course just a guess, from the available code.
I notice you are double buffering to reduce flickering. Are you also handling WM_ERASEBKGND and doing nothing but returning 1? Are you "erasing" your window in your UpdateDisplayData() function (bitblting the background color)? Or is this not neccessary in your case.
In your OnTimer handler, do you invalidate the part of your window that needs to be redrawn (or the whole client area if that is the case)? You shouldn't be calling UpdateDisplayData() in your OnTimer handler as CPaintDC (BeginPaint()/EndPaint()) should only be used in response to a WM_PAINT message.
Windows maintains the clip region for your window when you need to do your painting, so if you don't have anything within the clip region (ie. didn't invalidate explicitly or windows didn't invalidate anything for you) then it's not going to actually draw anything to the screen.
|
|
|
|
|
hfry,
hfry wrote: I'm actually not too sure what you mean by this.
What I'm trying to understand is the initial dialog/window creation sequence and the ramifications on drawing. In a galaxy far away called Unix, they have this beast called X-Windows. A drawing 101 error was trying to draw before something called an expose event. Drawing before this results in all of the drawing going into the bit bucket. Fast forward to today - Windows has it's own little mechanism called the invalid region - if you don't invalidate things, they won't draw. Now, most of my drawing codes does not invalidate anything, it just starts blasting away, and the images show up. My theory is that when a dialog/window is created, everything is invalid, and my drawing appears... just a theory. So, that's why I'm asking how things really work - maybe there is something more subtle.
Okay, so getting to your specific comments:
1) WM_ERASEBKGND - with a couple of very unique situations, I never handle this.
My drawing code regenerates the image in a bitmap. The BitBlt then replaces
the entire region on the window. My rationale was, why bother to erase
something that I'm getting ready to paint over?
FWIW: I added a handler for this message - no effect.
2) As far as "erasing my window" - I simply call InvalidateRect with the same
CRect that I use in the drawing code. Note this this is just the test code
I'm running now. Usually, I do not do an invalidate operation, electing to
draw out of the OnTimer handler. Both *seem* to work (but I have my doubts
).
Even with relocating the code to the OnPaint handler, the only time the
screen is updated correctly is when I draw directly to it. If I use the
code to fill a bitmap then blit the bitmap, I get white.
I truly appreciate your suggestions and patience. This issue has me a bit
Charlie Gilley
Will program for food...
Whoever said children were cheaper by the dozen... lied.
My son's PDA is an M249 SAW.
|
|
|
|
|
OMG!!!
I just found it, and I understand why superficially, but I think my way should have worked. Ignore all the onpaint comments, not directly relevent to the behavior at hand.
Broken code / working code is bolded below.... Now, does someone want to explain to me why I have to create a compatible bitmap from the PaintDC? Is it not logical that if I create a buffering DC with CreateCompatibleDC(PaintDC) should not a bitmap created be compatible as well?
CDisplayData::UpdateDisplayData()
{
CPaintDC dcDialog(m_pParentWindow);
CDC dcBitmap;
BOOL bStatus = dcBitmap.CreateCompatibleDC(&dcDialog);
ASSERT (bStatus);
CBitmap buffer_bm;
CBitmap *old_bm;
// Create an offscreen bitmap for antiflicker buffering...
bStatus = buffer_bm.CreateCompatibleBitmap(&dcBitmap, 100, 100);
bStatus = buffer_bm.CreateCompatibleBitmap(&dcDialog, 100, 100); // this works
ASSERT (bStatus);
old_bm = dcBitmap.SelectObject(&buffer_bm);
// Fill the bitmap with an arbitrary color, and copy it to the screen.
// "RED" is a macro...
dcBitmap.FillSolidRect(0, 0, 500, 100, RED);
bStatus = dcDialog.BitBlt(rc.left,
rc.top,
rc.Width(),
rc.Height(),
&dcBitmap, 0, 0, SRCCOPY);
dcBitmap.SelectObject(old_bm);
}
Charlie Gilley
Will program for food...
Whoever said children were cheaper by the dozen... lied.
My son's PDA is an M249 SAW.
|
|
|
|
|
|
Okay, I'm going to go off on a little rant here... the method is called CreateCompatibleBitmap, for God's sake! So the documentation clearly calls out that the bitmap created ISN'T compatible - that makes me feel better?
I think it should be renamed as CreateMostlyCompatibleBitmap()
The only thing worse than no doc is erroneous doc.
Charlie Gilley
Will program for food...
Whoever said children were cheaper by the dozen... lied.
My son's PDA is an M249 SAW.
|
|
|
|
|
charlieg wrote: What I'm trying to understand is the initial dialog/window creation sequence and the ramifications on drawing.
You could try using a spy application like this[^] to look at the kind of messages that windows generates for your application.
charlieg wrote: theory is that when a dialog/window is created, everything is invalid
Yes when your window is created everything is invalid.
charlieg wrote: WM_ERASEBKGND - with a couple of very unique situations, I never handle this.
When windows sees that there is an invalid region (and there are no more messages on the queue) it will generate 2 messages, WM_ERASEBKGND and then WM_PAINT. Windows paints the entire invalid client area with the background brush for you if you choose not to explicitly handle WM_ERASEBKGND.
Windows flicker for 2 reasons. One is when it takes too long to draw and you're drawing directly to the screen, then you can see windows drawing your window piece by piece. The other is when the window gets invalidated too quickly, ie, you keep having a lot of WM_ERASEBKGND and WM_PAINT messages in succession. The default window procedure (if WM_ERASEBKGND isn't handled and you don't return a non-zero value) will paint the entire client area with the background for you. This is usually undesirable if you are trying to eliminate flicker as you first have a white screen, then what you painted, then white again, etc, causing flicker.
charlieg wrote: Usually, I do not do an invalidate operation, electing to draw out of the OnTimer handler
The problem with this is when your window gets obscured and uncovered by another window. In which case windows will generate a WM_ERASEBKGND and WM_PAINT message. If WM_ERASEBKGND is not handled, the invalid area is painted with the background brush for you. If WM_PAINT doesn't do anything, then you just have the background. If you handled WM_ERASEBKGND and elected not to do anything and WM_PAINT isn't doing anything, then you are left with whatever bits happen to be there on the invalid area of your window. Of course if your timer is fast and you keep painting to the screen, it's unlikely that you'd really notice anything amiss.
|
|
|
|
|
All of your comments are dead-on. I'll be going back to clean up some sins of the past next week. Since my timers fire rather rapidly, and my drawing is buffered, the user never notices anything... until I have to slow the timers down ...
I appreciate all your help. I still want to slap silly the CreateCompatibleBitmap coder.
Charlie Gilley
Will program for food...
Whoever said children were cheaper by the dozen... lied.
My son's PDA is an M249 SAW.
|
|
|
|
|
Well, it probably means that the function is creating a bitmap compatible to the bitmap selected in the device context that you passed in. Yeah kinda bites, I'm also guilty of assuming what functions do based on their names instead of checking the docs.
Raymond Chen's blog[^] is a good read if you don't know about it already.
|
|
|
|
|
I have a CString variable that has a port address that needs to be converted to an unsigned short....I'm using the _outp(short, int) to communicate to an i/o port. I tried doing a 'atoi' on the string, but I end up with a 0.
For the second part I would like to convert the address(short) back to a string.....
I apperciate the help.
Thanks,
-------------
CString test = "0xF03";
test.Remove(' " '); //Remove the quotes
unsigned short x = atoi(test);
_outp(x,0x81); //Need this to be _outp(0xF03, 0x81)
|
|
|
|
|
arunkk1 wrote: test.Remove(' " '); //Remove the quotes
Unless you actually have CString test = ""0xF03""; there are no quotes to remove.
arunkk1 wrote: unsigned short x = atoi(test);
atoi() does not understand integer strings written in anything other than base 10.
Use strtol() instead.
unsigned short x = strtol(test, NULL, 16);
Convert back
test.Format("0x%X", x);
|
|
|
|
|
Hello,
I have developed an application that uses a bitmap associated with a static control using CStatic::SetBitmap. When displaying the image in the client area:
1. I would like the ability for the user to select an area of interest (via a transparent rectangle).
2. Be able to move and rubber band the rectangle similar to CRectTracker. The rectangle should not be able to be moved or resized out of the CStatic client window.
3. Report the coordinates of the rectangle with respect to the client area coordinates (i.e. top left, bottom right).
Does anybody have Visual C++/Studio sample code that would do this?
Thanks in advance,
PJP
|
|
|
|
|
sub class CStatic class and then on mouse events u update the window in Onpaint handler.
Sudeesh
|
|
|
|
|
I have a solution that contains several projects. Most of these projects use files that are common between them all.
I would like to only have the common files in one place so that I only have one version of them. Thus if one of the common files is changed all of the projects that use it will be updated/compiled again.
Any help with this would be very much appriciated.
Thanks,
Jim
|
|
|
|
|
Put all your common files in one directory, and set your projects to look in that directory. Configuration Properties >> C/C++ >> Additional Include Directories. Or you could set it in VC++ Directories.
|
|
|
|
|
Hi,
This is for anyone familiar with the TabCtrlSSL code.
I am using Viusal Studio 6.0 MFC. I downloaded the code for the tab control class CTabCtrlSSL and implemented it. Everything seems to be working fine except that the app does not appear to process the command messages. I had an existing dialog IDD_DIALOG_1 with button controls. I created a second dialog called IDD_DIALOG_TAB and placed a tab control in it. I created a variable for the control called m_ctrlTab of type CTabCtrlSSL. Then in the OnInitDialog() function of my TabCtrl class I call m_ctrlTab.AddSSLPage(("Tests"), nPageID++, IDD_DIALOG_1); If I click on any of the buttons in the IDD_DIALOG_1 (displayed as tab page 1) then the CTabPageSSL::OnCommand() function is called. If I click on OK or CANCEL buttons then the "return GetParent()->SendMessage (WM_COMMAND, wParam, lParam);" in the CTabPageSSL::OnCommand() function is called and returns a value of 1 and the application exits as I would expect. If I click on any other button then the "return GetParent()->SendMessage (WM_COMMAND, wParam, lParam);" function is called with wParam of 1022 (the IDD_DIALOG_1) but the function the button is supposed to invoke never executes. I have tried various handles from GetParent(), GetOwner(), GetParentOwner() but the SendMessage() function will return a value of 0 for these other buttons. I am not a Windows programmer so I am not very familiar with parent child relationships. The way I see it the dialog IDD_DIALOG_1 is a child of the tab dialog IDD_DIALOG_TAB. If anyone sees something obvious I could use some help on this.
Thanks,
Buck
|
|
|
|
|
I get the error listed at the bottom when I try compiling a source file which absolutely has no syntax errors. This has something to do with the precompiled header. I just learnt that precompiled headers are used for performance to speed up the compilation. If a header file is used in many different source files, precompiled header (.pch) file will be created the first time this header file is compiled and the next time this header file is called in a differnt source file, it will stop compiling and directly use the .pch file. Knowing that, what relationshop does precompiled header have with stdafx.h. Why are stdafx.h used for? When I tried creating a project in Visual Studio 2005, stdafx.h and stdafx.cpp were automatically created. Can someone explain what is going on here please......
Thanks,
Jaysan
BUILD LOG
------ Build started: Project: winsock, Configuration: Debug Win32 ------
Compiling...
ObjectRoot.cpp
c:\documents and settings\aravinth\desktop\objectroot\objectroot.cpp(299) : fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add '#include "stdafx.h"' to your source?
Build log was saved at "file://c:\Documents and Settings\Aravinth\Desktop\ObjectRoot\winsock\winsock\Debug\BuildLog.htm"
winsock - 1 error(s), 0 warning(s)
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
|
|
|
|
|
When using precompiled headers, anything that comes before #include "stdafx.h" is considered a comment. The file doesn't have to be named "stdafx.h", but if you create your project using the wizard, it will be. Stdafx.h is where you add common header files to gain this compilation optimization. Make sure that the first #include is "stdafx.h".
If you decide to become a software engineer, you are signing up to have a 1/2" piece of silicon tell you exactly how stupid you really are for 8 hours a day, 5 days a week
Zac
|
|
|
|
|
When the compiler is processing a CPP file (other than stdafx.cpp) it looks for the line
#include "stdafx.h"
and at that point, loads the saved state from the PCH file. (This is, btw, why anything you write before that include gets ignored.) If for some reason you can't include stdafx.h but want that CPP file to use the PCH, you can use
#pragma hdrstop
--Mike--
Visual C++ MVP
LINKS~! Ericahist | PimpFish | CP SearchBar v3.0 | C++ Forum FAQ
|
|
|
|
|
Are #include "stdafx.h" standard in the sense that you don't have to modify anything in the file after its created for you when you create a new project in Visual Studio 2005?
What could be the problem when I compiled a source file when I got the following error
------ Build started: Project: winsock, Configuration: Debug Win32 ------
Compiling...
ObjectRoot.cpp
c:\documents and settings\aravinth\desktop\objectroot\objectroot.cpp(299) : fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add '#include "stdafx.h"' to your source?
Build log was saved at "file://c:\Documents and Settings\Aravinth\Desktop\ObjectRoot\winsock\winsock\Debug\BuildLog.htm"
winsock - 1 error(s), 0 warning(s)
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
Could it because the precompiled header feature was on without including stdafx.h (#include "stdafx.h")
Thanks boys
-- modified at 16:01 Tuesday 25th July, 2006
|
|
|
|
|