News:

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

Moving child windows, revisited

Started by NoCforMe, October 16, 2011, 10:11:00 AM

Previous topic - Next topic

dedndave

well - for some reason, the WndProc function for the child window is not seeing the mouse button message
i haven't looked at your code, but i would say that it has to be designed that way

jj2007

Quote from: dedndave on October 16, 2011, 09:39:54 PM
well - for some reason, the WndProc function for the child window is not seeing the mouse button message
i haven't looked at your code, but i would say that it has to be designed that way

You looked for it in the loword of WM_PARENTNOTIFY?

dedndave

here is a simple example of what i was talking about
it does have a small issue - one that could be fixed
that is, if you size the main window so that it clips part of the child, then cursor clipping doesn't work anymore
that can be overcome by limiting the main window sizing so that it does not clip the child
i.e., they wouldn't be able to size the main window so small that the child gets clipped

qWord

hi,
In the attachment a simple solution using a container, which holds the actual control.
I like the idea, that the user has the option to rearrange and resize the controls - maybe I will continue work on this idea.

qWord
FPU in a trice: SmplMath
It's that simple!

dedndave


NoCforMe

#20
Well, folks, I got it all working--pretty well, too. And it wasn't all that complicated.

The whole thing (window resizing/moving/resize-handle handling) is table-driven, one of my favorite ways of coding.

First, some data structures and a fairly scary-looking array of structs:



$handleInfo STRUCT
  ID DD ?
  X DD ?
  Y DD ?
  flags DD ?
  handle HWND ?
  cursor HCURSOR ?
$handleInfo ENDS

;===== Move handle flag bits: =====
$Vconstrain EQU 100H
$Hconstrain EQU 200H
$dX0 EQU 400H
$dY0 EQU 800H
$dXneg EQU 1000H
$dXpos EQU 2000H
$dYneg EQU 4000H
$dYpos EQU 8000H
$dW EQU 10000H
$dH EQU 20000H

HandleInfo LABEL DWORD
  $handleInfo <2000, $Xc - $Wh, $Yc - $Hh, 11111110B OR $dX0 OR $dY0 OR $dW OR $dH OR $dXneg OR $dYneg>
  $handleInfo <2001, $Xc + ($Wc / 2) + ($Wh / 2), $Yc - $Hh, 11111000B OR $Vconstrain OR $dY0 OR $dH OR $dYneg>
  $handleInfo <2002, $Xc + $Wc, $Yc - $Hh, 11111011B OR $dY0 OR $dH OR $dW OR $dXpos OR $dYneg>
  $handleInfo <2003, $Xc - $Wh, $Yc + ($Hc / 2) - ($Hh / 2), 11010110B OR $Hconstrain OR $dX0 or $dW OR $dXneg>
  $handleInfo <2004, $Xc + $Wc, $Yc + ($Hc / 2) - ($Hh / 2), 01101011B OR $Hconstrain OR $dW OR $dXpos>
  $handleInfo <2005, $Xc - $Wh, $Yc + $Hc, 11011111B OR $dX0 OR $dW OR $dH OR $dXneg OR $dYpos>
  $handleInfo <2006, $Xc + ($Wc / 2) + ($Wh / 2), $Yc + $Hc, 00011111B OR $Vconstrain OR $dH OR $dYpos>
  $handleInfo <2007, $Xc + $Wc, $Yc + $Hc, 01111111B OR $dW OR $dH OR $dXpos OR $dYpos>



OK, that's a bit on the ugly side. But the good news is that it makes coding the routines that move all those littlle resizing handles much simpler. Far fewer "if-then-else" tests. The flags make a lot of the decisions.

Moving or resizing the child window is detected and initiated in the child-window's proc, by setting a global flag:



lbuttondown:
CMP MovingChild, TRUE
JE dodefault
CMP ResizingChild, TRUE
JE dodefault

; Get current mouse position, save as initial move/resize offset:
MOV EAX, lParam ;Get new X & Y pos.
MOV EDX, EAX ;Make a copy for later
AND EAX, 0FFFFH ;Mask off high word
CWDE ;Sign-extend to DWORD
MOV MM_Xoffset, EAX
MOV EAX, EDX ;Get back copy
SHR EAX, 16 ;High word--> low word
CWDE
MOV MM_Yoffset, EAX

; See which child window (main child or handles) generated the message:
MOV EAX, hWin
CMP EAX, ChildWinHandle
JE lbutton_child
CALL FindHandleWindow
OR EAX, EAX
JZ dodefault ;No match, do the default thang.
MOV HandleWinHandle, EAX
MOV HandlePtr, EDX
MOV ResizingChild, TRUE
call putresizing
; Compute starting position from child's POV:
MOV EAX, MM_Xoffset
MOV pt.x, EAX
MOV EAX, MM_Yoffset
MOV pt.y, EAX
INVOKE ClientToScreen, hWin, ADDR pt
INVOKE ScreenToClient, MainWinHandle, ADDR pt
; Calculate pos. from parent's POV:
MOV EAX, pt.x
MOV StartResizePosX, EAX
MOV EAX, pt.y
MOV StartResizePosY, EAX
JMP SHORT cwp_capture

