Custom Window Frame Using DWM/Aero

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
F4Jonatas
Posts: 45
Joined: 22 Oct 2015, 06:35
Contact:

Custom Window Frame Using DWM/Aero

25 May 2020, 14:06

Hello to all code lovers! :salute:

I was always very curious to create a GUI like this example.
Image
But I never had much time to read about it, and life went on and I dropped it.

Today, with this pandemic, I'm working on all the scripts I saved and ended up starting to read about this post, but I crashed when using the NCCALCSIZE_PARAMS structure.

Could someone help me with this structure?

Code: Select all

// Calculate new NCCALCSIZE_PARAMS based on custom NCA inset.
NCCALCSIZE_PARAMS *pncsp = reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam);

pncsp->rgrc[0].left   = pncsp->rgrc[0].left   + 0;
pncsp->rgrc[0].top    = pncsp->rgrc[0].top    + 0;
pncsp->rgrc[0].right  = pncsp->rgrc[0].right  - 0;
pncsp->rgrc[0].bottom = pncsp->rgrc[0].bottom - 0;
Last edited by BoBo on 27 May 2020, 01:13, edited 1 time in total.
Reason: Added 'Aero' to subject line, as this might be the more common term (?)
BoBo
Posts: 6564
Joined: 13 May 2014, 17:15

Re: Custom Window Frame Using DWM

27 May 2020, 00:58

If none of AHK's DllCall()-Gods will show up soon ... something to play with :think:
https://autohotkey.com/board/topic/59196-lib-aero-libary/
https://github.com/Ixiko/AHK-libs-and-classes-collection/blob/master/lib-a_to_h/Aero_Lib.ahk
... and the rest of the story :arrow: AHK+DWM/Aero

HTH :shifty:

Please post your outcome, once you've accomplished that challenge :)
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Custom Window Frame Using DWM/Aero

27 May 2020, 01:59

I found some old code which is related to the examples you linked to, I do not remember anything about this, most probably it contains errors,

Code: Select all


Gui,+HWNDdHWND
Gui, Color, 000001

Gui, show, w600 h450, hello
OnMessage(0x0083,"WM_NCCALCSIZE")



Dwm_ExtendFrameIntoClientArea(dHWND, -1)
Dwm_SetWindowAttributeAllowNCPaint(dHWND, 1)

WinSet,TransColor, 000001, ahk_id %dHWND%
WinGetPos,x,y,w,h,ahk_id %dHWND%
DllCall("User32.dll\SetWindowPos", "Ptr", dHWND, "Uint", 0, "Int", x, "Int", y, "Int", w, "Int", h, "Uint", 0x0020) ; SWP_FRAMECHANGED=0x0020


Gui, add, Button,x+m y+m gButton1 ,Press me.

return
Button1:
	MsgBox, Thanks!
return

WM_NCCALCSIZE(wParam, lParam, msg, hwnd)
{
	
	x1:=NumGet(lParam+0,0,"Int")
	y1:=NumGet(lParam+0,4,"Int")
	x2:=NumGet(lParam+0,8,"Int")
	y2:=NumGet(lParam+0,12,"Int")
	
	old_x1:=NumGet(lParam+0,32,"Int")
	old_y1:=NumGet(lParam+0,4+32,"Int")
	old_x2:=NumGet(lParam+0,8+32,"Int")
	old_y2:=NumGet(lParam+0,12+32,"Int")
	dx1:=abs(x1-old_x1)
	dx2:=abs(x2-old_x2)
	dy1:=abs(y1-old_y1)
	dy2:=abs(y2-old_y2)
	

;	Msgbox, %dx1%  %dy1% %dx2% %dy2%
	NumPut(x1-dx1,lParam+0,0,"Int")
	NumPut(y1-dy1,lParam+0,4,"Int")
	NumPut(x2+dx2,lParam+0,8,"Int")
	NumPut(y2+dy2,lParam+0,12,"Int")
	
	NumPut(0x0040,wParam+0,0,"UInt")
}

