Windows API: Simple window does not redraw correctly - windows

I'm working on a Lua binding for Windows API. So far I've been able to create a basic window with a list box control:
require('Alien')
package.path = 'libs\\?.lua;libs\\?\\init.lua;' .. package.path
local function printf(...) io.write(string.format(...)) end
Windows = require('Windows')
local W = Windows
print("loaded OK")
print("Command line=", W.GetCommandLine())
local windowClassName = "testWindow"
local windowClass, window
local hInstance = assert(W.GetModuleHandle(nil))
assert(W.WM_CREATE)
assert(W.WM_CLOSE)
assert(W.WM_DESTROY)
assert(W.DefWindowProc)
assert(W.DestroyWindow)
assert(W.PostQuitMessage)
local msgID = {}
for k, v in pairs(Windows) do
if k:match('^WM_') then msgID[v] = k end
end
local function wndProc(hWnd, msg, wParam, lParam)
local id = msgID[msg] or ('%08X'):format(msg)
printf('wndProc(%08X, %20s, %08X, %08X\n',
hWnd,id, wParam, lParam)
if msg == W.WM_CREATE then
printf("window %08X create\n", hWnd)
elseif msg == W.WM_CLOSE then W.DestroyWindow(hWnd)
elseif msg == W.WM_DESTROY then W.PostQuitMessage(0)
else return W.DefWindowProc(hWnd, msg, wParam, lParam)
end
return 0
end
local wndProcCB = alien.callback(wndProc,
assert(W.LRESULT), assert(W.HWND),
assert(W.UINT), assert(W.WPARAM), assert(W.LPARAM))
windowClass = assert(W.WNDCLASSEX:new {
cbSize = assert(W.WNDCLASSEX.size),
style = bit.bor(W.CS_HREDRAW, W.CS_VREDRAW),
lpfnWndProc = assert(wndProcCB),
cbClsExtra = 0,
cbWndExtra = 0,
hInstance = assert(hInstance),
hIcon = nil,
hCursor = nil,
hbrBackground = assert(W.COLOR_APPWORKSPACE)+1,
lpszMenuName = nil,
lpszClassName = assert(windowClassName),
hIconSm = nil,
})
assert(W.RegisterClassEx(windowClass))
local dwExStyle = bit.bor(
W.WS_EX_TOOLWINDOW,
W.WS_EX_OVERLAPPEDWINDOW)
local dwStyle = bit.bor(
W.WS_OVERLAPPEDWINDOW,
W.WS_CLIPSIBLINGS,
W.WS_CLIPCHILDREN)
window = assert(W.CreateWindowEx(
assert(dwExStyle), --dwExStyle
assert(windowClassName), --lpClassName
"Test Window", --lpWindowName
assert(dwStyle), --dwStyle
assert(W.CW_USEDEFAULT), --x
assert(W.CW_USEDEFAULT), --y
420, 314, --width, height
nil, nil, assert(hInstance), nil)) --hWndParent, hMenu, hInstance, lpParam
local SWP_SHOW_ONLY = bit.bor(
W.SWP_ASYNCWINDOWPOS,
W.SWP_SHOWWINDOW,
W.SWP_NOACTIVATE,
W.SWP_NOMOVE,
W.SWP_NOSIZE,
W.SWP_NOZORDER)
W.SetWindowPos(assert(window), nil, 0, 0, 0, 0, assert(SWP_SHOW_ONLY))
local msg = assert(W.MSG:new())
while W.GetMessage(msg, 0, 0, 0) do
W.TranslateMessage(msg)
W.DispatchMessage(msg)
end
It creates and displays a window, but the window doesn't redraw correctly. From the console output I can see lots of WM_PAINT, WM_ERASEBKGND, WM_NCPAINT etc, which I pass on to DefWindowProc, but it seems to be not handling them.
Example screenshots:
When the window first appears, it either takes the image of whatever was behind it (as in this case) or appears solid gray. As it's dragged around, it keeps that image and anything that drags over it leaves behind a ghost image. (Can be seen on the titlebar, where I have a small white button following the active window around.) If I drag it off screen a bit, it starts to get even more glitchy but never successfully repaints itself.
I've tried various methods of handling WM_PAINT, WM_ERASEBKGND, etc but none have had any effect. Examples don't show these messages being handled either. Am I doing something wrong, or is there something else I need to be doing?

