With a class derived from CScrollWindowImpl
void Scroll::DoPaint(CDCHandle hDC)
{
if ( _MemDC==NULL)
return;
RECT r;
//I'd like to update r with rcPaint from the DC's PAINTSTRUCT here
hDC.BitBlt(r.left, r.top, r.right-r.left, r.bottom-r.top,
*_MemDC, r.left, r.top, SRCCOPY);
}
What is the most efficient way of painting the window contents with a WTL ScrollWindow?
The CScrollImpl WM_PAINT doesn't pass the CPaintDC to the derived class OnPaint, which has the PAINTSTRUCT m_ps member with the update RECT rcPaint member.
LRESULT CScrollImpl::OnPaint(UINT, WPARAM wParam, LPARAM, BOOL&) {
T* pT = static_cast<T*>(this);
ATLASSERT(::IsWindow(pT->m_hWnd));
if(wParam != NULL) { // The HDC is sometimes passed in
CDCHandle dc = (HDC)wParam;
dc.SetViewportOrg(-m_ptOffset.x, -m_ptOffset.y);
pT->DoPaint(dc);
}
else {
CPaintDC dc(pT->m_hWnd);
dc.SetViewportOrg(-m_ptOffset.x, -m_ptOffset.y);
pT->DoPaint(CDCHandle(dc));
}
return 0;
}
So I've handled the WM_PAINT message and the best I've achieved so far is BitBlt the whole _MemDC when scrolled, but during an unscrolled redraw to only BitBlt the invalidated rectangle.
UPDATE:
Sometimes rcPaint is larger than the rectangle of the MemDc so the increase in efficiency is negligible and buggy.
Is it really the most efficient or not, however note that WTL Samples include Samples\BmpView project which features CScrollWindowImpl use in CBitmapView class, which displays a visible part of a [supposedly large] image. Specifically, it overrides background erase and paint handlers and demostrates how to do BitBlt for the requested for painting part only.
Related
I found on (at least) Win10 that calling ::GetClipboardData() on a CF_DIBV5 created via Alt-PrtScrn (may be a synthesized format) causes the image to be modified (and basically corrupted).
For example, on the handler for ON_WM_CLIPBOARDUPDATE() the simple loop below will cause the corruption (note that you need to use debug mode so the ::GetClipboardData() is not optimized out).
To test, first don't run your app that processes the clipboard, use Alt-PrntScrn to capture data, then paste it to Paint. Now run the app that process the clipboard (with below sample below). Repeat Alt-PrntScrn process and you'll see it's different where the right side of the captured window ends up on the left and not centered in the area.
void CMainFrame::OnClipboardUpdate()
if (::OpenClipboard(AfxGetMainWnd()->m_hWnd)) {
UINT uformat=0;
while ((uformat=::EnumClipboardFormats(uformat))!=0) {
if (uformat==CF_DIBV5) {
// get the data - run in debug mode so not optimized out
HGLOBAL hglobal=::GetClipboardData(uformat);
}
}
// clean up
::CloseClipboard();
}
}
To enable the handler you need to call AddClipboardFormatListener(GetSafeHwnd()); in something like int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) then RemoveClipboardFormatListener(GetSafeHwnd()); on void CMainFrame::OnDestroy()
So is this a bug in Win10 (and other Windows Versions) or should I be doing something else that the sample isn't doing? (I know other formats exist, but the CF_DBIV5 is what I wanted).
I'm on Version 1903 (OS Build 18362.838)
Note the sample pic has right side items on left and some garbage pixels in lower left. I alt-prtscrn while app running, pasted in paint.
My resolution is 2560x1600.
Here's a link to a project that will cause the problem:
Sample Project
You can find the following description in the documentation :
The red, green, and blue bitfield masks for BI_BITFIELD bitmaps
immediately follow the BITMAPINFOHEADER, BITMAPV4HEADER, and
BITMAPV5HEADER structures. The BITMAPV4HEADER and BITMAPV5HEADER
structures contain additional members for red, green, and blue masks
as follows.
When the biCompression member of BITMAPINFOHEADER is set to BI_BITFIELDS and the function receives an argument of type LPBITMAPINFO, the color masks will immediately follow the header. The color table, if present, will follow the color masks. BITMAPCOREHEADER bitmaps do not support color masks.
When you handle CF_DIBV5 correctly you will draw the image successfully. The following is an example of Win32 C++ you can refer to:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static UINT uFormat = (UINT) -1;
HDC hdcMem = NULL;
RECT rc = {0};
BYTE * pData = NULL;
BITMAPV5HEADER *pDibv5Info = NULL;
switch (message)
{
case WM_CLIPBOARDUPDATE:
{
if (IsClipboardFormatAvailable(CF_DIBV5))
{
uFormat = CF_DIBV5;
::CloseClipboard();
GetClientRect(hWnd, &rc);
InvalidateRect(hWnd, &rc, TRUE);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
switch (uFormat)
{
case CF_DIBV5:
hdcMem = CreateCompatibleDC(hdc);
if (hdcMem != NULL)
{
if (::OpenClipboard(hWnd)) {
HANDLE hglobal = ::GetClipboardData(uFormat);
pData = (BYTE*)GlobalLock(hglobal);
if (pData)
{
pDibv5Info = (BITMAPV5HEADER *)pData;
int offset = pDibv5Info->bV5Size + pDibv5Info->bV5ClrUsed * sizeof(RGBQUAD);
if (pDibv5Info->bV5Compression == BI_BITFIELDS)
offset += 3 * sizeof(DWORD); //three DWORD color masks that specify the red, green, and blue components
pData += offset;
SetDIBitsToDevice(hdc, 20, 20, pDibv5Info->bV5Width, pDibv5Info->bV5Height, 0, 0, 0, pDibv5Info->bV5Height, pData, (BITMAPINFO *)pDibv5Info, 0);
}
GlobalUnlock(hglobal);
::CloseClipboard();
}
}
break;
}
EndPaint(hWnd, &ps);
}
break;
}
The correct image drawn in my application window:
I can reproduce the same issue without code:
if (pDibv5Info->bV5Compression == BI_BITFIELDS)
offset += 3 * sizeof(DWORD);
The corrupted image:
I am using GDI* plus to do custom drawing, but I have a drawing error when my window gets drawn under a windows explorer window, it looks like this:
As u can see just under the explorer window.. the colors are weird.. the top right are buttons and the checkbox is also a button .. "Are you.." is a static control.. they are all inherited and implemented as custom controls.. when receiving WM_PAINT. I also use a buffered image in WM_PAINT.. anyway.. I can't explain this, any ideas? It works fine when not under windows explorer window, as u can see in the left site of the window.
The checkbox is a button control, use a MSG_OCM_DRAWITEM(OnPaintImpl) handler in which I get the DC as so:
LRESULT OnPaintImpl(UINT ctrlID, LPDRAWITEMSTRUCT lpDIS)
{
ATLASSERT(GdiPlus::IsInitialized());
OnPaintGdiPlus(lpDIS->hDC, lpDIS->rcItem, lpDIS->itemState);
return S_OK;
}
and in my OnPaintGdiPlus(HDC hDC, CRect rc, UINT nState) I do this:
CMemoryDC dcMem(hDC, rc);
Graphics graphics(dcMem);
Rect rcClient = GdiPlus::GetRect(rc);
Everything else is just calling basic drawing functions from the graphics.
In the dialog I get WM_PAINT and handle it here:
LRESULT OnPaintImpl(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
PAINTSTRUCT ps;
HDC hDC = BeginPaint(&ps);
if (ps.rcPaint.right || ps.rcPaint.bottom) // draw rect is defined
{
if (GdiPlus::IsInitialized())
OnPaintGdiPlus(hDC, ps, GetClientRect(m_hWnd));
else
::MessageBox(m_hWnd, L"Graphics mode not initialized properly!", L"Graphics", MB_OK | MB_ICONWARNING);
}
EndPaint(&ps);
return S_OK;
}
Based on this hDC I create a Graphics object and paint using it.
Any other stuff I should add here?
I'm playing around with drawing my own custom controls using the uxTheme library in Windows, and I can't work out why my control doesn't look like the regular Windows control that (supposedly) uses the same theme I'm using:
The above image shows a standard Windows ComboBox (top) and my custom control drawn using the ComboBox theme (bottom). What I can't work out is why the border from my control is a different shape and colour to the standard control.
In my class constructor I open the theme data:
mComboTheme = OpenThemeData( hwnd, L"COMBOBOX" );
And then in the handler for WM_PAINT I'm just drawing two parts of the ComboBox components:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc;
RECT client;
if( GetUpdateRect( hwnd, &ps.rcPaint, false ))
{
hdc = BeginPaint( hwnd, &ps );
GetClientRect( hwnd, &client );
if( IsThemeBackgroundPartiallyTransparent( mComboTheme, CP_BACKGROUND, CBXS_HOT ))
{
DrawThemeParentBackground( hwnd, hdc, &ps.rcPaint );
}
DrawThemeBackground( mComboTheme, hdc, CP_BACKGROUND, CBXS_HOT, &client, &ps.rcPaint );
client.left = client.right - 20;
DrawThemeBackground( mComboTheme, hdc, CP_DROPDOWNBUTTONRIGHT, CBXSR_HOT, &client, ps.rcPaint );
EndPaint( *this, &ps );
}
break;
}
Any suggestions as to why these two controls don't look the same would be greatly appreciated.
Thanks,
James
You called DrawThemeBackground with CP_BACKGROUND and CP_DROPDOWNBUTTONRIGHT. Perhaps you should also call it with CP_BORDER if you want the border to match the standard combobox?
I created a window with the following flags to overlay a d3d application:
WS_EX_TOPMOST | WS_EX_COMPOSITED | WS_EX_TRANSPARENT | WS_EX_LAYERED
I proceeded with colorkeying the window for transperacy and all worked well.
However once I began drawing on it using GDI an unforeseen problem occurred:
For some reason the mouse events (especially movement) are not passed correctly through the window when WM_PAINT is in progress, and so it appears as though the mouse and the keyboard for that matter lag. the FPS is fine, this is some API problem, I suspect that for some reason the keyboard/mouse messages are not handled as they should while the WM_PAINT is in progress, because the slower the timer is set to the less jerking there is.
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_DESTROY:
{
KillTimer(hwnd, ID_TIMER);
PostQuitMessage(0);
break;
}
case WM_CREATE:
{
SetTimer(hwnd, ID_TIMER, 10, NULL);
break;
}
case WM_TIMER:
{
InvalidateRect(hwnd, 0, 1);
break;
}
case WM_PAINT:
{
paint(hwnd);
break;
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
and
void paint (HWND hwnd)
{
PAINTSTRUCT Ps;
HDC hdc = BeginPaint(hwnd, &Ps);
SetBkColor(hdc, RGB(0,0,0));
SetBkMode(hdc, TRANSPARENT);
LOGBRUSH log_brush;
log_brush.lbStyle = BS_NULL;
HBRUSH handle_brush = CreateBrushIndirect(&log_brush);
SelectObject(hdc, handle_brush);
..........................................
DeleteObject(font);
DeleteObject(pen);
DeleteObject(handle_brush);
EndPaint(hwnd, &Ps);
}
Thank you for any help you may be able to give.
WM_PAINT messages are never delivered to your window unless someone calls UpdateWindow or there are no keyboard or mouse messages in your input queue.
Once you begin processing WM_PAINT, if a keyboard or mouse message arrives, it just sits in your queue until you are done with WM_PAINT. So What you are describing isn't possible.
If your WM_PAINT code takes a long time to execute, that could cause jerkiness, but you say that's not a problem so perhaps it's your handling of WM_ERASEBKGND? I don't see that code, but I do see that when you InvalidateRect, you are passing TRUE as the last parameter which means that you want the background to be erased.
If you don't handle WM_ERASEBKGND, then DefWindowProc will do it for you erasing your entire window with the brush from from your window class. This could result in windows thinking that no part of your window is transparent.
If you want mouse messages to pass through your window, a more reliable way is to handle the WM_NCHITTEST message and return HTTRANSPARENT where you want the mouse to pass through.
This is basically what how the WS_EX_TRANSPARENT style works. like this
case WM_NCHITTEST:
{
lRet = DefWindowProc(hwnd, uMsg, wParam, lParam);
if (HTCLIENT == lRet)
lRet = HTTRANSPARENT;
}
If your window has no non-client area, then you can skip the call to DefWindowProc.
WndProc() is not always re-entrant. I believe with the main message pump, the mouse and keyboard messages are queued up and waiting for you to finish the prior WM_PAINT message. Conversely, if you were to call SendMessage() from WndProc(), then you are looking at re-entrace. The other case is PostMessage() which would add the message to the queue. Maybe look at using DirectInput for mouse and keyboard input if this is an issue. Otherwise, look for ways to speed up your drawing.
The requirement is to draw my information in side of another application's window.
To take care of z order and so forth hooking WH_GETMESSAGE and draw on WM_PAINT seem good.
However some WM_PAINT are intended for the window area of my concern, but other WM_PAINT are for something completely different, like a context menu or button.
Example Notepad is hooked with an overlay writing "Hello" into the Notepad screen. This works fine. However when right clicking Notepad the context menu gets overlay with Hello. Basically the context menu is destroyed.
Is there an elegant way of determining what WM_PAINT is context menu?
LRESULT CALLBACK overlayHook(int code, WPARAM wParam, LPARAM lParam) {
//Try and be the LAST responder to WM_PAINT messages;
LRESULT retCode = CallNextHookEx(hhk, code, wParam, lParam);
//Per GetMsgProc documentation, don't do anything fancy
if(code < 0) {
return retCode;
}
//Assumes that target application only draws when WM_PAINT message is
//removed from input queue.
if(wParam == PM_NOREMOVE) {
return retCode;
}
MSG* message = (MSG*)lParam;
if(message->message != WM_PAINT) {
//Ignore everything that isn't a paint request
return retCode;
}
PAINTSTRUCT psPaint;
BeginPaint(message->hwnd, &psPaint);
HDC hdc = psPaint.hdc;
RECT r = psPaint.rcPaint;
TextOut(hdc, 10, 10, "Hello", 4);
EndPaint(message->hwnd, &psPaint);
return retCode;
}
It is not enough to test for the draw update region, because the context menu could be anywhere and contain the area of my concern.
I don't know of any elegant way to do this, but you could use GetWindowLong() to get the window's style or GetClassName() to get its class name, and then base your filtering decisions on those values.