-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathTaskDialog.ahk
453 lines (383 loc) · 23.4 KB
/
TaskDialog.ahk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
/*! TheGood
TaskDialog() - Launches a Task Dialog (Windows Vista and later only)
http://www.autohotkey.com/forum/viewtopic.php?t=58952
Last updated: August 22nd, 2010
________________________________________________________________________________________________________________________
Parameter: Description:
hParent Handle to the parent window. This will cause the Task Dialog to become modal. Leave 0 if that is
undesirable.
hParent could also be used as the sCallback parameter when it is the only parameter you wish to provide.
For example, using TaskDialog("CallbackFunc") is the same as
TaskDialog(0, "", "", 0, "", "", "CallbackFunc"). This is useful when you intend to build a multi-page
wizard using the hNavigate parameter, and you want to create your first page in your callback function
(so that it's easier to implement a Back button).
sText Text displayed inside the Task Dialog. It is a pipe-delimited list of the following components:
o sWindowTitle
o sMainInstruction
o sContent
o sExpandedInformation
o sFooter
o sVerificationText
o sCollapsedControlText
o sExpandedControlText
All components are optional. For example, to only fill in sWindowTitle and sMainInstruction, set sText
to "This is my window title|This is my main instruction." To only fill in sWindowTitle and sFooter, set
sText to "This is my window title||||This is my footer." To use the characters | and \ as part of the
text, escape them with a backslash (i.e. "\"). For example,
sText := "Window Title|A backslash in the instruction: \\|A pipe in the content: \||Expanded info."
To use a resource string, prefix the string ID by a backslash. You must specify the module handle or
path in the third parameter of sIcons. For example: sText := "\65430|The window title uses a res. ID"
sButtons Buttons to display in the Task Dialog. It is a pipe-delimited list of their caption text. It may be a
combination of custom buttons and common buttons. The following button texts are recognized as common
buttons: OK, YES, NO, CANCEL, RETRY, CLOSE. If this parameter is absent, an OK button will appear. Put
two pipes at the end of a button's caption text to make it the default button. To use the characters |
and \ as part of the text, escape them with a backslash (i.e. "\").
To use a resource string, prefix the string ID by a backslash. You must specify the module handle or
path in the third parameter of sIcons.
iFlags Combination of flags used to specify the behaviour of the Task Dialog. See MSDN for available flags and
their meaning (look at the dwFlags member): http://msdn.microsoft.com/en-us/library/bb787473
Their values are:
o TDF_ENABLE_HYPERLINKS 0x0001
o TDF_USE_HICON_MAIN 0x0002
o TDF_USE_HICON_FOOTER 0x0004
o TDF_ALLOW_DIALOG_CANCELLATION 0x0008
o TDF_USE_COMMAND_LINKS 0x0010
o TDF_USE_COMMAND_LINKS_NO_ICON 0x0020
o TDF_EXPAND_FOOTER_AREA 0x0040
o TDF_EXPANDED_BY_DEFAULT 0x0080
o TDF_VERIFICATION_FLAG_CHECKED 0x0100
o TDF_SHOW_PROGRESS_BAR 0x0200
o TDF_SHOW_MARQUEE_PROGRESS_BAR 0x0400
o TDF_CALLBACK_TIMER 0x0800
o TDF_POSITION_RELATIVE_TO_WINDOW 0x1000
o TDF_RTL_LAYOUT 0x2000
o TDF_NO_DEFAULT_RADIO_BUTTON 0x4000
o TDF_CAN_BE_MINIMIZED 0x8000
sIcons Pipe-delimited list of parameters that defines the icons used for the main icon and the footer icon. The
syntax is as follow (all parameters are optional - use 0 or keep blank if not needed):
<Main Icon Name/Handle/Resource ID>|<Footer Icon Name/Handle/Resourse ID>|<DLL/EXE path or module handle>
The following Icon Name is recognized: WARNING, ERROR, INFO, SHIELD, BLUE, YELLOW, RED, GREEN, GREY.
To use an icon handle, you can load it first using the API function LoadImage. Just make sure to use the
right icon size (the main icon is 32x32 and the footer is 16x16). You will also have to use the
appropriate flags (TDF_USE_HICON_MAIN and/or TDF_USE_HICON_FOOTER).
To use a resource icon, prefix its resource ID by a backslash. The icon resource ID is either a 16-bit
integer or a name. You must then specify the module handle or path in the third parameter. For example,
to use AutoHotkey's icon, use:
sIcons := "\159|\159|" . A_AhkPath (159 is the resource ID of the icon inside AutoHotkey.exe)
To use the characters | and \ as part of the icon name, escape them with a backslash (i.e. "\").
sRadios Radio buttons to display in the Task Dialog. It is a pipe-delimited list of their caption text. Put two
pipes at the end of a radio button's caption text to make it the default radio button. Otherwise, the
first radio button will be made the default one. Use the TDF_NO_DEFAULT_RADIO_BUTTON flag to counter
this. To use the characters | and \ as part of the text, escape them with a backslash (i.e. "\").
To use a resource string, prefix the string ID by a backslash. You must specify the module handle or
path in the third parameter of sIcons.
sCallback Specify a callback function. You may attach a specific reference data (a 32-bit int) by adding a pipe
and the int after. This int will be sent to the callback function when called (see dwRefData). For
example, sCallback := "MyFunction|4294967295". See MSDN for more information on the function's signature
and possible notifications: http://msdn.microsoft.com/en-us/library/bb760542
Notification and message values are: (WM_USER = 0x400)
o TDN_CREATED 0 o TDM_NAVIGATE_PAGE WM_USER+101
o TDN_NAVIGATED 1 o TDM_CLICK_BUTTON WM_USER+102
o TDN_BUTTON_CLICKED 2 o TDM_SET_MARQUEE_PROGRESS_BAR WM_USER+103
o TDN_HYPERLINK_CLICKED 3 o TDM_SET_PROGRESS_BAR_STATE WM_USER+104
o TDN_TIMER 4 o TDM_SET_PROGRESS_BAR_RANGE WM_USER+105
o TDN_DESTROYED 5 o TDM_SET_PROGRESS_BAR_POS WM_USER+106
o TDN_RADIO_BUTTON_CLICKED 6 o TDM_SET_PROGRESS_BAR_MARQUEE WM_USER+107
o TDN_DIALOG_CONSTRUCTED 7 o TDM_SET_ELEMENT_TEXT WM_USER+108
o TDN_VERIFICATION_CLICKED 8 o TDM_CLICK_RADIO_BUTTON WM_USER+110
o TDN_HELP 9 o TDM_ENABLE_BUTTON WM_USER+111
o TDN_EXPANDO_BUTTON_CLICKED 10 o TDM_ENABLE_RADIO_BUTTON WM_USER+112
o TDM_CLICK_VERIFICATION WM_USER+113
o TDM_UPDATE_ELEMENT_TEXT WM_USER+114
o TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE WM_USER+115
o TDM_UPDATE_ICON WM_USER+116
iWidth The width of the task dialog's client area. Leave 0 for the ideal width to be automatically calculated.
hNavigate Handle to an existing Task Dialog. This allows you to recreate the task dialog with new contents,
simulating the functionality of a multi-page wizard. It uses the TDM_NAVIGATE_PAGE message. See MSDN for
more details: http://msdn.microsoft.com/en-us/library/bb787509
Return value If the function succeeds, the return value is the ID of the button clicked. Common button IDs are:
o OK 1
o YES 6
o NO 7
o CANCEL 2
o RETRY 4
o CLOSE 8
If the button clicked is a custom button, the button ID is 1000 + <Button position in sButtons>.
Common buttons do not count. For example, if sButtons = "CUSTOM1|CUSTOM2|YES|NO|CUSTOM3" and the
user clicks on CUSTOM3, the return value will be 1003.
ErrorLevel additionally contains the radio button ID of the radio button selected in its highword,
and the value of the checkbox in its lowword. The radio button ID is 2000 + <Radio button position
in sRadios>. The checkbox value is either True or False. To extract them individually, you can use:
iSelectedRadioID := ErrorLevel >> 16, bChecked := ErrorLevel & 0xFFFF
If the function fails, the return value is 0 with ErrorLevel containing either the reason the
DllCall failed (like -1, -2, An, etc...) or TaskDialogIndirect's return value (see MSDN for values).
*/
TaskDialog(hParent = 0, sText = "", sButtons = "", iFlags = 0, sIcons = "", sRadios = "", sCallback = "", iWidth = 0, hNavigate = 0) {
;Check if Windows version is appropriate (i.e. at least Windows Vista/Server 2008)
If (DllCall("GetVersion") & 0xFF < 6) {
ErrorLevel := "You need at least Windows Vista or Windows Server 2008 to use TaskDialog()."
Return 0
}
;Check if hParent actually holds the callback function
If hParent is not integer
sCallback := hParent, hParent := 0
;Split text, if any
If sText {
_TaskDialog_PrepSplitString(sText)
StringSplit, sText, sText, % Chr(3)
}
;Check for resource numbers
Loop % sText0 {
If (SubStr(sText%A_Index%, 1, 1) = Chr(4)) {
s := SubStr(sText%A_Index%, 2)
If s is integer
sText%A_Index%_IsRes := True, sText%A_Index% := s
}
}
;Check if user wants custom buttons
If sButtons {
;Split buttons
_TaskDialog_PrepSplitString(sButtons)
StringSplit, sButtons, sButtons, % Chr(3)
;Extract common buttons and default button
Loop % sButtons0 {
If (sButtons%A_Index% = "OK") And (sLast := "OK")
iCommonButtons += (iCommonButtons & 0x0001 ? 0 : 0x0001)
Else If (sButtons%A_Index% = "YES") And (sLast := "YES")
iCommonButtons += (iCommonButtons & 0x0002 ? 0 : 0x0002)
Else If (sButtons%A_Index% = "NO") And (sLast := "NO")
iCommonButtons += (iCommonButtons & 0x0004 ? 0 : 0x0004)
Else If (sButtons%A_Index% = "CANCEL") And (sLast := "CANCEL")
iCommonButtons += (iCommonButtons & 0x0008 ? 0 : 0x0008)
Else If (sButtons%A_Index% = "RETRY") And (sLast := "RETRY")
iCommonButtons += (iCommonButtons & 0x0010 ? 0 : 0x0010)
Else If (sButtons%A_Index% = "CLOSE") And (sLast := "CLOSE")
iCommonButtons += (iCommonButtons & 0x0020 ? 0 : 0x0020)
Else If (sButtons%A_Index% = "") And Not iDefaultButtonID And sLast {
If (sLast = "OK")
iDefaultButtonID := 1
Else If (sLast = "YES")
iDefaultButtonID := 6
Else If (sLast = "NO")
iDefaultButtonID := 7
Else If (sLast = "CANCEL")
iDefaultButtonID := 2
Else If (sLast = "RETRY")
iDefaultButtonID := 4
Else If (sLast = "CLOSE")
iDefaultButtonID := 8
} Else {
sLast := ""
Continue
}
;Nuke it from the array
If (A_Index <> sButtons0) {
StringReplace, sButtons, sButtons, % sLast Chr(3)
;We're at the last one. Check if it's the only item left
} Else If Not InStr(sButtons, Chr(3)) {
sButtons := ""
Break
;Only nuke it
} Else StringReplace, sButtons, sButtons, % sLast
}
;Cure the array (also check for a default if we didn't find one yet in the common buttons) and re-split
i := _TaskDialog_CureStringArray(sButtons)
If Not iDefaultButtonID And i
iDefaultButtonID := i + 1000
StringSplit, sButtons, sButtons, % Chr(3)
;Check for resource numbers
Loop % sButtons0 {
If (SubStr(sButtons%A_Index%, 1, 1) = Chr(4)) {
s := SubStr(sButtons%A_Index%, 2)
If s is integer
sButtons%A_Index%_IsRes := True, sButtons%A_Index% := s
}
}
}
;Check if user wants radio buttons
If sRadios {
;Split radio buttons
_TaskDialog_PrepSplitString(sRadios)
StringSplit, sRadios, sRadios, % Chr(3)
;Cure the array and resplit
iDefaultRadioID := (i := _TaskDialog_CureStringArray(sRadios)) ? i + 2000 : 0
StringSplit, sRadios, sRadios, % Chr(3)
;Check for resource numbers
Loop % sRadios0 {
If (SubStr(sRadios%A_Index%, 1, 1) = Chr(4)) {
s := SubStr(sRadios%A_Index%, 2)
If s is integer
sRadios%A_Index%_IsRes := True, sRadios%A_Index% := s
}
}
}
;Translate the strings to UTF-16
Loop % sText0 {
If Not sText%A_Index%_IsRes
iText%A_Index% := &sText%A_Index%
Else iText%A_Index% := sText%A_Index%
}
Loop % sButtons0 {
If Not sButtons%A_Index%_IsRes
iButtons%A_Index% := &sButtons%A_Index%
Else iButtons%A_Index% := sButtons%A_Index%
}
Loop % sRadios0 {
If Not sRadios%A_Index%_IsRes
iRadios%A_Index% := &sRadios%A_Index%
Else iRadios%A_Index% := sRadios%A_Index%
}
;Prep the custom buttons, if any
If sButtons0 {
;Prep the TASKDIALOG_BUTTON struct
VarSetCapacity(tdbButtons, (4 + A_PtrSize) * sButtons0, 0)
Loop % sButtons0 {
NumPut( A_Index + 1000, tdbButtons, (4 + A_PtrSize) * (A_Index - 1) + 0, "UInt")
NumPut(iButtons%A_Index%, tdbButtons, (4 + A_PtrSize) * (A_Index - 1) + 4, "UPtr")
}
}
;Prep the radio buttons, if any
If sRadios0 {
;Prep the TASKDIALOG_BUTTON struct
VarSetCapacity(tdbRadios, (4 + A_PtrSize) * sRadios0, 0)
Loop % sRadios0 {
NumPut( A_Index + 2000, tdbRadios, (4 + A_PtrSize) * (A_Index - 1) + 0, "UInt")
NumPut(iRadios%A_Index%, tdbRadios, (4 + A_PtrSize) * (A_Index - 1) + 4, "UPtr")
}
}
;Split the icons
_TaskDialog_PrepSplitString(sIcons)
StringSplit, sIcons, sIcons, % Chr(3)
;Check what we were given
If sIcons0 And (sIcons0 < 3) {
;Icon can only be a handle or a common icon
iMainIcon := _TaskDialog_ResolveIcon(sIcons1)
iFooterIcon := _TaskDialog_ResolveIcon(sIcons2)
} Else If (sIcons0 = 3) {
;Check for resource IDs
If (SubStr(sIcons1, 1, 1) = Chr(4)) {
s := SubStr(sIcons1, 2)
If s is integer ;Check if resource ID is a number
iMainIcon := s
Else { ;Resource ID is a name
sMainIcon := s
iMainIcon := &sMainIcon
} ;Not resource ID -> Either a common icon or an icon handle
} Else iMainIcon := _TaskDialog_ResolveIcon(sIcons1)
;Check for resource IDs
If (SubStr(sIcons2, 1, 1) = Chr(4)) {
s := SubStr(sIcons2, 2)
If s is integer ;Check if resource ID is a number
iFooterIcon := s
Else { ;Resource ID is a name
sFooterIcon := s
iFooterIcon := &sFooterIcon
} ;Not resource ID -> Either a common icon or an icon handle
} Else iFooterIcon := _TaskDialog_ResolveIcon(sIcons2)
StringReplace, sIcons3, sIcons3, % Chr(4), \, All
;Check if it's a path (otherwise assume it's already a handle)
If (FileExist(sIcons3))
hModule := DllCall("LoadLibrary", "Str", sIcons3, "Ptr"), bUnload := True
Else hModule := sIcons3
}
;Split the callback string
StringSplit, sCallback, sCallback, |
;Check what we got
If (sCallback0 = 1)
sCBFunc := sCallback1
Else If (sCallback0 = 2) {
sCBFunc := sCallback1
sCBData := sCallback2
}
;Prep the TASKDIALOGCONFIG struct
VarSetCapacity(TDC, 160, 0), ptr := 0, z := A_PtrSize
NumPut( 4 * 8 + z * 16, TDC, ptr += 0, "UInt") ;cbSize
NumPut( hParent, TDC, ptr += 4, "UPtr") ;hwndParent
NumPut( hModule, TDC, ptr += z, "UPtr") ;hInstance
NumPut( iFlags, TDC, ptr += z, "UInt") ;dwFlags
NumPut( iCommonButtons, TDC, ptr += 4, "UInt") ;dwCommonButtons
NumPut( iText1, TDC, ptr += 4, "UPtr") ;pszWindowTitle
NumPut( iMainIcon, TDC, ptr += z, "UPtr") ;pszMainIcon
NumPut( iText2, TDC, ptr += z, "UPtr") ;pszMainInstruction
NumPut( iText3, TDC, ptr += z, "UPtr") ;pszContent
NumPut( sButtons0, TDC, ptr += z, "UInt") ;cButtons
NumPut( &tdbButtons, TDC, ptr += 4, "UPtr") ;pButtons
NumPut( iDefaultButtonID, TDC, ptr += z, "UInt") ;nDefaultButton
NumPut( sRadios0, TDC, ptr += 4, "UInt") ;cRadioButtons
NumPut( &tdbRadios, TDC, ptr += 4, "UPtr") ;pRadioButtons
NumPut( iDefaultRadioID, TDC, ptr += z, "UInt") ;nDefaultRadioButton
NumPut( iText6, TDC, ptr += 4, "UPtr") ;pszVerificationText
NumPut( iText4, TDC, ptr += z, "UPtr") ;pszExpandedInformation
NumPut( iText8, TDC, ptr += z, "UPtr") ;pszExpandedControlText
NumPut( iText7, TDC, ptr += z, "UPtr") ;pszCollapsedControlText
NumPut( iFooterIcon, TDC, ptr += z, "UPtr") ;pszFooterIcon
NumPut( iText5, TDC, ptr += z, "UPtr") ;pszFooter
NumPut( RegisterCallback(sCBFunc), TDC, ptr += z, "UPtr") ;pfCallback
NumPut( sCBData, TDC, ptr += z, "UPtr") ;lpCallbackData
NumPut( iWidth, TDC, ptr += z, "UInt") ;cxWidth
;Check if we're doing a TDM_NAVIGATE_PAGE
If hNavigate {
DetectHiddenWindows, On ;Just in case the Task Dialog is hidden
SendMessage, 0x400 + 101, 0, &TDC,, ahk_id %hNavigate%
If bUnload
DllCall("FreeLibrary", "Ptr", hModule)
} Else {
r := DllCall("TaskDialogIndirect", "Ptr", &TDC, "UInt*", iButtonClicked, "UInt*", iRadioChecked, "UInt*", bCheckboxChecked, "UInt")
sErrorLevel := ErrorLevel ;Remember error, if any
If bUnload
DllCall("FreeLibrary", "Ptr", hModule)
;Check for error
If Not r
r := ErrorLevel := iRadioChecked << 16 | bCheckboxChecked
Else ErrorLevel := sErrorLevel ? sErrorLevel : r ;Restore the error we had, if any
Return iButtonClicked
}
}
;INTERNAL FUNCTIONS
_TaskDialog_PrepSplitString(ByRef sString) {
Static flip
;Based on Lazslo's rev_bytes
;http://www.autohotkey.com/forum/viewtopic.php?p=189469#189469
If (flip = "") {
If (A_PtrSize = 4)
hex=8B4C24048B4424088D04413BC17619560FB750FE668B3183E80266893066891183C1023BC177E95EC3
Else hex=4863C24C8D04414C3BC176200F1F40000FB701410FB750FE4983E802664189006689114883C1024C3BC177E4F3C3
VarSetCapacity(flip, StrLen(hex) // 2)
Loop % StrLen(hex)//2
NumPut("0x" . SubStr(hex,2*A_Index-1,2), flip, A_Index-1, "Char")
DllCall("VirtualProtect", "Ptr", &flip, "Ptr", VarSetCapacity(flip), "UInt", 0x40, "UInt*", 0)
}
;We need to replace in reverse to keep the odd ones at the beginning
DllCall(&flip, "Ptr", &sString, "UInt", StrLen(sString), "CDecl")
StringReplace, sString, sString, \\, % Chr(1), All
DllCall(&flip, "Ptr", &sString, "UInt", StrLen(sString), "CDecl")
StringReplace, sString, sString, \|, % Chr(2), All
StringReplace, sString, sString, |, % Chr(3), All
StringReplace, sString, sString, \, % Chr(4), All
StringReplace, sString, sString, % Chr(2), |, All
StringReplace, sString, sString, % Chr(1), \, All
}
_TaskDialog_CureStringArray(ByRef sString) {
While (SubStr(sString, 1, 1) = Chr(3))
StringTrimLeft, sString, sString, 1
;Check for a default
If (i := InStr(sString, Chr(3) Chr(3))) {
;Check index by counting delimiters
j := 1, n := 1
While ((j := InStr(sString, Chr(3), False, j)) < i)
n += 1, j += 1
}
While (SubStr(sString, 0) = Chr(3))
StringTrimRight, sString, sString, 1
ErrorLevel := True
While ErrorLevel
StringReplace, sString, sString, % Chr(3) Chr(3), % Chr(3), UseErrorLevel
Return n
}
_TaskDialog_ResolveIcon(sIcon) {
Static sCommonIcons := "WARNING|ERROR|INFO|SHIELD|BLUE|YELLOW|RED|GREEN|GREY"
Loop, Parse, sCommonIcons, |
If (sIcon = A_LoopField)
Return 0x10000 - A_Index
Return sIcon
}