Well, I found the issue (I hope) with the help of Rohitab API Monitor. It was indeed a problem with the Lua binding: it had DefWindowProc declared as taking four int parameters. That's mostly fine, until we get a WM_ERASEBKGND message with a wParam (a handle to a device context) which is > 0x7FFFFFFF, and the call to DefWindowProc fails at converting that to an int and passes the wrong value. So the window never renders certain items to the correct device context. My debug logging (which wraps Windows API functions to print their parameters to the console) didn't catch this either, since it was logging the value before it got mangled.
Changing the declaration to use uint seems to have resolved it. (And maybe I can use API Monitor's definition files for my bindings, since the ones I have seem to be somewhat inaccurate.)

Related

RegisterPointerInputTarget not consuming all input

I'm working in Windows 10 and I am trying to write a C++ program that intercepts all touch screen input, however some input is still getting through.
My code calls RegisterPointerInputTarget with PT_TOUCH to intercept touch input. This mostly seems to work, however the results are inconsistent. As a test I have added code that uses SendInput to move the mouse slowly to the right whenever touch input is detected. With my program running I can, for example, open up MS Paint and touch the screen. So long as I hold my finger still on the cursor moves slowly to the right as expected. The moment I move my finger however, the cursor snaps to the position under my finger, just as it would if my program were not running at all.
To give another example, if I try the same thing in visual studio, again the cursor moves slowly to the right until I move my finger at which point the cursor follows my fingers' movement but slowly, lagging be behind it with a significant delay.
The code to set up my window looks like this;
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // Store instance handle in our global variable
static const char* class_name = "DUMMY_CLASS";
WNDCLASSEX wx = {};
wx.cbSize = sizeof(WNDCLASSEX);
wx.lpfnWndProc = WndProc; // function which will handle messages
wx.hInstance = hInst;
wx.lpszClassName = class_name;
HWND hWnd = 0;
if (RegisterClassEx(&wx)) {
hWnd = CreateWindowEx(0, class_name, "dummy_name", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
}
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
if (RegisterTouchWindow(hWnd, 0) &&
RegisterPointerInputTarget(hWnd, PT_TOUCH))
{
...
and my message handling code looks like this;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_TOUCH:
{
INPUT Inputs[1] = { 0 };
Inputs[0].type = INPUT_MOUSE;
Inputs[0].mi.dx = 1;
Inputs[0].mi.dy = 0;
Inputs[0].mi.dwFlags = MOUSEEVENTF_MOVE;
SendInput(1, Inputs, sizeof(INPUT));
Ideally this test code would simply move the cursor for any touch input. Any help in fixing or just understanding this would be greatly appreciated!
I have made some progress with this but have hit other, related, problems I will ask about in a separate question. I will add a comment here when that question is live. The key to sorting out this initial issue however was to make sure I am returning 0, without calling DefWindowProc from all WM_POINTERENTER, WM_POINTERLEAVE, WM_POINTERUP, WM_POINTERDOWN, WM_POINTERUPDATE and WM_TOUCH messages, and to put my SendInput call into the code processing the WM_UPDATE message.

Is there a way to forward TTN_NEEDTEXT to CFrameWnd?

I have an application that already deals with the generating of tooltips. I am modifying a CWnd derived class that has a parent frame. It doesn't implement tooltips.
From this, I can get tooltips to show up by adding the following code:
BEGIN_MESSAGE_MAP(CMyWindow, CWnd)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
END_MESSAGE_MAP()
BOOL CMyWindow::OnToolTipNotify(UINT id, NMHDR* pNMHDR, LRESULT* pResult)
{
UNREFERENCED_PARAMETER(id);
UNREFERENCED_PARAMETER(pResult);
// need to handle both ANSI and UNICODE versions of the message
TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
CStringA strTipText;
UINT_PTR nID = pNMHDR->idFrom;
if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) ||
pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND))
{
// idFrom is actually the HWND of the tool
nID = ::GetDlgCtrlID((HWND)nID);
}
if (nID != 0) // will be zero on a separator
strTipText.Format("Control ID = %d", nID);
if (pNMHDR->code == TTN_NEEDTEXTA)
{
strncpy_s(pTTTA->szText, sizeof(pTTTA->szText), strTipText,
strTipText.GetLength() + 1);
}
else
{
::MultiByteToWideChar(CP_ACP, 0, strTipText, strTipText.GetLength() + 1,
pTTTW->szText, sizeof(pTTTW->szText) / (sizeof pTTTW->szText[0]));
}
return TRUE; // message was handled
}
I can use GetParentFrame() to get the frame window, so I'd like to leverage the tooltip code that is already in place so that I get a consistent look. Is there some way that I can forward the TTN_NEEDTEXT message so that it gets handled by the frame window?
In your message map, you use ON_NOTIFY_whatever, which should hint that the message that TTN_NEEDTEXT uses is WM_NOTIFY — and indeed this is the case. So you can just produce the WM_NOTIFY yourself and send it to the parent.
The documentation for WM_NOTIFY says wParam is the control's identifier and lParam is the NMHDR pointer. There's an example on the bottom of the page that shows wParam is just the idFrom member of the NMHDR, so you have everything you need to reconstruct the message:
LRESULT lr = this->GetParentFrame()->SendMessage(WM_NOTIFY, pNMHDR->idFrom, (LPARAM) pNMHDR);
When you should issue this call is up to what you need to do. In this case, you would probably want to make it the first call, override your string, and return TRUE. I'm not fully sure, though.
Note: if MFC provides a function to do this, I don't know it; I personally don't use MFC, but the concepts are the same.
So what about the return value from that SendMessage()? Well, you can return it via pResult (for instance, if you're just tweaking the parent's alterations slightly), or you can ignore it and return a custom value. But for TTN_NEEDTEXT, it won't matter; TTN_NEEDTEXT doesn't care what the LRESULT is.

What is wrong with PrintWindow?

What is wrong with the following code? Why does PrintWindow return 0?
HWND hwnd = GetDesktopWindow();
CHK(hwnd);
HDC hdc = GetWindowDC(hwnd);
CHK(hdc);
if (hdc)
{
HDC hdcMem = CreateCompatibleDC(hdc);
CHK(hdcMem);
if (hdcMem)
{
RECT rc;
CHK(GetWindowRect(hwnd, &rc));
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, rc.right-rc.left, rc.bottom-rc.top);
CHK(hbitmap);
if (hbitmap)
{
SelectObject(hdcMem, hbitmap);
CHK(PrintWindow(hwnd, hdcMem, 0)); //HERE return 0
DeleteObject(hbitmap);
}
DeleteObject(hdcMem);
}
ReleaseDC(hwnd, hdc);
}
PrintWindow is a fairly thin operation. What it really does is post a WM_PRINTmessage to the queue for the window in question, in this case the desktop, and hopes that that window will respond to WM_PRINT correctly if at all (see here and here).
I repro'd your behavior but I'm not 100% sure why it's failing either. Perhaps you cannot call PrintWindow on an HWND that your process does not own, or perhaps the desktop does not respond to WM_PRINT messages.
The second link above includes a comment about using BitBlt instead:
Try getting a handle (HWND) to the
desktop window - and use BitBlt to
capture all the contents. Mind you -
you'll only capture what is visible on
the screen.
Maybe this helps.
It looks like GetDesktopWindow() returns a virtual HWND whose value is universally 0x0010010 on all Windows machines. This virtual HWND does not conform to usual PrintWindow behavior so the PrintWindow() returns FALSE, and GetLastError() reports no error code on this PrintWindow call.
To make PrintWindow() work, you can instead use the HWND from GetShellWindow(), which has the title "Program Manager" from the WinSpy++ figure below.
Replace:
HWND hwnd = GetDesktopWindow();
With:
HWND hwnd = GetDesktopWindow();
hwnd = FindWindowEx( hwnd, 0, _T("Progman"), _T("Program Manager") );
I'm not sure whether this gets what you want though. If you want to take a screenshot of the entire current desktop (including whatever top level windows are visible) then BitBlt is the route you want to take.
If you want to get the taskbar as well, you can still use this method but you'll have to take 2 screenshots and stitch the results together.

