问题
First, I'm new here, so hello world!
I'm working on a little lightweight controls library. Each control is an instance of a class named "GraphicElement", and doesn't have a handle. I created an event dispatcher and it works as expected but I struggle with the painting of my controls. They are stored in a tree, and I paint them as I go through this tree. I also use a back buffer to ensure the window's content doesn't flicker.
Everything works fine, but when I move one of the controls, this happens:
.Of course, I can invalidate and repaint the whole window, which theoretically solves my problem, but I'd like to avoid doing so, especially when it's not necessary and for performance reasons.
Here's an example:
I'd like to move R2, then repaint the empty spot (I mean the old location of R2) without redrawing R4 and R5 (and maybe many others).
How to repaint the part of the background which "disappeared" ? Will I have to repaint the whole background, and so all my controls ? I won't post all my code here because it's pretty long and it also handles other things like events, but as I said before, I draw my controls as I iterate through a tree, so there's nothing crazy in it.
Thank you in advance for your help, and sorry if I'm being not clear.
EDIT: Here's some code, but as I said before if I invalidate the client area of the window it works like a charm, but I want to avoid doing that.
This method ("render") is called when Windows sends a WM_PAINT message :
m_hdcMem = CreateCompatibleDC(hdc);
m_bmpMem = CreateCompatibleBitmap(hdc, m_rect.right - m_rect.left, m_rect.bottom - m_rect.top);
m_bmpOld = (HBITMAP)SelectObject(m_hdcMem, m_bmpMem);
m_background->predraw(m_hdcMem); // draws the client area, which is an instance of GraphicElement
BitBlt(hdc, m_rect.left, m_rect.top, m_rect.right - m_rect.left, m_rect.bottom - m_rect.top, m_hdcMem, 0, 0, SRCCOPY);
SelectObject(m_hdcMem, m_bmpOld);
DeleteObject(m_bmpMem);
DeleteDC(m_hdcMem);
And here's the method "predraw" :
draw(hdc); // draws the current control
for (std::vector<GraphicElement*>::iterator it = m_children.begin(); it != m_children.end(); ++it)
(*it)->predraw(hdc); // "predraws" the other controls
Finally, when a control gets resized or moved, its area is invalidated using this function :
InvalidateRect(m_parentHwnd, lpRect, FALSE); // If I invalidate the whole window, my code works perfectly, but I'd like to know how to paint parts of my window
回答1:
I don't know what you mean by "lightweight control that doesn't have a handle", but I guess they are simple C++ classes (and not true "controls) that must be drawn on the parent window's client area.
The "problem" is that the WM_PAINT
message is a low-priority one, sent if the window has an invalid part in its client area, just before the application yields.
The documentation you should read first is: Painting and Drawing
The implementation I would suggest, as I have used it quite a few times and works really well, is a combination of both methods:
- Process the
WM_PAINT
message (and theBeginPaint()
/EndPaint()
functions) to paint the whole client window (or a part of it, using thercPaint
member of thePAINTSTRUCT
structure, if a more "optimized" implementation is desired). Please note that theWM_PAINT
message may be sent as a result of moving, resizing, bringing the window in foreground, or revealing a part of the window previously obscured by another one, that is due to user actions, in addition to programmatically invalidating the whole or part of it. So in response to this message you should draw the parent window and all the controls in their current position. - Use the
GetDC()
/ReleaseDC()
functions to draw only the part of the window affected by actions like adding, deleting, or moving a control. This kind of drawing takes place immediately, not waiting for theWM_PAINT
message to be sent. You should fill the area previously occupied by the control and draw the control in its new position. You should not invalidate any part of the client area, as this would cause anotherWM_PAINT
message to be sent. - The control drawing functions should be taking a
HDC
parameter (among any others needed), so as to be usable by both drawing methods (the handles returned by either theBeginPaint()
or theGetDC()
functions).
I have used this technique to make image-processing applications (eg have the user selecting a part of the image and drawing/restoring the rectangle selected) and unattended monitor applications.
An alternative, simpler implementation (employing only "Painting" but not "Drawing") could be:
- When a control is resized or moved invalidate only the old and new areas occupied by the control.
- Processing of the
WM_PAINT
message generally as above, but it should be modified so as to fill only the rectangle in thercPaint
member of thePAINTSTRUCT
structure, and draw only the controls intersecting with the above rectangle.
来源:https://stackoverflow.com/questions/43825479/paint-invalid-areas-of-a-window-using-win32-api-and-gdi