?
Solved

An easy one about WM_PAINT

Posted on 2003-02-25
9
Medium Priority
?
916 Views
Last Modified: 2012-05-04
I have a CImage drawing a graph through moveto and lineto commands. When repainting the form I wish to update my grid, I think this is easy but I don't know how!! If I keep a global DC to the CImage and put moveto/lineto in the onpaint event I can draw the lines ... but only if I don't have the parent dialog event in. How do I make my moveto/lineto commands stay.

Code:
     if (IsIConic())
     {
          ...
     }
     else
     {
          CanvasHandle->MoveTo (0,0);
          CanvasHandle->LineTo (102,102);
     }

works but hasn't got the proper repaint command.
     if (IsIConic())
     {
          ...
     }
     else
     {
          CanvasHandle->MoveTo (0,0);
          CanvasHandle->LineTo (102,102);
     }
     CDialog::OnPaint ();

Displays nothing in the way of lines but updates the screen correctly.
0
Comment
Question by:J_Seaman
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 5
  • 4
9 Comments
 
LVL 12

Expert Comment

by:Salte
ID: 8018186
WM_PAINT is sent to a window by Windows as a message where windows says "Paint yourself!" to the window.

When you handle that message you are given access to a certain device context which is usually unavailable to you. It is ONLY while processing a WM_PAINT message that you have the device context that handles the real screen available to you. At all other times you can get other device contexts that do output to memory or other places but none of them can do output to screen.

Similarly, when printing windows send a WM_PAINT message to the window and provide a device context for the printer for you.

Obviously, when processing a WM_PAINT message you should use the device context that windows provide for you and none other. I.e. you can use other device contexts but only for doing output to some other non-screen place in order to calculate or simulate something. The real output should go to the device context that windows has ready for you when it send a WM_PAINT message.

Similarly you should not try to access that device context when you're NOT processing a WM_PAINT message, that will in general not work they way you thought it would work.

So, I suspect those CanvasHandle thingies to be part of your problem. That is probably a member that specify a certain Canvas and you can draw on that canvas. This will in general cause the canvas to be invalidated and WM_PAINT will paint the canvas and draw whatever lines you wrote to it - i.e. when you're not in WM_PAINT while making those calls. How this work when you are processing WM_PAINT I am not sure about - probably not the way you thought it would.

The problem is one of infinite recursion, you draw via that canvas and that will cause the canvas to remember your drawing and send a WM_PAINT message to update itself. However, it is already processing a WM_PAINT message and so that processing is taking place first and that already running WM_PAINT knows nothing about the update of the canvas structure.

For one thing I suspect that this canvas is capable of drawing itself and as such you should not override the OnPaint function already present. Overriding it and in particular if you dont' call the baseclass OnPaint() function will probably lead the window to not paint itself as it should.

Secondly, if you do override OnPaint() you will essentially draw the client rectangle of the window yourself. Make sure you draw everything that needs to be drawn perhaps by calling baseclass' OnDraw() and when you want to draw your lines you should do GetDC() and draw through that device context that GetDC() gives you. I am not sure which parameters to give to GetDC(). GetDC() is overloaded and have many different functions, one of them is reserved for use inside an OnPaint() function to get the real DC that allows you to draw on the screen, that is the GetDC() function you should use.

Then you can draw. You can draw the whole rectangle if you want (the client rectangle of your window), windows will ignore whatever is outside of the area that is invalidated and only update the invalidated areas. However, you can also speed up that drawing by asking windows to get the smallest rectangle that contain all invalidated areas and only paint that smaller rectangle. I think it is even possible to be even more aggressive and get an actual list of non-overlapping rectangles that are invalidated and only update those. Not sure how you get that list though, there's presumably a Win32 function for it or it is a parameter to the OnPaint() function or it is a data member in the device context or something like that.

Bottom line is: I suspect the CanvasHandle thingies to be the source of your problem. Kinda hard to say for sure though since I don't know what type the CanvasHandle is.

Alf
0
 

Author Comment

by:J_Seaman
ID: 8024772

Alf,

Thanks very much for all the info on WM_PAINT - I understand it a lot better than I used to! I do have problems with it though:

"When you handle that message you are given access to a certain device context which is usually unavailable to you"
In which case, how come I am able to draw on the image and see it on screen OK until a repaint event messes it all up. Is the CImage object calling its own

WM_PAINT event?

"you should use the device context that windows provide for you and none other"
I would love to use the device context returned to me but that one will let me draw lines all over the form but not on the CImage!

As for the CanvasHandle, this is simply a CDC * variable I have set up which uses the GetDC() function to obtain the handle of the CImage. I can then use