Dwm_SetWindowAttributeAllowNCPaint(hwnd,onOff)
{
	;	DWMWA_ALLOW_NCPAINT = 4
	;	Enables content rendered in the non-client area to be visible on the frame drawn by DWM.
	;	The pvAttribute parameter points to a value of TRUE to enable content rendered in the
	;	non-client area to be visible on the frame; otherwise, it points to FALSE.
	dwAttribute := 4
	cbAttribute := 4
	VarSetCapacity(pvAttribute, 4)
	NumPut(onOff, pvAttribute, 0, "Int")
	hr:=DllCall("Dwmapi.dll\DwmSetWindowAttribute", "Ptr", hwnd, "Uint", dwAttribute, "Ptr", &pvAttribute, "Uint", cbAttribute)
	return hr ; 0 is ok!
}

Dwm_ExtendFrameIntoClientArea(hwnd,l:=0,r:=0,t=0,b:=0)
{
	;	Extends the window frame into the client area.
	;	Input:
	;			hwnd, unique id to the window to which to extend window frame
	;			l,r,t,b is left, right top and bottom margins, respectively. Use negative to create "sheet of glass"-effect
	;	Notes:
	;			https://msdn.microsoft.com/en-us/library/windows/desktop/aa969512(v=vs.85).aspx
	;			https://msdn.microsoft.com/en-us/library/windows/desktop/bb773244(v=vs.85).aspx (margins struct)
	VarSetCapacity(margin, 16)
	NumPut(l, margin, 0, "Int")
	NumPut(r, margin, 4, "Int")
	NumPut(t, margin, 8, "Int")
	NumPut(b, margin, 12, "Int")
	hr:=DllCall("Dwmapi.dll\DwmExtendFrameIntoClientArea", "Ptr", hwnd, "Ptr", &margin)
	return hr	; 0 is ok!
}
I think this is pretty cumbersome and awkward to work with this in AHK.

Cheers.
User avatar
F4Jonatas
Posts: 45
Joined: 22 Oct 2015, 06:35
Contact:

Re: Custom Window Frame Using DWM/Aero

27 May 2020, 05:37

@BoBo
I think this does not apply well, because I want something that is compatible with win7 or win10
My focus is not transparency but free access to the title bar...
Added 'Aero' to subject line


@Helgef
This is already starting to improve the result... :)

Code: Select all

numput( numget( lParam +0, 0, "int"  ), lParam +0, 0, "int"  )
numput( numget( lParam +0, 4, "int"  ), lParam +0, 4, "int"  )
numput( numget( lParam +0, 8, "int"  ), lParam +0, 8, "int"  )
numput( numget( lParam +0, 12, "int" ), lParam +0, 12, "int" )



I will continue to read more everything you showed. As soon as I arrive or approach the goal, I will show it!
User avatar
lmstearn
Posts: 698
Joined: 11 Aug 2016, 02:32
Contact:

Re: Custom Window Frame Using DWM/Aero

27 May 2020, 09:25

Hi @F4Jonatas, not much I can add here- @Helgef's code has the three rectangles as described in MSDN:
When the window procedure receives the WM_NCCALCSIZE message, the first rectangle contains the new coordinates of a window that has been moved or resized, that is, it is the proposed new window coordinates. The second contains the coordinates of the window before it was moved or resized. The third contains the coordinates of the window's client area before the window was moved or resized. If the window is a child window, the coordinates are relative to the client area of the parent window. If the window is a top-level window, the coordinates are relative to the screen origin.
That code is dealing only with the first (lParam + 0)- if you want the data from the others, it should be lParam + 16 and lParam + 32 respectively.
This code from SO is a simple case for WM_NCCALCSIZE - it also has a WM_NCPAINT section you might wish to add in later.
Please post a WIP test script, Helgef's code is applying WVR_ALIGNBOTTOM to the window- it would be interesting to see the result without it.
:arrow: itros "ylbbub eht tuO kaerB" a ni kcuts m'I pleH
User avatar
F4Jonatas
Posts: 45
Joined: 22 Oct 2015, 06:35
Contact:

Re: Custom Window Frame Using DWM/Aero

27 May 2020, 15:21

I think I'm lost ...
Here I am
:?:

Code: Select all


OnMessage( 0x001, "wndproc" ) ; WM_CREATE
OnMessage( 0x006, "wndproc" ) ; WM_ACTIVATE
OnMessage( 0x00F, "wndproc" ) ; WM_PAINT
OnMessage( 0x083, "wndproc" ) ; WM_NCCALCSIZE
OnMessage( 0x084, "wndproc" ) ; WM_NCHITTEST
OnMessage( 0x111, "wndproc" ) ; WM_COMMAND




fm := GuiCreate( "+resize +lastfound", "Custom DWM" )
fm.addbutton( "x" . ( a_screenwidth // 4 ) . " y2 h20",  "Click Me!" )
fm.show( "w" . ( a_screenwidth // 2 ) . " h" . ( a_screenheight // 2 ))



wndproc( wparam, lparam, msg, handle ) {
	local fCallDWP := true
	local fDwmEnabled := false
	local lRet := 0
	local hr := 0

	; Winproc worker for custom frame issues.
	hr := ! dllcall( "dwmapi\DwmIsCompositionEnabled", "int*", fDwmEnabled )
	if hr
		lRet := CustomCaptionProc( handle, msg, wparam, lparam, fCallDWP, lRet )

	; Winproc worker for the rest of the application.
	if fCallDWP {
		lRet := AppWinProc( handle, msg, wParam, lParam )
	}

	return lRet
}


; Message handler for handling the custom caption messages.
CustomCaptionProc( hWnd, message, wParam, lParam, byref pfCallDWP, byref lRet ) {
	local fCallDWP := true ; Pass on to DefWindowProc?

	local fCallDWP := ! dllcall( "dwmapi\DwmDefWindowProc", "ptr", hWnd, "uint", message, "ptr", wParam, "uptr", lParam, "uint*", lRet )

	; Handle window creation
	if message == 0x001 { ; WM_CREATE
		local rect := struct_rect( hWnd )
		; Inform application of the frame change.
		dllcall( "user32\SetWindowPos",
			"uint", hWnd,
			"uint", 0,
			"int", rect.left,
			"int", rect.top,
			"int", rect.width,
			"int", rect.height,
			"uint", 0x0020
		)

		fCallDWP := true
		lRet := 0
	}


	; Handle window activation.
	if message == 0x006 { ; WM_ACTIVATE
		; Extend the frame into the client area.
		; LEFTEXTENDWIDTH:    8
		; RIGHTEXTENDWIDTH:   8
		; BOTTOMEXTENDWIDTH: 20
		; TOPEXTENDWIDTH:    27
		local margins := struct_margins()
		if dllcall( "dwmapi\DwmExtendFrameIntoClientArea", "ptr", hWnd, "ptr", margins.ptr ) {
			; Handle error.
		}

		fCallDWP := true
		lRet := 0
	}


	if message == 0x00F { ; WM_PAINT
		local paint := struct_paint()
		local hdc := dllcall( "user32\BeginPaint", "Ptr", hWnd, "ptr", paint.ptr )
		PaintCustomCaption( hWnd, hdc )
		dllcall( "user32\EndPaint", "ptr", hWnd, "ptr", paint.ptr )
		fCallDWP := true
		lRet := 0
	}


	; Handle the non-client size message.
	if message == 0x083 and wParam == 1 {
		x1 := numget( lParam +0, 0,  "int" )
		y1 := numget( lParam +0, 4,  "int" )
		x2 := numget( lParam +0, 8,  "int" )
		y2 := numget( lParam +0, 12, "int" )

		old_x1 := numget( lParam +0, 32,      "int" )
		old_y1 := numget( lParam +0, 4 + 32,  "int" )
		old_x2 := numget( lParam +0, 8 + 32,  "int" )
		old_y2 := numget( lParam +0, 12 + 32, "int" )
		dx1 := abs( x1 - old_x1 )
		dx2 := abs( x2 - old_x2 )
		dy1 := abs( y1 - old_y1 )
		dy2 := abs( y2 - old_y2 )

		numput( x1 - dx1, lParam +0, 0,  "int" )
		numput( y1 - dy1, lParam +0, 4,  "int" )
		numput( x2 + dx2, lParam +0, 8,  "int" )
		numput( y2 + dy2, lParam +0, 12, "int" )

		lRet := 0

		; // No need to pass the message on to the DefWindowProc.
		fCallDWP := false
	}

		; // Handle hit testing in the NCA if not handled by DwmDefWindowProc.
		if message == 0x084 and lRet == 0 { ; WM_NCHITTEST
			lRet := HitTestNCA( hWnd, wParam, lParam )

			if ( lRet != 0 )
				fCallDWP := false
	}


	pfCallDWP := fCallDWP
	return lRet
}


; Paint the title on the custom frame.
PaintCustomCaption( hWnd, hdc) {
	local rcClient := struct_rect( hWnd )
	local hTheme := DllCall( "uxtheme\OpenThemeData", "ptr", hWnd, "str", "CompositedWindow::Window" )

	if ( hTheme ) {
		local hdcPaint := dllcall( "gdi32\CreateCompatibleDC", "uint", dllcall( "user32\GetDC", "uint", hWnd ))
		if ( hdcPaint ) {
			local cx := rcClient.width
			local cy := rcClient.height

			; Define the BITMAPINFO structure used to draw text.
			; Note that biHeight is negative. This is done because
			; DrawThemeTextEx() needs the bitmap to be in top-to-bottom
			; order.
			local dib := struct_bitmapinfo( cx, -cy )

			local ptr := a_ptrsize ? "uptr" : "uint"
			local hbm := dllcall( "gdi32\CreateDIBSection",
				ptr, hdc,
				ptr, dib.ptr,
				"uint", 0, ; DIB_RGB_COLORS
				A_PtrSize ? "uptr*" : "uint*", 0,
				ptr, 0,
				"uint", 0,
				ptr
			)

			if hbm {
				local hbmOld := dllcall( "gdi32\SelectObject", ptr, hdcPaint, ptr, hbm )
				; Setup the theme drawing options.
				local dttopts := bufferalloc( 64, 0 )
				numput( 64, dttopts, 0, "uint" ) ; dwSize
				numput( 0x2800, dttopts, 4, "uint" ) ; dwFlags (DTT_COMPOSITED | DTT_GLOWSIZE)
				numput( 1<<11|1<<13|1<<3, dttopts, 4 )
				numput( 15, dttopts, 52, "int" )
				numput( 0, dttopts, 20, "int" )

				; https://www.autoahk.com/archives/23733
				; Select a font.
				local logfont := bufferalloc( 60, 0 )
				numput( 5, logfont, 26, "int" )
				local hFontOld := 0
				if ! dllcall( "uxtheme\GetThemeSysFont", "int", hTheme, "int", 801, "uint", logfont.ptr ) {
					local hFont := dllcall( "CreateFontIndirect", "uint", logfont.ptr )
					hFontOld := dllcall( "gdi32\SelectObject", "int", hdcPaint,  "int", hFont )
				}


				local rcPaint := bufferalloc( 64, 0 )
				dllcall( "RtlMoveMemory", "uint", rcPaint.ptr, "uint", &rcClient, "uint", 16)

				numput( numget( rcPaint, 0, "int" ) +8, rcPaint, 0 )
				numput( numget( rcPaint, 4, "int" ) +8, rcPaint, 4 )
				numput( numget( rcPaint, 8, "int" ) -125, rcPaint, 8 )
				numput( numget( rcPaint, 12, "int" ) +50, rcPaint, 12 )

				; Draw the title.
				DllCall( "uxTheme\DrawThemeTextEx",
					"int", hTheme,
					"uint", hdcPaint,
					"int", 0,
					"int", 0,
					"uint", WSTR( "New Title Caption" ).ptr,
					"int", -1,
					"uint", 0x00000000|0x00040000,
					"uint", rcPaint.ptr,
					"uint", dttopts.ptr
				)

				; Blit text to the frame.
				DllCall( "BitBlt",
					"int", hdc,
					"int", 0,
					"int", 0,
					"int", cx,
					"int", cy,
					"uint", hdcPaint,
					"int", 0,
					"int", 0,
					"uInt", 0xCC0020
				)

				dllcall( "SelectObject", "uint", hdcPaint,"uint",hbmOld)
				if hFontOld
					dllcall( "SelectObject", "uint", hdcPaint, "uint", hFontOld )


				dllcall( "DeleteObject", "uint", hbm )
			}

			dllcall( "gdi32\DeleteDC", A_PtrSize ? "uptr" : "uint", hdcPaint )
		}

		dllcall( "Uxtheme\CloseThemeData", "ptr", hTheme )
	}
}



WSTR( str ) {
	local wide := bufferalloc(( strlen(str) * 2) + 1, 0 )
	dllcall( "MultiByteToWideChar", "uint", 0, "uint", 0, "str", str, "int", -1, "uint", wide.ptr, "int", strlen(str) +1 )
	return wide
}


GET_X_LPARAM( lParam ) {
	numput( lParam, Buffer := "    ", "uint" )
	return numget( Buffer, 0, "short" )
}


GET_Y_LPARAM( lParam ) {
	numput( lParam, Buffer := "    ", "uint" )
	return numget( Buffer, 2, "short" )
}


; Hit test the frame for resizing and moving.
HitTestNCA( hWnd, wParam, lParam ) {
	; Get the point coordinates for the hit test.
	local ptMouse := {
		"x": GET_X_LPARAM( lParam ),
		"y": GET_Y_LPARAM( lParam )
	}

	; Get the window rectangle.
	local rcWindow := struct_rect( hWnd )
	; Get the frame rectangle, adjusted for the style without a caption.
	local rcFrame := struct_rect( hWnd )

	; WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
	; AdjustWindowRectEx(&rcFrame, WS_OVERLAPPEDWINDOW & ~WS_CAPTION, FALSE, NULL);
	dllcall( "user32\AdjustWindowRectEx", "uint", rcFrame.ptr, "uint", (0 | 12582912 | 524288 | 262144 | 131072 | 65536) & ~12582912, "uint", 0, "uint", 0 )


	; Determine if the hit test is for resizing.
	local uRow := 2
	local uCol := 2
	local fOnResizeBorder := false

	; Determine if the point is at the top or bottom of the window.
	if ( ptMouse.y >= rcWindow.top and ptMouse.y < ( rcWindow.top + 27 )) {
		fOnResizeBorder := (ptMouse.y < ( rcWindow.top - rcFrame.top ))
		uRow := 1
	}
	else if ( ptMouse.y < rcWindow.bottom and ptMouse.y >= ( rcWindow.bottom - 20 )) {
		uRow := 3
	}

	; Determine if the point is at the left or right of the window.
	if ( ptMouse.x >= rcWindow.left and ptMouse.x < rcWindow.left + 8 ) {
		uCol := 1 ; left side
	}
	else if ( ptMouse.x < rcWindow.right and ptMouse.x >= rcWindow.right - 8 ) {
		uCol := 3 ; right side
	}

	; Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT)
	; { HTTOPLEFT,    fOnResizeBorder ? HTTOP : HTCAPTION,    HTTOPRIGHT }
	; { HTLEFT,       HTNOWHERE,     HTRIGHT }
	; { HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT }
	local hitTests := [
		[ 13, fOnResizeBorder ? 12 : 2, 14 ],
		[ 10,                      0,   11 ],
		[ 16,                      15,  17 ],
	]
	return hitTests[uRow][uCol]
}


; Message handler for the application.
AppWinProc( hWnd, message, wParam, lParam ) {
	local wmId
	local wmEvent
	local ps := struct_paint()
	local hdc := dllcall( "user32\BeginPaint", "Ptr", hWnd, "ptr", ps.ptr )
	local hr
	local result := 0

	if message == 0x001 {
		; continue
	}

	if message == 0x111 {
		wmId := wParam & 0xffff
		wmEvent := (wParam >> 16) & 0xffff
		if (wmId)
			return dllcall( "user32\DefWindowProc", "ptr", hWnd, "uint", message, "ptr", wParam, "ptr", lParam )
	}

	if message == 0x00F {
		local hdc := dllcall( "user32\BeginPaint", "Ptr", hWnd, "ptr", ps.ptr )
		PaintCustomCaption( hWnd, hdc )
		dllcall( "user32\EndPaint", "ptr", hWnd, "ptr", ps.ptr )
	}

	; return dllcall( "user32\DefWindowProc", "ptr", hWnd, "uint", message, "ptr", wParam, "ptr", lParam )
	return 0
}





struct_rect( handle := "" ) {
	local rect := bufferalloc( 16, 0 )
	if handle {
		dllcall( "user32.dll\GetWindowRect", "ptr", handle, "ptr", rect.ptr )
		rect.left   := numget( rect, 0, "int"  )
		rect.top    := numget( rect, 4, "int"  )
    rect.right  := numget( rect, 8, "int"  )
    rect.bottom := numget( rect, 12, "int" )
    rect.width  := rect.right  - rect.left
    rect.height := rect.bottom - rect.top
	}

	return rect
}

struct_margins() {
	local margins := bufferalloc( 16, 0 )
	numput( 1, margins.ptr, 0, "uint"  )
	numput( 1, margins.ptr, 4, "uint"  )
	numput( 1, margins.ptr, 8, "uint"  )
	numput( 1, margins.ptr, 12, "uint" )
	return margins
}

struct_paint() {
	return bufferalloc( 64, 0 )
}

struct_bitmapinfo( width, height, bpp := 32 ) {
	local bi := bufferalloc( 40, 0 )
	numput( 40, bi, 0, "uint" )
	numput( width, bi, 4, "uint" )
	numput( height, bi, 8, "uint" )
	numput( 1, bi, 12, "ushort" )
	numput( bpp, bi, 14, "ushort" )
	numput( 0, bi, 16, "uint" )
	return bi
}
User avatar
lmstearn
Posts: 698
Joined: 11 Aug 2016, 02:32
Contact:

Re: Custom Window Frame Using DWM/Aero

28 May 2020, 01:29

Just a couple of general things, (sorry, don't use V2):
The only way out of the tangle is to check and report the returns of all the DLLCalls, and employ a debugger.
Have you defined GET_X_LPARAM and GET_Y_LPARAM?

The wndproc looks a little odd- e.g. is there ever a case when fCallDWP is not true? Or perhaps it's my ignorance of V2 showing. :P
Also,
OnMessage wrote:Caution should be used when monitoring system messages (those below 0x400). For example, if a monitor function does not finish quickly, the response to the message might take longer than the system expects, which might cause side-effects. Unwanted behavior may also occur if a monitor function returns an integer to suppress further processing of a message, but the system expected different processing or a different response.
:arrow: itros "ylbbub eht tuO kaerB" a ni kcuts m'I pleH

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: peter_ahk, Rauvagol and 326 guests