Using WM_MOUSEMOVE/WM_NCMOUSEMOVE to update specified area - windows

My perpose: When cursor hover on the specified area , a rectangle, filled with red brush. When cursor out of this area, filled with blue brush.
So I handle with WM_MOUSEMOVE/WM_NCMOUSEMOVE message(depend on the area, client zone or non-client zone), according the paramater of the cursor positon, I do defferent paintings.
Howerver,If I spliped faster, it do not work correctly.
case WM_NCMOUSEMOVE: {
UINT HITPOS = wParam;
POINT pt={GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam)};
BOOL active_flag=(GetActiveWindow()==hwnd);
Frame_NCDraw(hwnd,&active_flag);
if(HITPOS==HTMENU) Frame_PopMenu(hwnd,pt);
TRACKMOUSEEVENT tme;
tme.cbSize=sizeof(TRACKMOUSEEVENT);
tme.dwFlags=TME_NONCLIENT;
tme.hwndTrack=hwnd;
tme.dwHoverTime=HOVER_DEFAULT;
TrackMouseEvent(&tme);
return 0;
} break;
case WM_NCMOUSELEAVE: {
BOOL active_flag=(GetActiveWindow()==hwnd);
Frame_NCDraw(hwnd,&active_flag);
TRACKMOUSEEVENT tme;
tme.cbSize=sizeof(TRACKMOUSEEVENT);
tme.dwFlags=TME_CANCEL;
tme.hwndTrack=hwnd;
tme.dwHoverTime=HOVER_DEFAULT;
TrackMouseEvent(&tme);
} break;
I think this may be something with the spin of each WM_MOUSEMOVE message has been triggered.
Compared with the max/min/close button on window non-client zone, no matter how fast I sliped over , it still can work well. How did it do that ?

If the mouse is moving fast, your program can receive not all mouse messages. I think in your case, while the mouse moves to a new position, your program still handles a message with an older position, thus the wrong output.
To handle this you can check mouse position in other handlers, for example WM_TIMER.
At startup (typically in WM_CREATE handler) you should create the timer:
#define ID_MY_TIMER 750
void SetupTimer(HWND hwnd)
{
//Set timer for every second
if( !SetTimer(hwnd, ID_MY_TIMER, 1000, (TIMERPROC) NULL))
handle error;
}
In your WndProc add WM_TIMER handler:
case WM_TIMER:
{
if( wParam == ID_MY_TIMER )
{
TODO:
Get mouse position (for example GetCursorPos, it returns cursor
position in screen coordinates), compare it with the previous position.
If the position changed, issue a redraw command (seems it is Frame_NCDraw
call in your case).
Save the current position as previous position
//Set timer
SetupTimer(hwnd);
}
break;
}
At closing (typically WM_DESTROY) kill the timer:
KillTimer(hwnd, 1);

Related

Win32 WM_CTLCOLORSTATIC background not completely filled

I am working on a dialog box that is created and controlled by a host program. The host creates the window and then sends me all the messages, but this means I don't have full access to everything it's doing. (I mention this because it could be contributing to my issue.)
I would like to change the color of an LTEXT to red. I am handling the WM_CTLCOLORSTATIC message and it is working where the text is drawn. The problem I'm having is that the rectangle for the LTEXT is slightly wider than the length of the text. For the part of the control that does not contain text it is leaving the background white instead of COLOR_BTNFACE as I have specified.
Here is the code for my handler. I call it by way of HANDLE_WM_CTLCOLORSTATIC.
// color message handler
HBRUSH OnControlColor ( HWND hDlg, twobyte dlgItem, HDC hdcCtrl, int type ) override
{
if ( (dlgItem == ID_MANUAL_EDIT_WARNING) && (type == CTLCOLOR_STATIC) )
{
SetTextColor(hdcCtrl, RGB(204, 0, 0));
SetBkColor(hdcCtrl, GetSysColor(COLOR_BTNFACE));
return (HBRUSH)GetSysColorBrush(COLOR_BTNFACE);
}
return NO;
}
It seems like maybe I need to invalidate the entire client rect some way, but I am not sure how to do this. Obviously I could carefully make the rectangle the exact right size in the dialog designer, but this doesn't seem like the safest approach.
What you could do is to explicitly select your desired background brush into the control's device context; thus, when its rectangle is drawn, that brush will be used. You can do it with one additional line of code in your handler:
HBRUSH OnControlColor ( HWND hDlg, twobyte dlgItem, HDC hdcCtrl, int type ) override
{
if ( (dlgItem == ID_MANUAL_EDIT_WARNING) && (type == CTLCOLOR_STATIC) )
{
SetTextColor(hdcCtrl, RGB(204, 0, 0));
SetBkColor(hdcCtrl, GetSysColor(COLOR_BTNFACE));
SelectObject(hdcCtrl, GetSysColorBrush(COLOR_BTNFACE)); // Select the B/G brush
return (HBRUSH)GetSysColorBrush(COLOR_BTNFACE);
}
return NO;
}
My one concern here is that you're manipulating object selection in a device context that is 'owned' by another process; this could cause issues if the replaced object in that DC is user-created (but that doesn't seem to be so in this case).