lbutton_child:
; Capture that rodent!
MOV MovingChild, TRUE
cwp_capture:
INVOKE SetCapture, MainWinHandle
INVOKE GetClipCursor, ADDR PrevRect ;Get current clip rectangle
INVOKE GetWindowRect, MainWinHandle, ADDR gpRect
MOV EAX, SBheight
SUB gpRect.bottom, EAX ;Clip out status bar.
INVOKE ClipCursor, ADDR gpRect
JMP SHORT cwp_commonexit



while the actual moving/resizing work is done by the parent, since they've captured the mouse.

Here's the code for resizing the window. A good deal of this is just for checking the resulting size to make sure it doesn't get smaller than the minimum size:



ResizeChild PROC x:DWORD, y:DWORD
LOCAL deltaX:DWORD, deltaY:DWORD, pt:POINT

; Compute delta-X and delta-Y of move:
MOV EAX, x
SUB EAX, StartResizePosX
MOV deltaX, EAX
MOV EAX, y
SUB EAX, StartResizePosY
MOV deltaY, EAX

; Check that resulting new child window size is above minimum:
MOV EAX, deltaX
TEST ResizeFlag, $dXneg
JZ rcw3
NEG EAX
rcw3: MOV EDX, ChildWidth
ADD EDX, EAX
CMP EDX, $childMinW
JBE rcw99 ;No good, too small.
MOV EAX, deltaY
TEST ResizeFlag, $dYneg
JZ rcw6
NEG EAX
rcw6: MOV EDX, ChildHeight
ADD EDX, EAX
CMP EDX, $childMinH
JBE rcw99
MOV EAX, deltaX
ADD StartResizePosX, EAX
MOV EAX, deltaY
ADD StartResizePosY, EAX

; Get current (0,0) of child window:
XOR EAX, EAX
MOV pt.x, EAX
MOV pt.y, EAX
INVOKE ClientToScreen, ChildWinHandle, ADDR pt
INVOKE ScreenToClient, MainWinHandle, ADDR pt

; Determine whether & how to change origin of child:
MOV EAX, deltaX
MOV EDX, deltaY
TEST ResizeFlag, $dX0
JZ rcw10
ADD pt.x, EAX
rcw10: TEST ResizeFlag, $dY0
JZ rcw20
ADD pt.y, EDX


; Determine how to change child's size:
rcw20: TEST ResizeFlag, $dXneg
JZ rcw22
NEG EAX
rcw22: TEST ResizeFlag, $dYneg
JZ rcw24
NEG EDX
rcw24: TEST ResizeFlag, $dW
JZ rcw30
ADD ChildWidth, EAX
rcw30: TEST ResizeFlag, $dH
JZ rcw40
ADD ChildHeight, EDX
rcw40:
INVOKE SetWindowPos, ChildWinHandle, HWND_BOTTOM, pt.x, pt.y, ChildWidth, ChildHeight, SWP_SHOWWINDOW
INVOKE UpdateWindow, ChildWinHandle
INVOKE MoveHandleWindows, pt.x, pt.y
rcw99: RET

ResizeChild ENDP