this handle in my drawgraph function to moveto and lineto all over the place without any problems. The only issue I have is making the repaint event of the

form let me use this DC to redraw the graph. I have found examples of code which will happily call a function in the onpaint event to redraw the screen but I

can't seem to do this with my drawgraph. I know the function works ok though because every time I call it with new data it will update the image accordingly.

As for recursion, during the onpaint event does the beginpaint and endpaint stop the onpaint event being called again?

Assuming the canvas is capable of redrawing itself (Which I doubt because it isn't storing the image at any point so is simply being maipulated using my

values), how would I refresh it?

Well, now you know, the canvas is a CDC * and I can use it for moveto and lineto commands under normal execution but not when repainting (because the

CDialog::Onpaint overwrites it - I think!). Any ideas?

James.

Also, any ideas how I can convert the float X and float Y values for the onmousemove event of the image into CPoint values that can be used in a moveto/lineto function (Obviously more points if you do!)
0
 
LVL 12

Expert Comment

by:Salte
ID: 8025030
Yes, I believe that CanvasHandle is some kind of CImage object and when you call methods on that object to draw the image, the image will call Invalidate on its own rectangle and guess what - Invalidate causes windows to send a WM_PAINT message to the window containing the rectangle that was invalidated. For a CImage class that means the CImage::OnPaint function will most likely be called and will draw the lines.

So probably the code might work if you simply removed your own OnPaint() function and just used that canvas and draw on it outside of OnPaint.

This is most likely the way it is supposed to be used. This is the way that both Microsoft MFC and Borland VCL does it with their Image classes and the name CImage sounds very much like an MFC class to me. If the class has a window handle and is a subclass of CWindow (directly or indirectly, for example CControl is subclass of CWindow) then this is how you operate on it. No need to do your own OnPaint function in this case.

OnPaint() is only used for your client window if you have no controls on it - i.e. it is not a form - or if you have areas that are not controls.

In any event the OnPaint() should then only paint the part of the client window that is not having any control, If there is a control there it will handle its own painting.

so if you have a rectangle dividied in 4 and you have a panel in the upper left part, you want to write some text on the upper right part (directly on the rectangle, not on any control) and you have a big button on the lower left and an Image on the lower right part then your OnPaint() should ONLY update the upper right rectangle and write the text you want to write there and keep its hands off the other parts, if you feel you need to you can call the Invalidate() method on those other controls if you need them to refresh themselves but if you update them by calling methods that causes them to change appearance they will typically call invalidate on themselves by themselves so you don't have to do anything.

Alf
0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 

Author Comment

by:J_Seaman
ID: 8025995
First, thanks for your help!

Can you explain why you say the code might work if I remove the OnPaint function. I don't see how this is going to help. I know the code works and draws correctly, it's just not refreshing on repaint.

CImage is an MFC class and does inherit from CWnd. Saying that, I believe the repaint event for the object will only repaint the object and not the lines within it. Hence, when parts of my graph need repainting they end up being blank.

Basically, I'm still having no joy! I'm doing no drawing through the onpaint event but can draw perfectly well without it. Just need to combine the two. Can you make a CImage which draws a line on it with the onpaint event?
0
 

Author Comment

by:J_Seaman
ID: 8026153
Progress!

