Skip to content

Commit 5fbdbde

Browse files
authored
Implement the Windows Task Dialog (#1133)
Fixes #146
1 parent dc8dfba commit 5fbdbde

File tree

59 files changed

+9089
-4
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+9089
-4
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Windows Task Dialog Issue
2+
3+
## Access Violation when receiving [`TDN_NAVIGATED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-navigated) within [`TDN_BUTTON_CLICKED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-button-clicked)
4+
5+
An Access violation can occur when receiving a navigation notification ([`TDN_NAVIGATED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-navigated))
6+
within a [`TDN_BUTTON_CLICKED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-button-clicked)
7+
notification (e.g. due to running the message loop) and then returning `S_OK`.
8+
9+
* Run the code.
10+
* Click on the "My Custom Button" button.
11+
* Notice that the dialog navigates and an inner dialog shows.
12+
* Close the inner dialog by clicking the OK button.
13+
* **Issue:** In most cases, an access violation occurs now (if not, try again a
14+
few times).
15+
* Notice the problem also occurs when you (after the navigation) first close
16+
the original dialog, and then close the inner dialog.
17+
* The problem can be avoided by returning `S_FALSE` from the `TDN_BUTTON_CLICKED`
18+
notification.
19+
20+
```cpp
21+
#include "stdafx.h"
22+
#include "CommCtrl.h"
23+
24+
HRESULT WINAPI TaskDialogCallbackProc(_In_ HWND hwnd, _In_ UINT msg, _In_ WPARAM wParam, _In_ LPARAM lParam, _In_ LONG_PTR lpRefData)
25+
{
26+
switch (msg) {
27+
case TDN_BUTTON_CLICKED:
28+
if (wParam == 100) {
29+
TASKDIALOGCONFIG* navigationConfig = new TASKDIALOGCONFIG;
30+
*navigationConfig = { 0 };
31+
navigationConfig->cbSize = sizeof(TASKDIALOGCONFIG);
32+
33+
navigationConfig->pszMainInstruction = L"After navigation !!!";
34+
navigationConfig->pszContent = L"Text";
35+
navigationConfig->pszMainIcon = MAKEINTRESOURCEW(0xFFF7);
36+
37+
// Navigate the dialog.
38+
SendMessageW(hwnd, TDM_NAVIGATE_PAGE, 0, (LPARAM)navigationConfig);
39+
delete navigationConfig;
40+
41+
// After that, run the event loop by show an inner dialog.
42+
TaskDialog(nullptr, nullptr, L"Inner Dialog", L"Inner Dialog", nullptr, 0, 0, nullptr);
43+
}
44+
45+
break;
46+
}
47+
48+
return S_OK;
49+
}
50+
51+
void ShowTaskDialogNavigationInButtonClickedNotification()
52+
{
53+
TASKDIALOGCONFIG* tConfig = new TASKDIALOGCONFIG;
54+
*tConfig = { 0 };
55+
tConfig->cbSize = sizeof(TASKDIALOGCONFIG);
56+
57+
tConfig->nDefaultRadioButton = 100;
58+
tConfig->pszMainInstruction = L"Before navigation";
59+
tConfig->pfCallback = TaskDialogCallbackProc;
60+
61+
// Create 50 radio buttons.
62+
int radioButtonCount = 50;
63+
TASKDIALOG_BUTTON* radioButtons = new TASKDIALOG_BUTTON[radioButtonCount];
64+
for (int i = 0; i < radioButtonCount; i++) {
65+
radioButtons[i].nButtonID = 100 + i;
66+
radioButtons[i].pszButtonText = L"My Radio Button";
67+
}
68+
tConfig->pRadioButtons = radioButtons;
69+
tConfig->cRadioButtons = radioButtonCount;
70+
71+
// Create a custom button.
72+
TASKDIALOG_BUTTON* customButton = new TASKDIALOG_BUTTON;
73+
customButton->nButtonID = 100;
74+
customButton->pszButtonText = L"My Custom Button";
75+
tConfig->pButtons = customButton;
76+
tConfig->cButtons = 1;
77+
78+
int nButton, nRadioButton;
79+
BOOL checkboxSelected;
80+
TaskDialogIndirect(tConfig, &nButton, &nRadioButton, &checkboxSelected);
81+
82+
delete customButton;
83+
delete radioButtons;
84+
delete tConfig;
85+
}
86+
87+
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
88+
_In_opt_ HINSTANCE hPrevInstance,
89+
_In_ LPWSTR lpCmdLine,
90+
_In_ int nCmdShow)
91+
{
92+
UNREFERENCED_PARAMETER(hPrevInstance);
93+
UNREFERENCED_PARAMETER(lpCmdLine);
94+
95+
ShowTaskDialogNavigationInButtonClickedNotification();
96+
97+
return 0;
98+
}
99+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Windows Task Dialog Issue
2+
3+
## Access Violation when receiving [`TDN_NAVIGATED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-navigated) within [`TDN_RADIO_BUTTON_CLICKED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-radio-button-clicked)
4+
5+
An Access Violation occurs when receiving a navigation notification
6+
([`TDN_NAVIGATED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-navigated))
7+
within a [`TDN_RADIO_BUTTON_CLICKED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-radio-button-clicked)
8+
notification (e.g. due to running the message loop).
9+
10+
* Run the code and select one of the radio buttons.
11+
* Notice the dialog has navigated and an inner dialog is opened.
12+
* Close the inner dialog.
13+
* **Issue:** Notice the application crashes with an access violation.
14+
15+
```cpp
16+
#include "stdafx.h"
17+
#include "CommCtrl.h"
18+
19+
HRESULT WINAPI TaskDialogCallbackProc(_In_ HWND hwnd, _In_ UINT msg, _In_ WPARAM wParam, _In_ LPARAM lParam, _In_ LONG_PTR lpRefData)
20+
{
21+
switch (msg) {
22+
case TDN_RADIO_BUTTON_CLICKED:
23+
// When the user clicked the second radio button, navigate the dialog, then show an inner dialog.
24+
25+
// Navigate
26+
TASKDIALOGCONFIG* navigationConfig = new TASKDIALOGCONFIG;
27+
*navigationConfig = { 0 };
28+
navigationConfig->cbSize = sizeof(TASKDIALOGCONFIG);
29+
30+
navigationConfig->pszMainInstruction = L"After navigation !!";
31+
32+
SendMessageW(hwnd, TDM_NAVIGATE_PAGE, 0, (LPARAM)navigationConfig);
33+
delete navigationConfig;
34+
35+
// After navigating, run the event loop by showing an inner dialog.
36+
TaskDialog(nullptr, nullptr, L"Inner Dialog", L"Inner Dialog", nullptr, 0, 0, nullptr);
37+
38+
break;
39+
}
40+
41+
return S_OK;
42+
}
43+
44+
void ShowTaskDialogRadioButtonsBehavior()
45+
{
46+
TASKDIALOGCONFIG* tConfig = new TASKDIALOGCONFIG;
47+
*tConfig = { 0 };
48+
tConfig->cbSize = sizeof(TASKDIALOGCONFIG);
49+
50+
tConfig->dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON;
51+
tConfig->pszMainInstruction = L"Before Navigation";
52+
tConfig->pfCallback = TaskDialogCallbackProc;
53+
54+
// Create 2 radio buttons.
55+
int radioButtonCount = 2;
56+
57+
TASKDIALOG_BUTTON* radioButtons = new TASKDIALOG_BUTTON[radioButtonCount];
58+
for (int i = 0; i < radioButtonCount; i++) {
59+
radioButtons[i].nButtonID = 100 + i;
60+
radioButtons[i].pszButtonText = i == 0 ? L"Radio Button 1" : L"Radio Button 2";
61+
}
62+
tConfig->pRadioButtons = radioButtons;
63+
tConfig->cRadioButtons = radioButtonCount;
64+
65+
int nButton, nRadioButton;
66+
BOOL checkboxSelected;
67+
TaskDialogIndirect(tConfig, &nButton, &nRadioButton, &checkboxSelected);
68+
69+
delete radioButtons;
70+
delete tConfig;
71+
}
72+
73+
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
74+
_In_opt_ HINSTANCE hPrevInstance,
75+
_In_ LPWSTR lpCmdLine,
76+
_In_ int nCmdShow)
77+
{
78+
UNREFERENCED_PARAMETER(hPrevInstance);
79+
UNREFERENCED_PARAMETER(lpCmdLine);
80+
81+
ShowTaskDialogRadioButtonsBehavior();
82+
83+
return 0;
84+
}
85+
```
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Windows Task Dialog Issue
2+
3+
## [`TDN_BUTTON_CLICKED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-button-clicked) notification sent twice when pressing a button with its access key
4+
5+
When you "click" a button by pressing its access key (mnemonic) and the dialog
6+
is still open when the message loop continues, the [`TDN_BUTTON_CLICKED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-button-clicked)
7+
notification is sent twice instead of once.
8+
9+
* Run the code.
10+
* Click one of the buttons with the mouse, or press space or enter, and notice
11+
that the counter increments by 1.
12+
* **Issue:** Press <kbd>Alt</kbd>+<kbd>M</kbd> or <kbd>Alt</kbd>+<kbd>N</kbd> and
13+
notice the counter increments by 2.
14+
15+
```cpp
16+
#include "stdafx.h"
17+
#include "CommCtrl.h"
18+
19+
int counter = 0;
20+
wchar_t* counterTextBuffer;
21+
HRESULT WINAPI ShowTaskDialogButtonClickHandlerCalledTwice_Callback(_In_ HWND hwnd, _In_ UINT msg, _In_ WPARAM wParam, _In_ LPARAM lParam, _In_ LONG_PTR lpRefData) {
22+
switch (msg) {
23+
case TDN_BUTTON_CLICKED:
24+
// Increment the counter.
25+
counter++;
26+
swprintf_s(counterTextBuffer, 100, L"Counter: %d", counter);
27+
SendMessageW(hwnd, TDM_SET_ELEMENT_TEXT, TDE_MAIN_INSTRUCTION, (LPARAM)counterTextBuffer);
28+
29+
// When the user clicks the custom button, return false so that the dialog
30+
// stays open.
31+
if (wParam == 100) {
32+
return S_FALSE;
33+
}
34+
else if (wParam == IDNO) {
35+
// Otherwise, when the user clicks the common button, run the message loop.
36+
MSG msg;
37+
int bRet;
38+
while ((bRet = GetMessageW(&msg, nullptr, 0, 0)) != 0) {
39+
if (bRet == -1) {
40+
// Error
41+
}
42+
else {
43+
TranslateMessage(&msg);
44+
DispatchMessageW(&msg);
45+
}
46+
}
47+
}
48+
49+
break;
50+
}
51+
52+
return S_OK;
53+
}
54+
55+
void ShowTaskDialogButtonClickHandlerCalledTwice() {
56+
counterTextBuffer = new wchar_t[100];
57+
58+
TASKDIALOGCONFIG* tConfig = new TASKDIALOGCONFIG;
59+
*tConfig = { 0 };
60+
tConfig->cbSize = sizeof(TASKDIALOGCONFIG);
61+
62+
tConfig->dwFlags = TDF_USE_COMMAND_LINKS;
63+
tConfig->pszMainInstruction = L"Text...";
64+
tConfig->pfCallback = ShowTaskDialogButtonClickHandlerCalledTwice_Callback;
65+
66+
// Create a custom button and a common ("No") button.
67+
tConfig->dwCommonButtons = TDCBF_NO_BUTTON;
68+
69+
TASKDIALOG_BUTTON customButton = { 0 };
70+
customButton.nButtonID = 100;
71+
customButton.pszButtonText = L"&My Button";
72+
tConfig->pButtons = &customButton;
73+
tConfig->cButtons = 1;
74+
75+
int nButton, nRadioButton;
76+
BOOL checkboxSelected;
77+
TaskDialogIndirect(tConfig, &nButton, &nRadioButton, &checkboxSelected);
78+
79+
delete tConfig;
80+
delete[] counterTextBuffer;
81+
}
82+
83+
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
84+
_In_opt_ HINSTANCE hPrevInstance,
85+
_In_ LPWSTR lpCmdLine,
86+
_In_ int nCmdShow)
87+
{
88+
UNREFERENCED_PARAMETER(hPrevInstance);
89+
UNREFERENCED_PARAMETER(lpCmdLine);
90+
91+
ShowTaskDialogButtonClickHandlerCalledTwice();
92+
93+
return 0;
94+
}
95+
```
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Windows Task Dialog Issue
2+
3+
## Infinite loop with radio buttons
4+
5+
An infinite loop occurs when sending a
6+
[`TDM_CLICK_RADIO_BUTTON`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdm-click-radio-button) message within a
7+
[`TDN_RADIO_BUTTON_CLICKED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-radio-button-clicked) notification.
8+
9+
* Run the code and select the second radio button.
10+
* **Issue:** Notice that the GUI doesn't respond any more. When debugging, you can see that
11+
the callback is flooded with `TDN_RADIO_BUTTON_CLICKED` notifications even though we
12+
don't send any more messages to the dialog.
13+
14+
```cpp
15+
#include "stdafx.h"
16+
#include "CommCtrl.h"
17+
18+
bool done = false;
19+
HRESULT WINAPI TaskDialogCallbackProc(_In_ HWND hwnd, _In_ UINT msg, _In_ WPARAM wParam, _In_ LPARAM lParam, _In_ LONG_PTR lpRefData)
20+
{
21+
switch (msg) {
22+
case TDN_RADIO_BUTTON_CLICKED:
23+
// When the user clicked the second radio button, select the first one.
24+
// However, do this only once.
25+
if (wParam == 101 && !done) {
26+
done = true;
27+
SendMessageW(hwnd, TDM_CLICK_RADIO_BUTTON, 100, 0);
28+
}
29+
break;
30+
}
31+
32+
return S_OK;
33+
}
34+
35+
void ShowTaskDialogRadioButtonsBehavior()
36+
{
37+
TASKDIALOGCONFIG* tConfig = new TASKDIALOGCONFIG;
38+
*tConfig = { 0 };
39+
tConfig->cbSize = sizeof(TASKDIALOGCONFIG);
40+
41+
tConfig->dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON;
42+
tConfig->pszMainInstruction = L"Radio Button Example";
43+
tConfig->pfCallback = TaskDialogCallbackProc;
44+
45+
// Create 2 radio buttons.
46+
int radioButtonCount = 2;
47+
48+
TASKDIALOG_BUTTON* radioButtons = new TASKDIALOG_BUTTON[radioButtonCount];
49+
for (int i = 0; i < radioButtonCount; i++) {
50+
radioButtons[i].nButtonID = 100 + i;
51+
radioButtons[i].pszButtonText = i == 0 ? L"Radio Button 1" : L"Radio Button 2";
52+
}
53+
tConfig->pRadioButtons = radioButtons;
54+
tConfig->cRadioButtons = radioButtonCount;
55+
56+
int nButton, nRadioButton;
57+
BOOL checkboxSelected;
58+
TaskDialogIndirect(tConfig, &nButton, &nRadioButton, &checkboxSelected);
59+
60+
delete radioButtons;
61+
delete tConfig;
62+
}
63+
64+
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
65+
_In_opt_ HINSTANCE hPrevInstance,
66+
_In_ LPWSTR lpCmdLine,
67+
_In_ int nCmdShow)
68+
{
69+
UNREFERENCED_PARAMETER(hPrevInstance);
70+
UNREFERENCED_PARAMETER(lpCmdLine);
71+
72+
ShowTaskDialogRadioButtonsBehavior();
73+
74+
return 0;
75+
}
76+
```

0 commit comments

Comments
 (0)