News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Of Mice and Messages

Started by NoCforMe, October 04, 2011, 07:36:55 PM

Previous topic - Next topic

NoCforMe

OK, rookie conceptual questions here. I hope you'll be kind to this n00b.

So say a guy wants to create a simple window, with a very small window inside it, just for proof-of-concept purposes. The parent window will create the "client area", within which the small window is free to move. The idea is to be able to drag the small window around inside the parent, like placing an object with a drawing program, say.

OK, so far so good. I've created the parent and the child(*) windows. I started playing around with intercepting mouse-move and button messages. But I'm very confused about a basic point that I need to get cleared up before I go further.

I tried making the child window the message-interceptor, using RegisterClassEx() to set up a message-handling procedure (let's call it "ChildProc"). On left-button-down messages, I checked for signs of dragging, using DragDetect(), at which point I captured the mouse with SetCapture(). After this, I watched for WM_MOUSEMOVE messages, thinking I could use them to actually move my window around.

Let me back this up to a more conceptual level. I'm confused about who should handle what in this situation.

Because I'm looking at mouse coordinates from the child's point of view, (0, 0) is at the upper-left of the child window, correct? But I want to move this window within the client area (=parent window). So should I be looking for mouse moves within the parent instead of the child?

Using pseudocode:


[ParentProc]
case WM_MOUSEMOVE:
if (DragFlag)
- move child window to (mouseX, mouseY)

case WM_LBUTTONUP:
- DragFlag = FALSE
- ReleaseCapture()

...


[ChildProc]
case WM_LBUTTONDOWN:
- if (DragDetect())
- DragFlag = TRUE
- SetCapture()

case WM_LBUTTONUP:
- DragFlag = FALSE
- ReleaseCapture()

...


The basic idea is that the signal to capture the mouse comes from the user clicking and dragging within the child, but of tracking mouse movements from the parent's point of view, so that the child can be moved within the parent (that is, relative to (0,0) of the parent). Both parent and child must monitor for WM_LBUTTONUP to know when the user releases the left button (DragFlag is a global variable).

Will this work? Is this the way to do this? Remember, all I want to do is be able to drag this little child around in the client area.

But I'm confused about who gets (and handles) what messages when. Is there some clear explanation of this out there in web-land? There's some good stuff at MSDN, but it's not basic enough to clear this up for me.

Other questions: should I use SetWindowPos() or MoveWindow() to move the window?
What about repainting, invalidating rectangles, etc.? What are the minimal requirements for being able to drag this child around and have it visible and with underlying stuff repainted as needed?
* Do I need to create the child as an actual child (that is, with the Windows child property)?

Probably more questions later.

jj2007

Your idea looks suspiciously like \masm32\examples\exampl02\mdidemo  :bg

dedndave

if the mouse is moved outside the main window, i believe you no longer get WM_MOUSEMOVE messages
you may want to use "mouse capture"
http://msdn.microsoft.com/en-us/library/windows/desktop/ms645601%28v=VS.85%29.aspx#_win32_Mouse_Capture

if you create an MDI child, you may not have this problem, but it has it's own quirks, too - lol

NoCforMe

Quote from: jj2007 on October 04, 2011, 07:47:44 PM
Your idea looks suspiciously like \masm32\examples\exampl02\mdidemo  :bg

Yes, something like what happens if you click the first button, except that I don't want the scroll bars to appear when the child is dragged to the extremes. So close, but no cigar.

NoCforMe

Quote from: dedndave on October 04, 2011, 07:52:03 PM
if the mouse is moved outside the main window, i believe you no longer get WM_MOUSEMOVE messages
you may want to use "mouse capture"
http://msdn.microsoft.com/en-us/library/windows/desktop/ms645601%28v=VS.85%29.aspx#_win32_Mouse_Capture

Look at my pseudocode; I'm already capturing the mouse. The question is, who captures it, the parent or the child?

Quoteif you create an MDI child, you may not have this problem, but it has it's own quirks, too - lol

Yes, looks like maybe too many quirks for me. I'd like a robust, elegant solution I can use from now on.

dedndave

i don't think you are using the SetCapture function   :P
if you were, you probably wouldn't ask that question
QuoteThe system typically posts a mouse message to the window that contains the cursor hot spot when a mouse event
occurs. An application can change this behavior by using the SetCapture function to route mouse messages to a
specific window. The window receives all mouse messages until the application calls the ReleaseCapture function
or specifies another capture window, or until the user clicks a window created by another thread.

NoCforMe

I am using SetCapture(). Here's my code:


MOV EAX, uMsg
CMP EAX, WM_LBUTTONDOWN
JE lbuttondown

...

lbuttondown:
; Left button down: see if user dragged:
MOV EAX, lParam
MOV EDX, EAX ;Make a copy
AND EAX, 0FFFFH ;Mask off high word
MOV pt.x, EAX
SHR EDX, 16 ;High word--> low word
MOV pt.y, EDX
INVOKE DragDetect, hWin, pt.x, pt.y
OR EAX, EAX ;Did we detect dragging?
JZ cp999
MOV Dragging, TRUE ;Yes, set flag.
INVOKE SetCapture, hWin
JMP cp999 ;Exit

NoCforMe

I went ahead and tried what I  described above in pseudocode. My hunch seems to have been right: it works pretty well. I can pick up and drag my child window all over the inside of the parent window.

Few weird things, though:

  • When I drag it past the edge of the parent window, the window keeps on going, but it gets clipped by the parent. I'm guessing I need to do that ClipCursor() thing to keep it inside the client area.
  • No matter where I grab the child, the mouse cursor always jumps to the upper left-hand corner of the window. How do you handle this? (I'd like the cursor to stay wherever it was when I grabbed it.)
  • Another weird thing: If I grab the window in its interior, I can move the mouse left and right inside the window without it being moved, even though I can see from my indicators that the mouse button is down and mouse capture is in progress. It's as if I can't actually move the window until the cursor hits one edge or the other.
Clearly I've got a lot to learn here. But I'm making progress.

I would still very much appreciate a general explanation of how this all works, without too many of the actual implementation details. (There's always time for them later!)

dedndave

the child window should clip inside the client of the main window - that's a good thing   :bg
(in fact, i strongly recommend using the WS_CLIPCHILDREN flag when creating the main window)
but, you shouldn't be able to drag the cursor outside the main window client

attached is a small MDI window that i use
i don't use IF/ELSEIF/ELSE/ENDIF structures in my code, so it doesn't make a wonderful example for newbies   :P
but, it does demonstrate proper dragging behaviour of a child window

NoCforMe

Quote from: dedndave on October 04, 2011, 11:41:15 PM
the child window should clip inside the client of the main window - that's a good thing   :bg
(in fact, i strongly recommend using the WS_CLIPCHILDREN flag when creating the main window)
but, you shouldn't be able to drag the cursor outside the main window client

attached is a small MDI window that i use

That still exhibits the problem I described with my test, except that your mouse cursor is constrained within the parent window. It's still possible to drag part of the child over the edge of the parent so it clips. I'd like my window to stop when it "bumps into" the parent window. (Not a huge problem, but still ...)

Quotei don't use IF/ELSEIF/ELSE/ENDIF structures in my code, so it doesn't make a wonderful example for newbies   :P

Well, I'm a n00b to Win32 only. Been writing 16-bit code for decades now. I also don't care much for those "streamlined" directives. Hell, why not just write C, if that's what you want your code to look like?

dedndave

yes - the child window will be clipped inside the main window
that is always true for child windows and is generally considered desirable
however, if you do not want that, then you do not want a child window
you want an independant window, or more likely, a dialog box
no more SetCapture, either   :U

QuoteWell, I'm a n00b to Win32 only. Been writing 16-bit code for decades now. I also don't care much for
those "streamlined" directives. Hell, why not just write C, if that's what you want your code to look like?

a man after my own heart - lol
my thoughts exactly - if i wanted my program to look like a C program, i'd probably write it in C   :bg
btw - i also wrote 16-bit asm for years

NoCforMe

More progress to report. Got clipping working correctly, using a page from MSDN:


PrevRect RECT <>

ChildProc PROC hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL pt:POINT

MOV EAX, uMsg
CMP EAX, WM_LBUTTONUP
JE lbuttonup

...

lbuttonup:
CMP MouseCaptured, TRUE ;Are we watching the mouse?
JNE cddodef ;Nope.
INVOKE ReleaseCapture
INVOKE ClipCursor, ADDR PrevRect ;Un-clip to our window.
INVOKE SetWindowText, mousebtnhandle, ADDR upmsg
MOV MouseCaptured, FALSE ;Clear flag.
JMP cddodef

...

WindowProc PROC hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
LOCAL pt:POINT, clipRect:RECT

MOV EAX, uMsg
CMP EAX, WM_MOUSEMOVE
JE domousemove
CMP EAX, WM_LBUTTONUP
JE buttonup

...

domousemove:
CMP MouseCaptured, TRUE ;Are we watching the mouse?
JE dm10 ;Yes, move child window.
CMP DragFlag, TRUE ;No; did child report dragging?
JNE dodefault ;Nope, do nothing.
MOV DragFlag, FALSE ;Clear flag
MOV MouseCaptured, TRUE
INVOKE SetCapture, hWin
INVOKE GetClipCursor, ADDR PrevRect ;Get current clip rectangle
INVOKE GetWindowRect, hWin, ADDR clipRect
INVOKE ClipCursor, ADDR clipRect
dm10: INVOKE MoveWindow, ChildHandle, pt.x, pt.y, $childWidth, $childHeight, TRUE

buttonup:
CMP MouseCaptured, TRUE ;Are we watching the mouse?
JNE dodefault ;Nope.
INVOKE ReleaseCapture ;Yep, stop capture.
INVOKE ClipCursor, ADDR PrevRect ;Un-clip to our window.
INVOKE SetWindowText, mousebtnhandle, ADDR upmsg
MOV MouseCaptured, FALSE ;Clear flag.
JMP dodefault


Notice that PrevRect is a global so it is preserved across calls.


NoCforMe

#13
Next question: I've got this window-dragging stuff working reasonably well. For one window. But what if one has dozens of windows that one wants to be able to drag around? I can see this turning into a real nightmare, unless one comes up with a clever organization scheme.

Hmm, I wonder if I could create an array of structures to keep track of all of these windows wandering all over the place?

Any thoughts?

Wait, wait! I think I just figured it out.

You might have dozens of windows, even active windows within a parent. But since the user can only drag one window at a time, it should actually be easy. (I'm leaving aside the ability to move more than one window at a time aside, like with marquee-selecting.) There will always be at most one window being dragged to keep track of.

In fact, it'll work fine the way I have things set up now. The window proc for the child window simply detects whether the user clicked and dragged in that window (using DragDetect()), then sets a flag. The parent window proc checks this flag, and if set, starts mouse capture. To move the child window (following the mouse), it needs the handle to the child window. So all the child window has to do is to copy its handle into a global variable, which the parent proc can use. Easy!

I'll have to try this to see if it's as easy as it looks ...

Problem: Each child window still requires its own window proc. That could still make the code pretty bloat-y. Any way to funnel all this through a single window proc? I don't see how you'd know which child window the user tried to drag. Any way to retrieve the ID of the window?

dedndave

it's probably given to you in wParam or lParam   :P