If I override CImage::OnPaint() I get the ability to draw lines with my moveto and lineto commands. Is this good practice (I've overwritten CImage so presumably all images will try and execute this code), also how do I access my drawgraph function which is out of the scope of a CImage?

Or am I going about it the wrong way altogether?!
0
 
LVL 12

Expert Comment

by:Salte
ID: 8026346
Well, you can draw when you do not paint, so obviously you can draw. It is even worse than that, the fact that you CAN draw then means that CImage has its own OnPaint function which obviously can draw quite well. The drawing on screen doesn't take place when you call the CImage functions to draw. Well, I assume that CImage work like most other classes in MFC. What it does is to remember how you want the things to be drawn, possibly by a bitmap locally in its own window and after it has updated its info about what is to be in the window it will call Invalidate to inform windows that it needs to be redrawn. Then the window message is called and handled and THAT message draws the lines to the screen. And then OnPaint returns and the method returns. Well, there are actually two possibilities here but I assume the MFC function Invalidate does SendMessage as opposed to PostMessage.

If SendMessage is used by Invalidate it goes something like this:

1. You call a function in CImage such as LineTo.
2. The MFC CImage object has no data except a window handle and will not update data in its own, instead it send a message to Windows that you want to draw a line on its client rectangle. This is most likely done by calling a function LineTo with the CImage window handle as handle.

3. gui32 (which implements the LineTo function) will update its internal data for the window and remember there's a line there now. Either GDI directly now call Invalidate but it is more likely that it doesn't and let user call invalidate. User in this context is MFC.

4. CImage will then call Invalidate on the rectangle.

5. gui32 or user32 (not sure which implements Invalidate) will then send a WM_PAINT to all windows that are affected by invalidate, i.e. all rectangles that has been invalidated. Some sorting is done also so that each window only get one WM_PAINT message even though there are several possibly overlapping rectangles that were invalidated. It is here where I am uncertain if the WM_PAINT is sent by SendMessage or PostMessage. If sent by PostMessage the message is simply placed in queue and won't be done until gui32/user32 returns and then again until the MFC CImage::LineTo function returns and your main program enters the main loop.

If sent by SendMessage the message is handled while still here and the Invalidate function won't return until the window has already been repainted.

IN other words if SendMessage then go to step 6 here, if PostMessage skip all the WM_PAINT handling for now and move on to step 8. Then when the code eventually enters the mainloop it will get the WM_PAINT message and handle the steps 6 through 7 at that time.

6. The WM_PAINT message is then received and forwarded to the window who needs to be repainted. This window's OnPaint function is called to repaint itself.

7. When OnPaint returns the window is painted. If WM_PAINT was sent by SendMessage the code now returns to gui32/user32 and to the invalidate function, if PostMessage was used then the program will return to the mainloop and check next message in the queue.

8. Back in gui32/user32 invalidate implementation, the repaint message has now been sent or posted and the function simply returns to caller, i.e. to the CImage::LineTo function in MFC.

9. The CImage::LineTo function is now done and returns to your code.

So, if CImage is an MFC class I guess it is already repainting itself - as it proves by working outside of OnPaint.

You need to refresh it you say, is that a response to WM_PAINT or is it because the line has changed and should be updated?

It is also possible that CImage does not remember or keep track of data for very long time and as such need to be constantly redrawn at every OnPaint - if so it is different from every other control in MFC which remember their own state. If so you need to redraw at all times, but I do see a problem in trying to redraw inside the OnPaint function by using the technique of simply calling CImage::LineTo. CImage::LineTo will most likely invalidate itself and thus require its own repaint and if that works outside of OnPaint it means that that is what it does. If you are inside OnPaint it means that you are currently occupying the device context that windows uses to really update the screen and so CImage::LineTo can't get it. I will suggest you try one of the following things:

1. Comment out your OnPaint function or have it only call baseclass and do nothing more.

If that didn't solve your problem, try:

2. simply do Invalidate on the CImage without actually attempting to draw anything.

If that didn't solve your problem, try:

3. write directly on the CImage window yourself, using the underlying Win32 line drawing functions. Since you have the device context and you have the handle to the CImage window you should be able to do that.

If that didn't solve your problem then do this:

4. Have your OnPaint function do PostMessage (not SendMessage) to a user message "draw the image".

It is very important that you do PostMessage here and not SendMessage, SendMessage would defeat the purpose for this user message.

5. Have a handler for that user image which draws the CImage since you are outside an OnPaint handler this should work.

Alf
0
 

Author Comment

by:J_Seaman
ID: 8026943

Well, that was an even more thorough explanation tham before - you seem to know an awful lot about the way windows draws!

Here goes with what I understand/think so far:

Right, my lineto and moveto draws OK which means some paint event is being called. I know that if I override the CImage OnPaint function I get entry to this every time the form is repainted. Thus, each CImage is refreshed with its OnPaint command when the main form calls OnPaint.

If I want to utilise the onpaint event of an image I have to override this handler because I need the CPaintDC of it which I can only get if I use the event for the specific object type. Therefore I must have to implement my redraw code by overriding this function ???

If I comment out the onpaint function it simply resorts to the parent (CDialog) on paint and refreshes the controls as necessary.

If I call invalidate on the object, I lose the information which is in it.

If I put code in the overridden OnPaint function to write directly to the CImage then it works .. but I can't get at the functions used in the dialog control because I am in CImage:: namespace.

Sadly, I don't know how to do your steps 4 and 5 with the postmessage but would like to know how if you could help.

I have nearly all the pieces of the puzzle but can't quite pit it together. If I was doing this in Builder I would be using a canvas object and write to that. This definitely DOES store the results as a BMP so I'm fairly certain MSVC will be similar. In theory all I need to do is repaint the internally stored BMP obkect onto the screen. No problem in Borland's environment but haven't a clue with microsoft. Is it just me who finds it so incredibly hard to get to grips with or is this a common problem?

Regardless of that I need to solve this problem so I'm left with the idea of taking a BMP snapshot of the graph once I've drawn it and repainting this. Is that possible? Easier?

I've raised the points to 150 and I'm going to give them to you anyway because you've spent so much time answering my questions. Thanks in advance for any more help.
0
 
LVL 12

Accepted Solution

by:
Salte earned 600 total points
ID: 8027318
As far as I know the MFC CImage is almost identical in operation of the TImage class of VCL in borland so in theory the same things should work for both. To my knowledge none of them do something special that the other doesn't do. They just have different mechanisms for doing it - they do the same things, just in different ways. However, those differences should be irrelevant for your problem.

Doubt it is easier to keep that BMP and modifying it. I would believe that is exactly what a CImage does internally in windows and there should be no reason to copy that code to your own program.

However, what you said did seem revealing in some ways.

1. IF you left out the OnPaint it would refresh the controls but not the CImage. Appearantly you have either a) the image isn't drawn in such situation or b) it is drawn but blank.

