1 /* 2 * Task dialog control 3 * 4 * Copyright 2017 Fabian Maurer 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2.1 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 19 * 20 */ 21 22 #include "comctl32.h" 23 24 WINE_DEFAULT_DEBUG_CHANNEL(taskdialog); 25 26 #define ALIGNED_LENGTH(_Len, _Align) (((_Len)+(_Align))&~(_Align)) 27 #define ALIGNED_POINTER(_Ptr, _Align) ((LPVOID)ALIGNED_LENGTH((ULONG_PTR)(_Ptr), _Align)) 28 #define ALIGN_LENGTH(_Len, _Align) _Len = ALIGNED_LENGTH(_Len, _Align) 29 #define ALIGN_POINTER(_Ptr, _Align) _Ptr = ALIGNED_POINTER(_Ptr, _Align) 30 31 static const UINT DIALOG_MIN_WIDTH = 240; 32 static const UINT DIALOG_SPACING = 5; 33 static const UINT DIALOG_BUTTON_WIDTH = 50; 34 static const UINT DIALOG_BUTTON_HEIGHT = 14; 35 36 static const UINT ID_MAIN_INSTRUCTION = 0xf000; 37 static const UINT ID_CONTENT = 0xf001; 38 39 struct taskdialog_control 40 { 41 struct list entry; 42 DLGITEMTEMPLATE *template; 43 unsigned int template_size; 44 }; 45 46 struct taskdialog_button_desc 47 { 48 int id; 49 const WCHAR *text; 50 unsigned int width; 51 unsigned int line; 52 HINSTANCE hinst; 53 }; 54 55 struct taskdialog_template_desc 56 { 57 const TASKDIALOGCONFIG *taskconfig; 58 unsigned int dialog_height; 59 unsigned int dialog_width; 60 struct list controls; 61 WORD control_count; 62 LONG x_baseunit; 63 LONG y_baseunit; 64 HFONT font; 65 struct taskdialog_button_desc *default_button; 66 }; 67 68 struct taskdialog_info 69 { 70 HWND hwnd; 71 PFTASKDIALOGCALLBACK callback; 72 LONG_PTR callback_data; 73 }; 74 75 static void pixels_to_dialogunits(const struct taskdialog_template_desc *desc, LONG *width, LONG *height) 76 { 77 if (width) 78 *width = MulDiv(*width, 4, desc->x_baseunit); 79 if (height) 80 *height = MulDiv(*height, 8, desc->y_baseunit); 81 } 82 83 static void dialogunits_to_pixels(const struct taskdialog_template_desc *desc, LONG *width, LONG *height) 84 { 85 if (width) 86 *width = MulDiv(*width, desc->x_baseunit, 4); 87 if (height) 88 *height = MulDiv(*height, desc->y_baseunit, 8); 89 } 90 91 static void template_write_data(char **ptr, const void *src, unsigned int size) 92 { 93 memcpy(*ptr, src, size); 94 *ptr += size; 95 } 96 97 /* used to calculate size for the controls */ 98 static void taskdialog_get_text_extent(const struct taskdialog_template_desc *desc, const WCHAR *text, 99 BOOL user_resource, SIZE *sz) 100 { 101 RECT rect = { 0, 0, desc->dialog_width - DIALOG_SPACING * 2, 0}; /* padding left and right of the control */ 102 const WCHAR *textW = NULL; 103 static const WCHAR nulW; 104 unsigned int length; 105 HFONT oldfont; 106 HDC hdc; 107 108 if (IS_INTRESOURCE(text)) 109 { 110 if (!(length = LoadStringW(user_resource ? desc->taskconfig->hInstance : COMCTL32_hModule, 111 (UINT_PTR)text, (WCHAR *)&textW, 0))) 112 { 113 WARN("Failed to load text\n"); 114 textW = &nulW; 115 length = 0; 116 } 117 } 118 else 119 { 120 textW = text; 121 length = strlenW(textW); 122 } 123 124 hdc = GetDC(0); 125 oldfont = SelectObject(hdc, desc->font); 126 127 dialogunits_to_pixels(desc, &rect.right, NULL); 128 DrawTextW(hdc, textW, length, &rect, DT_LEFT | DT_EXPANDTABS | DT_CALCRECT | DT_WORDBREAK); 129 pixels_to_dialogunits(desc, &rect.right, &rect.bottom); 130 131 SelectObject(hdc, oldfont); 132 ReleaseDC(0, hdc); 133 134 sz->cx = rect.right - rect.left; 135 sz->cy = rect.bottom - rect.top; 136 } 137 138 static unsigned int taskdialog_add_control(struct taskdialog_template_desc *desc, WORD id, const WCHAR *class, 139 HINSTANCE hInstance, const WCHAR *text, DWORD style, short x, short y, short cx, short cy) 140 { 141 struct taskdialog_control *control = Alloc(sizeof(*control)); 142 unsigned int size, class_size, text_size; 143 DLGITEMTEMPLATE *template; 144 static const WCHAR nulW; 145 const WCHAR *textW; 146 char *ptr; 147 148 class_size = (strlenW(class) + 1) * sizeof(WCHAR); 149 150 if (IS_INTRESOURCE(text)) 151 text_size = LoadStringW(hInstance, (UINT_PTR)text, (WCHAR *)&textW, 0) * sizeof(WCHAR); 152 else 153 { 154 textW = text; 155 text_size = strlenW(textW) * sizeof(WCHAR); 156 } 157 158 size = sizeof(DLGITEMTEMPLATE); 159 size += class_size; 160 size += text_size + sizeof(WCHAR); 161 size += sizeof(WORD); /* creation data */ 162 163 control->template = template = Alloc(size); 164 control->template_size = size; 165 166 template->style = WS_VISIBLE | style; 167 template->dwExtendedStyle = 0; 168 template->x = x; 169 template->y = y; 170 template->cx = cx; 171 template->cy = cy; 172 template->id = id; 173 ptr = (char *)(template + 1); 174 template_write_data(&ptr, class, class_size); 175 template_write_data(&ptr, textW, text_size); 176 template_write_data(&ptr, &nulW, sizeof(nulW)); 177 178 list_add_tail(&desc->controls, &control->entry); 179 desc->control_count++; 180 return ALIGNED_LENGTH(size, 3); 181 } 182 183 static unsigned int taskdialog_add_static_label(struct taskdialog_template_desc *desc, WORD id, const WCHAR *str) 184 { 185 unsigned int size; 186 SIZE sz; 187 188 if (!str) 189 return 0; 190 191 taskdialog_get_text_extent(desc, str, TRUE, &sz); 192 193 desc->dialog_height += DIALOG_SPACING; 194 size = taskdialog_add_control(desc, id, WC_STATICW, desc->taskconfig->hInstance, str, 0, DIALOG_SPACING, 195 desc->dialog_height, sz.cx, sz.cy); 196 desc->dialog_height += sz.cy + DIALOG_SPACING; 197 return size; 198 } 199 200 static unsigned int taskdialog_add_main_instruction(struct taskdialog_template_desc *desc) 201 { 202 return taskdialog_add_static_label(desc, ID_MAIN_INSTRUCTION, desc->taskconfig->pszMainInstruction); 203 } 204 205 static unsigned int taskdialog_add_content(struct taskdialog_template_desc *desc) 206 { 207 return taskdialog_add_static_label(desc, ID_CONTENT, desc->taskconfig->pszContent); 208 } 209 210 static void taskdialog_init_button(struct taskdialog_button_desc *button, struct taskdialog_template_desc *desc, 211 int id, const WCHAR *text, BOOL custom_button) 212 { 213 SIZE sz; 214 215 taskdialog_get_text_extent(desc, text, custom_button, &sz); 216 217 button->id = id; 218 button->text = text; 219 button->width = max(DIALOG_BUTTON_WIDTH, sz.cx + DIALOG_SPACING * 2); 220 button->line = 0; 221 button->hinst = custom_button ? desc->taskconfig->hInstance : COMCTL32_hModule; 222 223 if (id == desc->taskconfig->nDefaultButton) 224 desc->default_button = button; 225 } 226 227 static void taskdialog_init_common_buttons(struct taskdialog_template_desc *desc, struct taskdialog_button_desc *buttons, 228 unsigned int *button_count) 229 { 230 DWORD flags = desc->taskconfig->dwCommonButtons; 231 232 #define TASKDIALOG_INIT_COMMON_BUTTON(id) \ 233 do { \ 234 taskdialog_init_button(&buttons[(*button_count)++], desc, ID##id, MAKEINTRESOURCEW(IDS_BUTTON_##id), FALSE); \ 235 } while(0) 236 237 if (flags & TDCBF_OK_BUTTON) 238 TASKDIALOG_INIT_COMMON_BUTTON(OK); 239 if (flags & TDCBF_YES_BUTTON) 240 TASKDIALOG_INIT_COMMON_BUTTON(YES); 241 if (flags & TDCBF_NO_BUTTON) 242 TASKDIALOG_INIT_COMMON_BUTTON(NO); 243 if (flags & TDCBF_RETRY_BUTTON) 244 TASKDIALOG_INIT_COMMON_BUTTON(RETRY); 245 if (flags & TDCBF_CANCEL_BUTTON) 246 TASKDIALOG_INIT_COMMON_BUTTON(CANCEL); 247 if (flags & TDCBF_CLOSE_BUTTON) 248 TASKDIALOG_INIT_COMMON_BUTTON(CLOSE); 249 250 #undef TASKDIALOG_INIT_COMMON_BUTTON 251 } 252 253 static unsigned int taskdialog_add_buttons(struct taskdialog_template_desc *desc) 254 { 255 unsigned int count = 0, buttons_size, i, line_count, size = 0; 256 unsigned int location_x, *line_widths, alignment = ~0u; 257 const TASKDIALOGCONFIG *taskconfig = desc->taskconfig; 258 struct taskdialog_button_desc *buttons; 259 260 /* Allocate enough memory for the custom and the default buttons. Maximum 6 default buttons possible. */ 261 buttons_size = 6; 262 if (taskconfig->cButtons && taskconfig->pButtons) 263 buttons_size += taskconfig->cButtons; 264 265 if (!(buttons = Alloc(buttons_size * sizeof(*buttons)))) 266 return 0; 267 268 /* Custom buttons */ 269 if (taskconfig->cButtons && taskconfig->pButtons) 270 for (i = 0; i < taskconfig->cButtons; i++) 271 taskdialog_init_button(&buttons[count++], desc, taskconfig->pButtons[i].nButtonID, 272 taskconfig->pButtons[i].pszButtonText, TRUE); 273 274 /* Common buttons */ 275 taskdialog_init_common_buttons(desc, buttons, &count); 276 277 /* There must be at least one button */ 278 if (count == 0) 279 taskdialog_init_button(&buttons[count++], desc, IDOK, MAKEINTRESOURCEW(IDS_BUTTON_OK), FALSE); 280 281 if (!desc->default_button) 282 desc->default_button = &buttons[0]; 283 284 /* For easy handling just allocate as many lines as buttons, the worst case. */ 285 line_widths = Alloc(count * sizeof(*line_widths)); 286 287 /* Separate buttons into lines */ 288 location_x = DIALOG_SPACING; 289 for (i = 0, line_count = 0; i < count; i++) 290 { 291 if (location_x + buttons[i].width + DIALOG_SPACING > desc->dialog_width) 292 { 293 location_x = DIALOG_SPACING; 294 line_count++; 295 } 296 297 buttons[i].line = line_count; 298 299 location_x += buttons[i].width + DIALOG_SPACING; 300 line_widths[line_count] += buttons[i].width + DIALOG_SPACING; 301 } 302 line_count++; 303 304 /* Try to balance lines so they are about the same size */ 305 for (i = 1; i < line_count - 1; i++) 306 { 307 int diff_now = abs(line_widths[i] - line_widths[i - 1]); 308 unsigned int j, last_button = 0; 309 int diff_changed; 310 311 for (j = 0; j < count; j++) 312 if (buttons[j].line == i - 1) 313 last_button = j; 314 315 /* Difference in length of both lines if we wrapped the last button from the last line into this one */ 316 diff_changed = abs(2 * buttons[last_button].width + line_widths[i] - line_widths[i - 1]); 317 318 if (diff_changed < diff_now) 319 { 320 buttons[last_button].line = i; 321 line_widths[i] += buttons[last_button].width; 322 line_widths[i - 1] -= buttons[last_button].width; 323 } 324 } 325 326 /* Calculate left alignment so all lines are as far right as possible. */ 327 for (i = 0; i < line_count; i++) 328 { 329 int new_alignment = desc->dialog_width - line_widths[i]; 330 if (new_alignment < alignment) 331 alignment = new_alignment; 332 } 333 334 /* Now that we got them all positioned, create all buttons */ 335 location_x = alignment; 336 for (i = 0; i < count; i++) 337 { 338 DWORD style = &buttons[i] == desc->default_button ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON; 339 340 if (i > 0 && buttons[i].line != buttons[i - 1].line) /* New line */ 341 { 342 location_x = alignment; 343 desc->dialog_height += DIALOG_BUTTON_HEIGHT + DIALOG_SPACING; 344 } 345 346 size += taskdialog_add_control(desc, buttons[i].id, WC_BUTTONW, buttons[i].hinst, buttons[i].text, style, 347 location_x, desc->dialog_height, buttons[i].width, DIALOG_BUTTON_HEIGHT); 348 349 location_x += buttons[i].width + DIALOG_SPACING; 350 } 351 352 /* Add height for last row and spacing */ 353 desc->dialog_height += DIALOG_BUTTON_HEIGHT + DIALOG_SPACING; 354 355 Free(line_widths); 356 Free(buttons); 357 358 return size; 359 } 360 361 static void taskdialog_clear_controls(struct list *controls) 362 { 363 struct taskdialog_control *control, *control2; 364 365 LIST_FOR_EACH_ENTRY_SAFE(control, control2, controls, struct taskdialog_control, entry) 366 { 367 list_remove(&control->entry); 368 Free(control->template); 369 Free(control); 370 } 371 } 372 373 static unsigned int taskdialog_get_reference_rect(const struct taskdialog_template_desc *desc, RECT *ret) 374 { 375 HMONITOR monitor = MonitorFromWindow(desc->taskconfig->hwndParent ? desc->taskconfig->hwndParent : GetActiveWindow(), 376 MONITOR_DEFAULTTOPRIMARY); 377 MONITORINFO info; 378 379 info.cbSize = sizeof(info); 380 GetMonitorInfoW(monitor, &info); 381 382 if (desc->taskconfig->dwFlags & TDF_POSITION_RELATIVE_TO_WINDOW && desc->taskconfig->hwndParent) 383 GetWindowRect(desc->taskconfig->hwndParent, ret); 384 else 385 *ret = info.rcWork; 386 387 pixels_to_dialogunits(desc, &ret->left, &ret->top); 388 pixels_to_dialogunits(desc, &ret->right, &ret->bottom); 389 390 pixels_to_dialogunits(desc, &info.rcWork.left, &info.rcWork.top); 391 pixels_to_dialogunits(desc, &info.rcWork.right, &info.rcWork.bottom); 392 return info.rcWork.right - info.rcWork.left; 393 } 394 395 static WCHAR *taskdialog_get_exe_name(const TASKDIALOGCONFIG *taskconfig, WCHAR *name, DWORD length) 396 { 397 DWORD len = GetModuleFileNameW(NULL, name, length); 398 if (len && len < length) 399 { 400 WCHAR *p; 401 if ((p = strrchrW(name, '/'))) name = p + 1; 402 if ((p = strrchrW(name, '\\'))) name = p + 1; 403 return name; 404 } 405 else 406 return NULL; 407 } 408 409 static DLGTEMPLATE *create_taskdialog_template(const TASKDIALOGCONFIG *taskconfig) 410 { 411 struct taskdialog_control *control, *control2; 412 unsigned int size, title_size, screen_width; 413 struct taskdialog_template_desc desc; 414 static const WORD fontsize = 0x7fff; 415 static const WCHAR emptyW[] = { 0 }; 416 const WCHAR *titleW = NULL; 417 DLGTEMPLATE *template; 418 NONCLIENTMETRICSW ncm; 419 WCHAR pathW[MAX_PATH]; 420 RECT ref_rect; 421 char *ptr; 422 HDC hdc; 423 424 /* Window title */ 425 if (!taskconfig->pszWindowTitle) 426 titleW = taskdialog_get_exe_name(taskconfig, pathW, sizeof(pathW)/sizeof(pathW[0])); 427 else if (IS_INTRESOURCE(taskconfig->pszWindowTitle)) 428 { 429 if (!LoadStringW(taskconfig->hInstance, LOWORD(taskconfig->pszWindowTitle), (WCHAR *)&titleW, 0)) 430 titleW = taskdialog_get_exe_name(taskconfig, pathW, sizeof(pathW)/sizeof(pathW[0])); 431 } 432 else 433 titleW = taskconfig->pszWindowTitle; 434 if (!titleW) 435 titleW = emptyW; 436 title_size = (strlenW(titleW) + 1) * sizeof(WCHAR); 437 438 size = sizeof(DLGTEMPLATE) + 2 * sizeof(WORD); 439 size += title_size; 440 size += 2; /* font size */ 441 442 list_init(&desc.controls); 443 desc.taskconfig = taskconfig; 444 desc.control_count = 0; 445 446 ncm.cbSize = sizeof(ncm); 447 SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0); 448 desc.font = CreateFontIndirectW(&ncm.lfMessageFont); 449 450 hdc = GetDC(0); 451 SelectObject(hdc, desc.font); 452 desc.x_baseunit = GdiGetCharDimensions(hdc, NULL, &desc.y_baseunit); 453 ReleaseDC(0, hdc); 454 455 screen_width = taskdialog_get_reference_rect(&desc, &ref_rect); 456 457 desc.dialog_height = 0; 458 desc.dialog_width = max(taskconfig->cxWidth, DIALOG_MIN_WIDTH); 459 desc.dialog_width = min(desc.dialog_width, screen_width); 460 desc.default_button = NULL; 461 462 size += taskdialog_add_main_instruction(&desc); 463 size += taskdialog_add_content(&desc); 464 size += taskdialog_add_buttons(&desc); 465 466 template = Alloc(size); 467 if (!template) 468 { 469 taskdialog_clear_controls(&desc.controls); 470 DeleteObject(desc.font); 471 return NULL; 472 } 473 474 template->style = DS_MODALFRAME | DS_SETFONT | WS_CAPTION | WS_VISIBLE | WS_SYSMENU; 475 template->cdit = desc.control_count; 476 template->x = (ref_rect.left + ref_rect.right + desc.dialog_width) / 2; 477 template->y = (ref_rect.top + ref_rect.bottom + desc.dialog_height) / 2; 478 template->cx = desc.dialog_width; 479 template->cy = desc.dialog_height; 480 481 ptr = (char *)(template + 1); 482 ptr += 2; /* menu */ 483 ptr += 2; /* class */ 484 template_write_data(&ptr, titleW, title_size); 485 template_write_data(&ptr, &fontsize, sizeof(fontsize)); 486 487 /* write control entries */ 488 LIST_FOR_EACH_ENTRY_SAFE(control, control2, &desc.controls, struct taskdialog_control, entry) 489 { 490 ALIGN_POINTER(ptr, 3); 491 492 template_write_data(&ptr, control->template, control->template_size); 493 494 /* list item won't be needed later */ 495 list_remove(&control->entry); 496 Free(control->template); 497 Free(control); 498 } 499 500 DeleteObject(desc.font); 501 return template; 502 } 503 504 static HRESULT taskdialog_notify(struct taskdialog_info *dialog_info, UINT notification, WPARAM wparam, LPARAM lparam) 505 { 506 return dialog_info->callback ? dialog_info->callback(dialog_info->hwnd, notification, wparam, lparam, 507 dialog_info->callback_data) : S_OK; 508 } 509 510 static void taskdialog_on_button_click(struct taskdialog_info *dialog_info, WORD command_id) 511 { 512 if (taskdialog_notify(dialog_info, TDN_BUTTON_CLICKED, command_id, 0) == S_OK) 513 EndDialog(dialog_info->hwnd, command_id); 514 } 515 516 static INT_PTR CALLBACK taskdialog_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 517 { 518 static const WCHAR taskdialog_info_propnameW[] = {'T','a','s','k','D','i','a','l','o','g','I','n','f','o',0}; 519 struct taskdialog_info *dialog_info; 520 521 TRACE("hwnd=%p msg=0x%04x wparam=%lx lparam=%lx\n", hwnd, msg, wParam, lParam); 522 523 if (msg != WM_INITDIALOG) 524 dialog_info = GetPropW(hwnd, taskdialog_info_propnameW); 525 526 switch (msg) 527 { 528 case TDM_CLICK_BUTTON: 529 taskdialog_on_button_click(dialog_info, LOWORD(wParam)); 530 break; 531 case WM_INITDIALOG: 532 dialog_info = (struct taskdialog_info *)lParam; 533 dialog_info->hwnd = hwnd; 534 SetPropW(hwnd, taskdialog_info_propnameW, dialog_info); 535 536 taskdialog_notify(dialog_info, TDN_DIALOG_CONSTRUCTED, 0, 0); 537 break; 538 case WM_SHOWWINDOW: 539 taskdialog_notify(dialog_info, TDN_CREATED, 0, 0); 540 break; 541 case WM_COMMAND: 542 if (HIWORD(wParam) == BN_CLICKED) 543 { 544 taskdialog_on_button_click(dialog_info, LOWORD(wParam)); 545 return TRUE; 546 } 547 break; 548 case WM_DESTROY: 549 taskdialog_notify(dialog_info, TDN_DESTROYED, 0, 0); 550 RemovePropW(hwnd, taskdialog_info_propnameW); 551 break; 552 } 553 return FALSE; 554 } 555 556 /*********************************************************************** 557 * TaskDialogIndirect [COMCTL32.@] 558 */ 559 HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *taskconfig, int *button, 560 int *radio_button, BOOL *verification_flag_checked) 561 { 562 struct taskdialog_info dialog_info; 563 DLGTEMPLATE *template; 564 INT ret; 565 566 TRACE("%p, %p, %p, %p\n", taskconfig, button, radio_button, verification_flag_checked); 567 568 if (!taskconfig || taskconfig->cbSize != sizeof(TASKDIALOGCONFIG)) 569 return E_INVALIDARG; 570 571 dialog_info.callback = taskconfig->pfCallback; 572 dialog_info.callback_data = taskconfig->lpCallbackData; 573 574 template = create_taskdialog_template(taskconfig); 575 ret = (short)DialogBoxIndirectParamW(taskconfig->hInstance, template, taskconfig->hwndParent, 576 taskdialog_proc, (LPARAM)&dialog_info); 577 Free(template); 578 579 if (button) *button = ret; 580 if (radio_button) *radio_button = taskconfig->nDefaultButton; 581 if (verification_flag_checked) *verification_flag_checked = TRUE; 582 583 return S_OK; 584 } 585 586 /*********************************************************************** 587 * TaskDialog [COMCTL32.@] 588 */ 589 HRESULT WINAPI TaskDialog(HWND owner, HINSTANCE hinst, const WCHAR *title, const WCHAR *main_instruction, 590 const WCHAR *content, TASKDIALOG_COMMON_BUTTON_FLAGS common_buttons, const WCHAR *icon, int *button) 591 { 592 TASKDIALOGCONFIG taskconfig; 593 594 TRACE("%p, %p, %s, %s, %s, %#x, %s, %p\n", owner, hinst, debugstr_w(title), debugstr_w(main_instruction), 595 debugstr_w(content), common_buttons, debugstr_w(icon), button); 596 597 memset(&taskconfig, 0, sizeof(taskconfig)); 598 taskconfig.cbSize = sizeof(taskconfig); 599 taskconfig.hwndParent = owner; 600 taskconfig.hInstance = hinst; 601 taskconfig.dwCommonButtons = common_buttons; 602 taskconfig.pszWindowTitle = title; 603 taskconfig.u.pszMainIcon = icon; 604 taskconfig.pszMainInstruction = main_instruction; 605 taskconfig.pszContent = content; 606 return TaskDialogIndirect(&taskconfig, button, NULL, NULL); 607 } 608