Find all windows beneath a point

I want to find all the top-level windows (children of the desktop) beneath a given point on the desktop. I can't find an API for this.
My scenario is that I'm dragging a window across the screen and want to drop it into another (known) window. I can hit test the bounds of the target window ok, but that doesn't tell me whether it's occluded by another (unknown) window. Using WindowFromPoint and friends won't work, because the window being dragged is necessarily directly under the mouse. So I'm wondering if I can obtain all windows at the mouse position, and review them to see whether one of the windows I'm tracking is directly beneath the window I'm dragging.
Is there a way to do this without resorting to EnumDesktopWindows/GetWindowRect on every mouse drag? Or perhaps there's another solution I'm missing.
If you ask kindly, WindowFromPoint will ignore your window (the one currently being dragged) and return the next window. This is what Internet Explorer does when you drag a tab.
To do that:
Handle WM_NCHITTEST in window being dragged
Return HTTRANSPARENT during dragging. Call default window proc otherwise.
WindowFromPoint will ignore HTTRANSPARENT windows, but only those belonging to the calling thread. This shouldn't be a problem for you, because you should be calling WindowFromPoint from the window owner thread anyway.
Make sure there're no child windows at point passed to WindowFromPoint, or handle WM_NCHITTEST for these child windows as well.
Troubleshooting (if you still get your window from WindowFromPoint)
Test GetCurrentThreadID() == GetWindowThreadProcessId(WindowFromPoint(), 0) to ensure you're calling from correct thread
In WM_NCHITTEST, test that hwnd parameter equals what you get from WindowFromPoint()
Example (the area within rectangle returns the underlying window from WindowFromPoint):
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static const RECT s_TransparentRect = {100, 100, 200, 200};
switch (message)
{
case WM_NCCREATE:
SetTimer(hWnd, 1, 100, 0);
break;
case WM_TIMER:
{
POINT cursorPos;
GetCursorPos(&cursorPos);
TCHAR buffer[256];
_snwprintf_s(buffer, _countof(buffer), _TRUNCATE, _T("WindowFromPoint: %08X\n"), (int)WindowFromPoint(cursorPos));
SetWindowText(hWnd, buffer);
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
Rectangle(ps.hdc, s_TransparentRect.left, s_TransparentRect.top, s_TransparentRect.right, s_TransparentRect.bottom);
EndPaint(hWnd, &ps);
}
break;
case WM_NCHITTEST:
{
POINT cursorPos;
GetCursorPos(&cursorPos);
MapWindowPoints(HWND_DESKTOP, hWnd, &cursorPos, 1);
if (PtInRect(&s_TransparentRect, cursorPos))
return HTTRANSPARENT;
}
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
Right, you already know what WindowFromPoint() is going to return, should be the one you are dragging. Then use GetWindow() with uCmd = GW_HWNDNEXT to get the one below it in the Z-order. GetWindowRect() to get its bounds, IntersectRect() to compute the overlap.
Keep calling GetWindow() to find more windows that might be overlapped. Until it returns NULL or the overlap is good enough. If not then you'll normally favor the one that has the largest result rectangle from IntersectRect().

Incorrect window layout while resizing MessageBox on Windows

I have to change the button text/size of a standard MessageBox in Windows (Win 7 actually). I'm coding in C++/CLI so the only way to show such a window is to do System::Windows::Forms::MessageBox::Show(...). I can modify the button by hooking a function to WH_CBT and getting the code HCBT_ACTIVATE...
So far so good. Now I want to resize the window itself to fit the buttons. I get the code HCBT_CREATEWND and I modify the struct CBT_CREATEWND (member cx for the width, etc.) And it works...
But it appears that the MessageBox buttons are drawn over a gray rectangle that spans the whole window width. This rectangle is not a window (using Spy++) but probably a draw done with FillRectangle() or something equivalent. And unfortunately my procedure won't allow to redraw it. In other words, if I widen the MessageBox, the gray rectangle under the buttons is not redrawn.
Here is my (oversimplified) function:
// Simplified code...
#define NEW_WIDTH 300
LRESULT CALLBACK CBTProc( int code, WPARAM wParam, LPARAM lParam )
{
HWND hwnd = (HWND)wParam;
switch ( code )
{
case HCBT_ACTIVATE:
// Change buttons here
return 0;
case HCBT_CREATEWND:
// Change window size here
LPCBT_CREATEWND cw = (LPCBT_CREATEWND)lParam;
if ( 'Main MessageBox Window, don't worry it works fine' )
{
cw.cx = NEW_WIDTH;
return 0;
}
}
}
return CallNextHookEx( hook, code, wParam, lParam );
}
So my question is quite simple: how, using the hook mechanism, can I force the MessageBox to draw the gray rectangle under the buttons correctly when it is resized by a hook function?

Can I suppress selected input before the application's main loop?

As part of my Visual Studio utilities add-in SamTools, I have a mouse input routine that catches Ctrl+MouseWheel and sends a pageup/pagedown command to the active text window. Visual Studio 2010 added a new "feature" that uses that gesture for zoom in/out (barf). Currently, my add-in does send the scrolling command, but Visual Studio still changes the font size because I'm not eating the input.
I set my hook with a call to SetWindowsHookEx. Here's the callback code. My question is: is the best way to prevent Visual Studio from handling the Ctrl+MouseWheel input as a zoom command to simply not call CallNextHookEx when I get a mouse wheel event with the Ctrl key down?
(Please bear in mind this is some old code of mine.) :)
private IntPtr MouseCallback(int code, UIntPtr wParam, ref MOUSEHOOKSTRUCTEX lParam)
{
try
{
// the callback runs twice for each action - this is the latch
if (enterHook)
{
enterHook = false;
if (code >= 0)
{
int x = lParam.mstruct.pt.X;
int y = lParam.mstruct.pt.Y;
uint action = wParam.ToUInt32();
switch (action)
{
case WM_MOUSEWHEEL:
OnMouseWheel(new MouseEventArgs(MouseButtons.None, 0, x, y, ((short)HIWORD(lParam.mouseData)) / (int)WHEEL_DELTA));
break;
default:
// don't do anything special
break;
}
}
}
else
{
enterHook = true;
}
}
catch
{
// can't let an exception get through or VS will crash
}
return CallNextHookEx(mouseHandle, code, wParam, ref lParam);
}
And here's the code that executes in response to the MouseWheel event:
void mouse_enhancer_MouseWheel( object sender, System.Windows.Forms.MouseEventArgs e )
{
try
{
if ( Keyboard.GetKeyState( System.Windows.Forms.Keys.ControlKey ).IsDown && Connect.ApplicationObject.ActiveWindow.Type == vsWindowType.vsWindowTypeDocument )
{
int clicks = e.Delta;
if (e.Delta < 0)
{
Connect.ApplicationObject.ExecuteCommand( "Edit.ScrollPageDown", "" );
}
else
{
Connect.ApplicationObject.ExecuteCommand( "Edit.ScrollPageUp", "" );
}
}
}
catch ( System.Runtime.InteropServices.COMException )
{
// this occurs if ctrl+wheel is activated on a drop-down list. just ignore it.
}
}
PS: SamTools is open source (GPL) - you can download it from the link and the source is in the installer.
PSS: Ctrl+[+] and Ctrl+[-] are better for zooming. Let Ctrl+MouseWheel scroll (the vastly more commonly used command).
According to MSDN, it's possible to toss mouse messages that you process. Here's the recommendation:
If nCode is less than zero, the hook
procedure must return the value
returned by CallNextHookEx.
If nCode is greater than or equal to
zero, and the hook procedure did not
process the message, it is highly
recommended that you call
CallNextHookEx and return the value it
returns; otherwise, other applications
that have installed WH_MOUSE hooks
will not receive hook notifications
and may behave incorrectly as a
result. If the hook procedure
processed the message, it may return a
nonzero value to prevent the system
from passing the message to the target
window procedure.
In other words, if your mouse callback ends up using the mouse message, you don't have to call the next CallNextHookEx -- just return a nonzero value and (in theory, at least) the mouse movement should get swallowed. If that doesn't work the way you want, comment and we can iterate.
BTW, another possible alternative: it's possible that VS's mapping to the mouse wheel shows up in the Tools...Customize... UI, just like key mappings do. In that case, you could simply remap your add-in's commands instead of working at the hook level. But it's also posible (likely?) that this gesture is hard-coded.