The rest of it uses the current value of ResizieFlag to direct program flow (whether to add or subtract the delta-X and -Ys, whether to change the window's origin, whether to resize the window width and height, etc.).

A really handy trick is to use this sequence to map an (x,y) point in one window to another one:



INVOKE ClientToScreen, ChildWinHandle, ADDR pt
INVOKE ScreenToClient, MainWinHandle, ADDR pt



The code looks complicated, but it really isn't.

By the way, speaking of complicated, qWord, I looked at your sample (DragContainer), saw some interesting thing in there, but I really couldn't figure out what you were doing, as it's almost completely uncommented.

Another thing mine does that yours doesn't is to display the resizing-arrow cursors. That's actually really easy to do, and of course is table-driven as well:



LoadArrowCursors PROC
LOCAL cursorNS:HCURSOR, cursorEW:HCURSOR, cursorLdiag:HCURSOR, cursorRdiag:HCURSOR

; Load 4 resizing cursors:
INVOKE LoadCursor, NULL, IDC_SIZENS
MOV cursorNS, EAX
INVOKE LoadCursor, NULL, IDC_SIZEWE
MOV cursorEW, EAX
INVOKE LoadCursor, NULL, IDC_SIZENWSE
MOV cursorLdiag, EAX
INVOKE LoadCursor, NULL, IDC_SIZENESW
MOV cursorRdiag, EAX

; Store cursor handles in handle window table:
PUSH ESI
PUSH EBX
LEA ESI, HandleInfo
MOV EAX, cursorLdiag
MOV EBX, cursorEW
MOV ECX, cursorRdiag
MOV EDX, cursorNS
; 1:
MOV [ESI + $handleInfo.cursor], EAX
; 2:
MOV [ESI + (SIZEOF $handleInfo) + $handleInfo.cursor], EDX
; 3:
MOV [ESI + (SIZEOF $handleInfo * 2) + $handleInfo.cursor], ECX
; 4:
MOV [ESI + (SIZEOF $handleInfo * 3) + $handleInfo.cursor], EBX
; 5:
MOV [ESI + (SIZEOF $handleInfo * 4) + $handleInfo.cursor], EBX
; 6:
MOV [ESI + (SIZEOF $handleInfo * 5) + $handleInfo.cursor], ECX
; 7:
MOV [ESI + (SIZEOF $handleInfo * 6) + $handleInfo.cursor], EDX
; 8:
MOV [ESI + (SIZEOF $handleInfo * 7) + $handleInfo.cursor], EAX

POP EBX
POP ESI
RET

LoadArrowCursors ENDP



Then each cursor gets automagically loaded in the child window proc:



CMP EAX, WM_SETCURSOR
JE setcursor

setcursor:
MOV EAX, hWin
CALL FindHandleWindow ;See if handle matches any handle window's:
OR EAX, EAX ;Any match?
JZ dodefault ;Nope, do nothing.
INVOKE SetCursor, [EDX + $handleInfo.cursor]
XOR EAX, EAX
RET



Of course, I noticed that you're simply drawing little boxes instead of creating little windows. Probably less overhead that way, but then you don't get the nice functionality of the windows. Anyhow, thanks for posting your code.

NoCforMe

Question: one small thing still bugs me; my static-text control isn't showing up. Here's how I create it:



StaticClassName DB "static"
StaticText DB "Some text", 0

makeStaticText:
CALL DestroyExistingControl
INVOKE CreateWindowEx, WS_EX_LEFT, ADDR StaticClassName, ADDR StaticText,
SS_LEFT OR WS_VISIBLE OR WS_CHILD, $STX, $STY,
$STWidth, $STHeight, ChildWinHandle, $STID, InstanceHandle, NULL
MOV STHandle, EAX
MOV ControlHandle, EAX
INVOKE UpdateWindow, EAX



which is basically the same code I use to create the other controls (checkbox, radio button, etc.), all of which work. Can anyone see the stupid mistake I made here? Is this the way to create static text in a Windows app?

dedndave

can you show us the resulting value of STHandle ?

NoCforMe

Sure; it's 1,050,206. In other words, not zero, so no error. Why would there be an error here and not in any of the other control-creation code, which is just about identical?

How do you create static text (like in dialogs)? This should work.

dedndave

        INVOKE  CreateWindowEx, NULL, ADDR StaticClassName, ADDR StaticText,
                WS_VISIBLE OR WS_CHILD or WS_CLIPSIBLINGS,
                $STX, $STY, $STWidth, $STHeight, ChildWinHandle, $STID, InstanceHandle, NULL


those are the style bits i would normally use - that is probably not the issue

to set the text, i would probably use SendMessage - never tried it that way
that text generally applies to a title bar, but seems like it ought to work here, too

that leaves the parameters on the last line
i assume the ChildWinHandle is for a child that is to be the parent of the static, and it is non-zero
make sure the X and Y parameters are inside the ChildWinHandle window
make sure the width and height are something logical

jj2007

Quote from: NoCforMe on October 19, 2011, 06:26:54 PM
Question: one small thing still bugs me; my static-text control isn't showing up.

OR WS_CHILD OR WS_BORDER ... and voilà it shows up in all its beauty :bg

dedndave


NoCforMe

Not so fast, jj. True, the control shows up--I see a little box--but not the text. (And besides, I don't want a border anyway.)

How do I get the TEXT to show up?

I tried this



INVOKE  SendMessage, ControlHandle, WM_SETTEXT, 0, ADDR StaticText



but still no joy.

dedndave

hang on, Jochen   :bg
a text static should not require a border

but - style bits do seem to be the issue, afterall

try these bits   :U
WS_CHILD or WS_VISIBLE or SS_EDITCONTROL

i know that makes no sense, but those are the bits that MessageBox uses (along with WS_NOPREFIX and WS_GROUP)

you can add WS_CLIPSIBLINGS if you like, as you are moving things around
normally, static conrtrols are static, so that bit is not applicable

btw - your SendMessage parms look ok

NoCforMe

OK, this works: make the control a read-only edit control:



INVOKE CreateWindowEx, WS_EX_LEFT, ADDR EditClassName, ADDR StaticText,
WS_VISIBLE OR WS_CHILD OR ES_LEFT OR ES_READONLY, $STX, $STY,
$STWidth, $STHeight, ChildWinHandle, $STID, InstanceHandle, NULL



Seems like a stupid way to do it, but it works (look ma, no border!).

Dave: I tried what you suggested (I think):



INVOKE CreateWindowEx, WS_EX_LEFT, ADDR StaticClassName, ADDR StaticText,
WS_VISIBLE OR WS_CHILD OR SS_EDITCONTROL, $STX, $STY,
$STWidth, $STHeight, ChildWinHandle, $STID, InstanceHandle, NULL



but it didn't work. I guess I'll go with the edit control.

Unless someone else has some method that actually works ...