2. If you invalidate it clears the image. This means that it does redraw a blank image. It appears that b) was the correct situation in 1.

3. Sounds interesting... why do you need to access the functions in the CDialog? Are those the functions that contain the data which is used to display the image?

What if you subclassed CImage and provided some data so that you could either access the CDialog (A pointer is probably enough if the functions are public) or that you simply store the data that are input variables to the graph drawing, so that you can redraw the image based on the info in the CImage class object itself without having to ask CDIalog for the info.

4. It is easy enough to PostMessage from WM_PAINT handler:

  CDialogObj -> PostMessage(WM_DRAWGRAPH, 0, 0);

The problem is to find out what value to put for WM_DRAWGRAPH.

Obviously, you can't use a value that collide with windows own messages such as WM_PAINT. Windows have a constant defined as WM_USER which specify user messages value, i.e. WM_USER, WM_USER+1 etc is available for user messages. Part of the problem here is that windows users values in WM_USER and above for its own windows already. All dialog controls have dialog specific messages and they use that area.

So I believe windows also provide another constant, can't recall the name right now it is WM_something for values which is guaranteed to not be a message code that windows ever send by itself. It is safest to use such a value since such a message would never be misinterpreted by windows.

so a simple:

#define WM_DRAWGRAPH 0x.....
somevalue there which correspond to that value should be enough.

The second thign you need to do is of course to handle that message when it comes to you. You sent it to the dialog box, and so it will be the dialog box that see the message. You need to capture it.

OnMessage is a generic handler that see every message that comes to your window. I can't remember the signature of that function offhand but one of the arguments is the message number and you can trigger on that:

int CDialog::OnMessage(int msg, ... )
{
   if (msg == WM_DRAWGRAPH)
      OnDrawGraph();
}

And then you just write a OnDrawGraph function which draws the graph. Since you did PostMessage you know you are outside of the WM_PAINT and you can safely draw on the canvas.

Alf
0
 

Author Comment

by:J_Seaman
ID: 8032665
I presumed the VCL and MFC objects would be near enough identical but it just seems a lot more convoluted under VC++. If I could gain access to the internal

bitmap stored for the CImage (Assuming there is one), I could repaint that. For now, I don't know if there is one stored and if there is then I don't know

how to get to it so I am thinking about storing it myself anyway just to get the thing working.

As for the redrawing, the CImage control itself is always redrawn (Just with a blank background colour showing) when OnPaint occurs it just loses the

contents of the image. This leads me to think there might not be an internal representation of the image. However, there surely has to be some kind of

storage to stop subsequent line drawing not erasing the old data. Then again, maybe all the lineto's are upadted at the end of the function call with a

single OnPaint event before 'forgetting' the data.

I need to access the function for the dialog which contains the CImage because they access the data for the graph. If I overwrite CImage I seem to be locked

in that namespace so I can't get to the functions.

The function calls are public so I could subclass CImage (First I'd have to figure out how in msvc but that shouldn't be a big issue), but this seems a bit

extreme really. I just want to assign code for the onpaint event!

I simply haven't got the time to do any more on this so I'm going to have to leave it. If you do come across a way to repaint an image with moveto and lineto

please let me know. In the meantime, thanks a lot for all the help
0

Featured Post

Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Unlike C#, C++ doesn't have native support for sealing classes (so they cannot be sub-classed). At the cost of a virtual base class pointer it is possible to implement a pseudo sealing mechanism The trick is to virtually inherit from a base class…
Article by: SunnyDark
This article's goal is to present you with an easy to use XML wrapper for C++ and also present some interesting techniques that you might use with MS C++. The reason I built this class is to ease the pain of using XML files with C++, since there is…
The goal of the video will be to teach the user the difference and consequence of passing data by value vs passing data by reference in C++. An example of passing data by value as well as an example of passing data by reference will be be given. Bot…
The viewer will learn additional member functions of the vector class. Specifically, the capacity and swap member functions will be introduced.

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question