MouseDown and then MouseUp doesn't work

I am trying to redirect mouse inputs on my Windows 7 application to some other window.
If I do this when I get WM_LBUTTONUP, it works (where MouseDown and MouseUp are SendInput functions in Win api):
SetForegroundWindow( other window );
SetCursorPos( somewhere on the window );
MouseDown();
MouseUp();
SetCursorPos( back );
SetForegroundWindow( main window );
But I don't want to only do mouse releases, I want to be able to capture all mouse stuff, including movements and dragging.
So this is next logical thing to do but it doesn't work:
WM_LBUTTONDOWN:
Do everything like before without MouseUp()
WM_LBUTTONUP:
Do everything like before without MouseDown()
This doesn't even work for regular clicks. I can't figure out why.
Can anybody help?
Mouse buttons are funky. When it gets an down then up, at some level those are converted to a click (and I think at some point the mouse up gets eaten, but I may not be recalling that correctly).
It could be any number of things, but if the other windows is (or is not) converting the buttondown/up to mouse clicks, it could be confusing your code.
I suggest you print a lot of debugging info and try to figure out exactly what the system is doing.
It might be worth looking at the SendMessage/PostMessage P/Invoke calls, and sending the messages directly to the window of the other application. You need to do some translation on the parameters so that the co-ordinates of mouse events tie up with what you want them to in the other application, but it's not a big deal to do that...
Edit -> I dug out some code where I have done this before... This is from a window which appears over the top of a tree view and replaces the default windows tooltip for that tree view.
private IntPtr _translate(IntPtr LParam)
{
// lparam is currently in client co-ordinates, and we need to translate those into client co-ordinates of
// the tree view we're attached to
int x = (int)LParam & 0xffff;
int y = (int)LParam >> 16;
Point screenPoint = this.PointToScreen(new Point(x, y));
Point treeViewClientPoint = _tv.PointToClient(screenPoint);
return (IntPtr)((treeViewClientPoint.Y << 16) | (treeViewClientPoint.X & 0xffff));
}
const int MA_NOACTIVATE = 3;
protected override void WndProc(ref Message m)
{
switch ((WM)m.Msg)
{
case WM.LBUTTONDBLCLK:
case WM.RBUTTONDBLCLK:
case WM.MBUTTONDBLCLK:
case WM.XBUTTONDBLCLK:
{
IntPtr i = _translate(m.LParam);
_hide();
InteropHelper.PostMessage(_tv.Handle, m.Msg, m.WParam, i);
return;
}
case WM.MOUSEACTIVATE:
{
m.Result = new IntPtr(MA_NOACTIVATE);
return;
}
case WM.MOUSEMOVE:
case WM.MOUSEHWHEEL:
case WM.LBUTTONUP:
case WM.RBUTTONUP:
case WM.MBUTTONUP:
case WM.XBUTTONUP:
case WM.LBUTTONDOWN:
case WM.RBUTTONDOWN:
case WM.MBUTTONDOWN:
case WM.XBUTTONDOWN:
{
IntPtr i = _translate(m.LParam);
InteropHelper.PostMessage(_tv.Handle, m.Msg, m.WParam, i);
return;
}
}
base.WndProc(ref m);
}

Resources