Moving a TaskDialog

Put simple Tips and Tricks that are not entire Tutorials in this forum
User avatar
lmstearn
Posts: 695
Joined: 11 Aug 2016, 02:32
Contact:

Moving a TaskDialog

18 Mar 2022, 08:35

Had trouble with part of a TaskDialog going offscreen below the taskbar when the "See Details" button was clicked. Turns out it can be done through the callback, so thought it would be an idea to post the code here in case anyone else has the same issue.
The code creates a test form and a test TaskDialog with an expando ("See Details" ) button. It captures the height of the expanded form (hopefully) before it gets to WM_PAINT, thereby adjusting the vertical co-ordinates of the TaskDialog:

Code: Select all

Gui, testGui: New, -DPIScale -MaximizeBox -MinimizeBox +OwnDialogs +LastFound
	Global hwndParent, testGuiH
	testGuiW := floor(A_ScreenWidth/6)
	testGuiH := floor(A_ScreenHeight/9)

	Gui, testGui: Add, Button, center Default w%testGuiW% h%testGuiH% gRunTaskDialog vTaskDlg, Run TaskDialog
	Gui, testGui: Default 
	GuiControl, testGui: Move, TaskDlg, x%testGuiW% y%testGuiH%

	testGuiW := floor(A_ScreenWidth/2)
	testGuiH := floor(A_ScreenHeight/3)


	Gui, testGui: Show, % "y" . A_ScreenHeight - testGuiH . " w" . testGuiW . " h" . testGuiH
	Return


	RunTaskDialog:
	retVal := TaskDialog("Test", "Test Issue", "", "Lorem ipsum dolor sit amet,`nconsectetur adipiscing elit,`nsed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.`nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident,`nsunt in culpa qui officia deserunt mollit anim id est laborum.", , "Close Test")
	Return

	Esc::
	testGuiGuiClose:
	ExitApp

	; Task dialog
	TaskDialog(pageTitle := "Page Title", instructionTitle := "Description of issue", description := "Choose one of the following options:", expandedText := "More Information with a <A HREF=""http://www.some_link.com"">Link</a>", checkText := "Do not show this again", choice1 := "", choice2 := "", choice3 := "", choice4 := "")
	{
	; This function requires A_Unicode and Vista or later.
	Static FooterText := ""
	; Error Flags
	Static S_OK = 0x0, E_OUTOFMEMORY = 0x8007000E, E_INVALIDARG = 0x80070057, E_FAIL = 0x80004005, E_ACCESSDENIED = 0x80070005

	;General Flags
	Static flags = 0x1011, TDF_VERIFICATION_FLAG_CHECKED = 0x0100, TDF_CALLBACK_TIMER := 0X0800

	; 0x1	:		TDF_ENABLE_HYPERLINKS
	; 0X0010:		TDF_USE_COMMAND_LINKS
	; 0x1000:		TDF_POSITION_RELATIVE_TO_WINDOW 
	; 0x1000000:	TDF_SIZE_TO_CONTENT

	CustomButtons := []

	Global hwndParent := WinExist("A")

		if (!(TDCallback := RegisterCallback("TDCallback", "Fast")))
		{
			MsgBox, 8208, Task Dialog, Could not Register Callback for the Task dialog.
			return 0
		}

		While (tp := choice%A_Index%)
		CustomButtons.Push(100 + A_Index, tp)


	cButtons := CustomButtons.Length()/2
	VarSetCapacity(pButtons, 4 * cButtons + A_PtrSize * cButtons, 0)

		loop %cButtons%
		{
		iButtonID := CustomButtons[2 * A_Index -1]
		iButtonText := &(b%A_Index% := CustomButtons[2 * A_Index])
		NumPut(iButtonID,   pButtons, (4 + A_PtrSize) * (A_Index - 1), "Int")
		NumPut(iButtonText, pButtons, (4 + A_PtrSize) * A_Index - A_PtrSize, "Ptr")
		}


	; TASKDIALOGCONFIG structure
		if (A_PtrSize == 8) ; X64
		{
		NumPut(VarSetCapacity(TDC, 160, 0), TDC, 0, "UInt") ; cbSize
		NumPut(hwndParent, TDC, 4, "Ptr") ; hwndParent
		;  HINSTANCE
		NumPut(flags, TDC, 20, "Int") ; dwflags
		NumPut(&pageTitle, TDC, 28, "Ptr") ; pszWindowTitle
		NumPut(&instructionTitle, TDC, 44, "Ptr") ; pszMainInstruction
		NumPut(&description, TDC, 52, "Ptr") ; pszContent
		NumPut(cButtons, TDC, 60, "UInt") ; cButtons
		NumPut(&pButtons, TDC, 64, "Ptr") ; pButtons
		NumPut(&checkText, TDC, 92, "Ptr") ; pszVerificationText
		NumPut(&ExpandedText, TDC, 100, "Ptr") ; pszExpandedInformation
		NumPut(&FooterText, TDC, 132, "Ptr") ; pszFooter
		NumPut(TDCallback, TDC, 140, "Ptr") ; pfCallback
		}
		else
		return

	retVal := DllCall("Comctl32.dll\TaskDialogIndirect", "Ptr", &TDC
		, "Int*", Button := 0
		, "Int*", Radio := 0
		, "Int*", Checked := 0)

	; various error checks omitted for brevity	

	return retVal
	}
	TDCallback(hWnd, Notification, wParam, lParam, RefData)
	{
	Static tdYpos := 0
	Static TDN_CREATED := 0, TDN_HYPERLINK_CLICKED := 3, TDN_EXPANDO_BUTTON_CLICKED := 10
		switch (Notification)
		{
			Case TDN_CREATED:
			{
			VarSetCapacity(rect, 16, 0)

				if (DllCall("GetWindowRect", "Ptr", hWnd, "Ptr", &rect))
				tdYpos := NumGet(rect, 4, "int")

			VarSetCapacity(rect, 0)
			}
			Case TDN_HYPERLINK_CLICKED:
			{
			url := StrGet(lParam, "UTF-16") ; <A HREF="URL">Link</A>
			Run %url%
			}

			Case TDN_EXPANDO_BUTTON_CLICKED:
			{
				if (wParam)
				tdYposOut := 0
				else
				tdYposOut := tdYpos

			hWndObj := {hWnd:hWnd, tdYpos:tdYposOut}

			MoveTDN(hWndObj)

			hWndObj := ""
			}
			Default:
		}
	}