Win32/C can't display msg box right after CreateWindow

I'm creating my app window in code and I'm trying to show a message box as soon as the window exists. But I can't. I see only the newly created window, no msg box. If I quit the app by closing its window, the msg box suddenly appears, as if it were waiting in some queue, to be shown only when the app window is closed. Does the way I create the window somehow block modal msg boxes? Note: the MessageBox line is there just for testing. I'll take it out for normal use, as it would obviously interfere with the GetMessage loop.
//start relevant section of WinMain:
WNDCLASS wc={0};
wc.lpfnWndProc = WindowProc;
...
if (!RegisterClass(&wc) || !CreateWindow("mc", "mc", WS_POPUPWINDOW|WS_CAPTION|WS_VISIBLE, 100, 50, 100, 100, NULL, NULL, hInstance, NULL)) {
Error("Can't create window");
return 0;
}
ShowWindow(win, SW_SHOWNORMAL);
MessageBox(0, "Test", 0 ,0);
while (GetMessage(&msg,NULL,0,0)>0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//end relevant section of WinMain
long FAR PASCAL WindowProc(HWND h, UINT m, WPARAM wParam, LPARAM l)
{
switch (m) {
//process other messages
case WM_CREATE:
win=h;
//init stuff, paint something in the main window
break;
}
return DefWindowProc(h, m, wParam, l);
}
It sounds like you're not returning immediately from WM_CREATE like you're supposed to, but your window's entire lifetime is inside CreateWindow. So MessageBox doesn't actually get called until your window is dead, and trying to pass wnd as the parent of the message box is an invalid argument (the window no longer exists).
You shouldn't call DefWindowProc for WM_CREATE. You shouldn't have a message loop (i.e. DispatchMessage) inside WindowProc (exception: a message loop handling a modal dialog that is a child of the main window).
Re-entrancy of window procedures is something to avoid if possible.

limit of 64 ownerdraw createwindow buttons

I want to create an array of 256 colored buttons with the owner draw extended style to a dialog box created with the visual studio dialog design tool. I added a loop to the WM_INITDIALOG message handler in the dialog procedure to do this:
for (i=0; i<=255; i++)
{
int xp, yp;
HWND status;
xp = rect_pos.left+16*(i%16);
yp = rect_pos.top+16*(i>>4);
status = CreateWindow (
TEXT("button"),
"\0",
WS_CHILD|WS_VISIBLE|BS_OWNERDRAW|BS_PUSHBUTTON,
xp,
yp,
15,
15,
hDlg,
(HMENU) 5000+i, // id used to report events
hInst,
NULL
);
if (status == NULL)
xp =7;
}
I added a message handler for the WM_CTLCOLORBTN message.
case WM_CTLCOLORBTN:
{
int zz;
zz = GetWindowLong ((HWND) lParam, GWL_ID); // window identifier
zz -= 5000;
if ((zz >= 0) && (zz <= 255))
{
HBRUSH BS;
SetTextColor ((HDC) wParam, Collector.Color);
SetBkColor ((HDC) wParam, Collector.Color);
return ((LRESULT) Collector.Brush);
}
break;
}
It more or less works but only the first 64 buttons are displayed. I intend to use a different brush to color each button but for debug puproses, I substituted a single well defined brush. I've debugged the code and satisfied myself the x/y coordinates are proper for each button and that the ID provided in the hMenu createwindow call is proper. I watched all 256 buttons get colored in the WM_CTLCOLORBTN handler. I included a check to make sure the createwindow call does not return failure (NULL). I can get either 4 rows of 16 buttons or 4 columns of 16 buttons by interchanging the x/y parameters on the createwindow call.
If I remove the BS_OWNERDRAW bit from the createwindow call, all 256 buttons are drawn.
It's as if there a limit of 64 buttons with BS_OWNERDRAW :-(
Any help would be greatly appreciated!
TIA, Mike
Are you handling the WM_DRAWITEM message in conjunction with the BS_OWNERDRAW style?
In your case, it seems surprising to me that any buttons are displayed while using the BS_OWNERDRAW style, while BS_PUSHBUTTON is set.
As mentioned in the following link to the documentation for BS_OWNERDRAW, you need to handle WM_DRAWITEM and avoid specifying any other BS_ button styles.
Button Styles from MSDN
Also curious is that the WM_CTLCOLORBUTTON message may be received and then ignored for buttons containing the BS_PUSHBUTTON style. Check out the following link for the documentation on that window message.
WM_CTLCOLORBUTTON from MSDN
From what I can see in your code snippet, most likely you will want to do the following:
Set BS_OWNERDRAW when creating the child buttons.
Handle WM_DRAWITEM on the dialog and draw the button in its correct state. Note that you don't have to handle WM_CTLCOLORBUTTON, just use the Brushes and Fonts and modify the DC as you wish inside your WM_DRAWITEM handler.
Also, depending on your application, you might benefit from making your own window class to represent a grid of buttons on your own, and just drawing the items to taste. This is preferable if you're just displaying internal state and not really looking for the user to manage or interact with a grid of buttons.
Thanks to all who gave advice and help. I now have this working to my satisfaction. I have successfully colored the buttons and surrounded them with a black outline if selected or if they have the input focus. I'll add some code snippets below to show the final state of things but here is synopsis. First, I believe there is some legacy code in my system which makes owner drawn buttons respond to the WM_CTLCOLORBTN for the first child 64 buttons created. Second, I believe the only thing one needs to do is create the buttons, respond properly to the WM_DRAWITEM and WM_COMMAND/BN_CLICKED messages.
Here are the code snippets from my dialog box handler.
In the WM_INITDIALOG code -- create the buttons
for (i=0; i<=255; i++)
{
int xp, yp;
HWND status;
xp = rect_pos.left+16*(i&0x0F);
yp = rect_pos.top+16*(i>>4);
status = CreateWindow
(
TEXT("button"),
"\0",
WS_CHILD|WS_VISIBLE|WS_TABSTOP|BS_OWNERDRAW,
xp,
yp,
15,
15,
hDlg,
(HMENU) (5000+i), // id used to report events
hInst,
NULL
);
if (status == NULL)
xp =7;
SetFocus (status);
}
Respond to the WM_DRAWITEM message
case WM_DRAWITEM: // Owner drawn botton
{
LPDRAWITEMSTRUCT lpDrawItem;
HBRUSH BS, BS_Old;
HPEN PN_Old;
int sz=15;
int cntl;
cntl = LOWORD (wParam) - 5000;
lpDrawItem = (LPDRAWITEMSTRUCT) lParam;
if (lpDrawItem->CtlType != ODT_BUTTON)
return FALSE;
BS = CreateSolidBrush (ColorRef[cntl]);
if (lpDrawItem->itemState & (ODS_SELECTED | ODS_FOCUS))
{
sz = 14;
PN_Old = (HPEN) SelectObject(lpDrawItem->hDC, GetStockObject(BLACK_PEN));
}
else
PN_Old = (HPEN) SelectObject(lpDrawItem->hDC, GetStockObject(NULL_PEN));
BS_Old = (HBRUSH) SelectObject(lpDrawItem->hDC, BS);
Rectangle (lpDrawItem->hDC, 0, 0, sz, sz);
SelectObject(lpDrawItem->hDC, PN_Old);
SelectObject(lpDrawItem->hDC, BS_Old);
DeleteObject (BS);
return true;
}
and finally in the WM_COMMAND code
if (HIWORD(wParam) == BN_CLICKED)
{
if ((LOWORD(wParam) >= 5000) && (LOWORD(wParam) <=5255))
{
Color[0] = ColorRef[LOWORD(wParam)-5000] & 0xFF;
Color[1] = (ColorRef[LOWORD(wParam)-5000] >> 16) & 0xFF;
Color[2] = (ColorRef[LOWORD(wParam)-5000] >> 8 ) & 0xFF;
InvalidateRect (hDlg, NULL, TRUE);
goto Set_Color;
}
}

Resources