MoveTDN(hWndObj)
{
	timerVal := -20
	Timer := Func("TDNTimer").Bind(A_ThisFunc, hWndObj)

	SetTimer % Timer, %timerVal%
	Return
}

TDNTimer(FuncName, hWndObj)
{

	VarSetCapacity(rect, 16, 0)

		if (!DllCall("GetWindowRect", "Ptr", hWndObj.hWnd, "Ptr", &rect))
		return
	tdxpos := NumGet(rect, 0, "int")

		if (hWndObj.tdYpos)
		hWndNewObj := hWndObj
		else
		{
		WinGetPos,,,, h, ahk_class Shell_TrayWnd
			if (h > A_ScreenHeight - 100) ; vert taskbar
			h := 0
		tdYposIn := A_ScreenHeight - h - (NumGet(rect, 12, "int") - NumGet(rect, 4, "int"))
		hWndNewObj := {hWnd:hWndObj.hWnd, tdYpos:tdYposIn}
		}

	VarSetCapacity(rect, 0)
	DllCall("SetWindowPos", "uint", hWndNewObj.hWnd, "uint", hwnd_prev
	, "int", tdxpos, "int", hWndNewObj.tdYpos, "int", 0, "int", 0, "uint", 0)
}
The script only just does the job, so welcome other better methods. One issue was a hard time attempting to modify the tdYpos element in hWndObj in TDNTimer()- it must be the object is only passed through as a reference then- no problem at all with the other object created within the function.
Edit: It came as to why- it's bound to MoveTDN, so cannot be written to from anywhere else.
Still learning. :)
Edit: On slower rigs, timerVal has to be set at multiples of its current setting for the same effect, thus it must vary from system to system.
Last edited by BoBo on 18 Mar 2022, 09:16, edited 1 time in total.
Reason: Moved to 'Tipps & Tricks' as requested by the OP.
:arrow: itros "ylbbub eht tuO kaerB" a ni kcuts m'I pleH

Return to “Tips and Tricks (v1)”

Who is online

Users browsing this forum: No registered users and 62 guests