-
Notifications
You must be signed in to change notification settings - Fork 999
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement the Windows Task Dialog (#1133)
Fixes #146
- Loading branch information
Showing
59 changed files
with
9,089 additions
and
4 deletions.
There are no files selected for viewing
99 changes: 99 additions & 0 deletions
99
...tem/Windows/Forms/TaskDialog/Issue_AccessViolation_NavigationInButtonClicked.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
# Windows Task Dialog Issue | ||
|
||
## 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) | ||
|
||
An Access violation can occur when receiving a navigation notification ([`TDN_NAVIGATED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-navigated)) | ||
within a [`TDN_BUTTON_CLICKED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-button-clicked) | ||
notification (e.g. due to running the message loop) and then returning `S_OK`. | ||
|
||
* Run the code. | ||
* Click on the "My Custom Button" button. | ||
* Notice that the dialog navigates and an inner dialog shows. | ||
* Close the inner dialog by clicking the OK button. | ||
* **Issue:** In most cases, an access violation occurs now (if not, try again a | ||
few times). | ||
* Notice the problem also occurs when you (after the navigation) first close | ||
the original dialog, and then close the inner dialog. | ||
* The problem can be avoided by returning `S_FALSE` from the `TDN_BUTTON_CLICKED` | ||
notification. | ||
|
||
```cpp | ||
#include "stdafx.h" | ||
#include "CommCtrl.h" | ||
|
||
HRESULT WINAPI TaskDialogCallbackProc(_In_ HWND hwnd, _In_ UINT msg, _In_ WPARAM wParam, _In_ LPARAM lParam, _In_ LONG_PTR lpRefData) | ||
{ | ||
switch (msg) { | ||
case TDN_BUTTON_CLICKED: | ||
if (wParam == 100) { | ||
TASKDIALOGCONFIG* navigationConfig = new TASKDIALOGCONFIG; | ||
*navigationConfig = { 0 }; | ||
navigationConfig->cbSize = sizeof(TASKDIALOGCONFIG); | ||
|
||
navigationConfig->pszMainInstruction = L"After navigation !!!"; | ||
navigationConfig->pszContent = L"Text"; | ||
navigationConfig->pszMainIcon = MAKEINTRESOURCEW(0xFFF7); | ||
|
||
// Navigate the dialog. | ||
SendMessageW(hwnd, TDM_NAVIGATE_PAGE, 0, (LPARAM)navigationConfig); | ||
delete navigationConfig; | ||
|
||
// After that, run the event loop by show an inner dialog. | ||
TaskDialog(nullptr, nullptr, L"Inner Dialog", L"Inner Dialog", nullptr, 0, 0, nullptr); | ||
} | ||
|
||
break; | ||
} | ||
|
||
return S_OK; | ||
} | ||
|
||
void ShowTaskDialogNavigationInButtonClickedNotification() | ||
{ | ||
TASKDIALOGCONFIG* tConfig = new TASKDIALOGCONFIG; | ||
*tConfig = { 0 }; | ||
tConfig->cbSize = sizeof(TASKDIALOGCONFIG); | ||
|
||
tConfig->nDefaultRadioButton = 100; | ||
tConfig->pszMainInstruction = L"Before navigation"; | ||
tConfig->pfCallback = TaskDialogCallbackProc; | ||
|
||
// Create 50 radio buttons. | ||
int radioButtonCount = 50; | ||
TASKDIALOG_BUTTON* radioButtons = new TASKDIALOG_BUTTON[radioButtonCount]; | ||
for (int i = 0; i < radioButtonCount; i++) { | ||
radioButtons[i].nButtonID = 100 + i; | ||
radioButtons[i].pszButtonText = L"My Radio Button"; | ||
} | ||
tConfig->pRadioButtons = radioButtons; | ||
tConfig->cRadioButtons = radioButtonCount; | ||
|
||
// Create a custom button. | ||
TASKDIALOG_BUTTON* customButton = new TASKDIALOG_BUTTON; | ||
customButton->nButtonID = 100; | ||
customButton->pszButtonText = L"My Custom Button"; | ||
tConfig->pButtons = customButton; | ||
tConfig->cButtons = 1; | ||
|
||
int nButton, nRadioButton; | ||
BOOL checkboxSelected; | ||
TaskDialogIndirect(tConfig, &nButton, &nRadioButton, &checkboxSelected); | ||
|
||
delete customButton; | ||
delete radioButtons; | ||
delete tConfig; | ||
} | ||
|
||
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, | ||
_In_opt_ HINSTANCE hPrevInstance, | ||
_In_ LPWSTR lpCmdLine, | ||
_In_ int nCmdShow) | ||
{ | ||
UNREFERENCED_PARAMETER(hPrevInstance); | ||
UNREFERENCED_PARAMETER(lpCmdLine); | ||
|
||
ShowTaskDialogNavigationInButtonClickedNotification(); | ||
|
||
return 0; | ||
} | ||
``` |
85 changes: 85 additions & 0 deletions
85
...indows/Forms/TaskDialog/Issue_AccessViolation_NavigationInRadioButtonClicked.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# Windows Task Dialog Issue | ||
|
||
## 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) | ||
|
||
An Access Violation occurs when receiving a navigation notification | ||
([`TDN_NAVIGATED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-navigated)) | ||
within a [`TDN_RADIO_BUTTON_CLICKED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-radio-button-clicked) | ||
notification (e.g. due to running the message loop). | ||
|
||
* Run the code and select one of the radio buttons. | ||
* Notice the dialog has navigated and an inner dialog is opened. | ||
* Close the inner dialog. | ||
* **Issue:** Notice the application crashes with an access violation. | ||
|
||
```cpp | ||
#include "stdafx.h" | ||
#include "CommCtrl.h" | ||
|
||
HRESULT WINAPI TaskDialogCallbackProc(_In_ HWND hwnd, _In_ UINT msg, _In_ WPARAM wParam, _In_ LPARAM lParam, _In_ LONG_PTR lpRefData) | ||
{ | ||
switch (msg) { | ||
case TDN_RADIO_BUTTON_CLICKED: | ||
// When the user clicked the second radio button, navigate the dialog, then show an inner dialog. | ||
|
||
// Navigate | ||
TASKDIALOGCONFIG* navigationConfig = new TASKDIALOGCONFIG; | ||
*navigationConfig = { 0 }; | ||
navigationConfig->cbSize = sizeof(TASKDIALOGCONFIG); | ||
|
||
navigationConfig->pszMainInstruction = L"After navigation !!"; | ||
|
||
SendMessageW(hwnd, TDM_NAVIGATE_PAGE, 0, (LPARAM)navigationConfig); | ||
delete navigationConfig; | ||
|
||
// After navigating, run the event loop by showing an inner dialog. | ||
TaskDialog(nullptr, nullptr, L"Inner Dialog", L"Inner Dialog", nullptr, 0, 0, nullptr); | ||
|
||
break; | ||
} | ||
|
||
return S_OK; | ||
} | ||
|
||
void ShowTaskDialogRadioButtonsBehavior() | ||
{ | ||
TASKDIALOGCONFIG* tConfig = new TASKDIALOGCONFIG; | ||
*tConfig = { 0 }; | ||
tConfig->cbSize = sizeof(TASKDIALOGCONFIG); | ||
|
||
tConfig->dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON; | ||
tConfig->pszMainInstruction = L"Before Navigation"; | ||
tConfig->pfCallback = TaskDialogCallbackProc; | ||
|
||
// Create 2 radio buttons. | ||
int radioButtonCount = 2; | ||
|
||
TASKDIALOG_BUTTON* radioButtons = new TASKDIALOG_BUTTON[radioButtonCount]; | ||
for (int i = 0; i < radioButtonCount; i++) { | ||
radioButtons[i].nButtonID = 100 + i; | ||
radioButtons[i].pszButtonText = i == 0 ? L"Radio Button 1" : L"Radio Button 2"; | ||
} | ||
tConfig->pRadioButtons = radioButtons; | ||
tConfig->cRadioButtons = radioButtonCount; | ||
|
||
int nButton, nRadioButton; | ||
BOOL checkboxSelected; | ||
TaskDialogIndirect(tConfig, &nButton, &nRadioButton, &checkboxSelected); | ||
|
||
delete radioButtons; | ||
delete tConfig; | ||
} | ||
|
||
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, | ||
_In_opt_ HINSTANCE hPrevInstance, | ||
_In_ LPWSTR lpCmdLine, | ||
_In_ int nCmdShow) | ||
{ | ||
UNREFERENCED_PARAMETER(hPrevInstance); | ||
UNREFERENCED_PARAMETER(lpCmdLine); | ||
|
||
ShowTaskDialogRadioButtonsBehavior(); | ||
|
||
return 0; | ||
} | ||
``` |
95 changes: 95 additions & 0 deletions
95
...tion/src/System/Windows/Forms/TaskDialog/Issue_ButtonClickHandlerCalledTwice.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
# Windows Task Dialog Issue | ||
|
||
## [`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 | ||
|
||
When you "click" a button by pressing its access key (mnemonic) and the dialog | ||
is still open when the message loop continues, the [`TDN_BUTTON_CLICKED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-button-clicked) | ||
notification is sent twice instead of once. | ||
|
||
* Run the code. | ||
* Click one of the buttons with the mouse, or press space or enter, and notice | ||
that the counter increments by 1. | ||
* **Issue:** Press <kbd>Alt</kbd>+<kbd>M</kbd> or <kbd>Alt</kbd>+<kbd>N</kbd> and | ||
notice the counter increments by 2. | ||
|
||
```cpp | ||
#include "stdafx.h" | ||
#include "CommCtrl.h" | ||
|
||
int counter = 0; | ||
wchar_t* counterTextBuffer; | ||
HRESULT WINAPI ShowTaskDialogButtonClickHandlerCalledTwice_Callback(_In_ HWND hwnd, _In_ UINT msg, _In_ WPARAM wParam, _In_ LPARAM lParam, _In_ LONG_PTR lpRefData) { | ||
switch (msg) { | ||
case TDN_BUTTON_CLICKED: | ||
// Increment the counter. | ||
counter++; | ||
swprintf_s(counterTextBuffer, 100, L"Counter: %d", counter); | ||
SendMessageW(hwnd, TDM_SET_ELEMENT_TEXT, TDE_MAIN_INSTRUCTION, (LPARAM)counterTextBuffer); | ||
|
||
// When the user clicks the custom button, return false so that the dialog | ||
// stays open. | ||
if (wParam == 100) { | ||
return S_FALSE; | ||
} | ||
else if (wParam == IDNO) { | ||
// Otherwise, when the user clicks the common button, run the message loop. | ||
MSG msg; | ||
int bRet; | ||
while ((bRet = GetMessageW(&msg, nullptr, 0, 0)) != 0) { | ||
if (bRet == -1) { | ||
// Error | ||
} | ||
else { | ||
TranslateMessage(&msg); | ||
DispatchMessageW(&msg); | ||
} | ||
} | ||
} | ||
|
||
break; | ||
} | ||
|
||
return S_OK; | ||
} | ||
|
||
void ShowTaskDialogButtonClickHandlerCalledTwice() { | ||
counterTextBuffer = new wchar_t[100]; | ||
|
||
TASKDIALOGCONFIG* tConfig = new TASKDIALOGCONFIG; | ||
*tConfig = { 0 }; | ||
tConfig->cbSize = sizeof(TASKDIALOGCONFIG); | ||
|
||
tConfig->dwFlags = TDF_USE_COMMAND_LINKS; | ||
tConfig->pszMainInstruction = L"Text..."; | ||
tConfig->pfCallback = ShowTaskDialogButtonClickHandlerCalledTwice_Callback; | ||
|
||
// Create a custom button and a common ("No") button. | ||
tConfig->dwCommonButtons = TDCBF_NO_BUTTON; | ||
|
||
TASKDIALOG_BUTTON customButton = { 0 }; | ||
customButton.nButtonID = 100; | ||
customButton.pszButtonText = L"&My Button"; | ||
tConfig->pButtons = &customButton; | ||
tConfig->cButtons = 1; | ||
|
||
int nButton, nRadioButton; | ||
BOOL checkboxSelected; | ||
TaskDialogIndirect(tConfig, &nButton, &nRadioButton, &checkboxSelected); | ||
|
||
delete tConfig; | ||
delete[] counterTextBuffer; | ||
} | ||
|
||
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, | ||
_In_opt_ HINSTANCE hPrevInstance, | ||
_In_ LPWSTR lpCmdLine, | ||
_In_ int nCmdShow) | ||
{ | ||
UNREFERENCED_PARAMETER(hPrevInstance); | ||
UNREFERENCED_PARAMETER(lpCmdLine); | ||
|
||
ShowTaskDialogButtonClickHandlerCalledTwice(); | ||
|
||
return 0; | ||
} | ||
``` |
76 changes: 76 additions & 0 deletions
76
...mentation/src/System/Windows/Forms/TaskDialog/Issue_RadioButton_InfiniteLoop.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
# Windows Task Dialog Issue | ||
|
||
## Infinite loop with radio buttons | ||
|
||
An infinite loop occurs when sending a | ||
[`TDM_CLICK_RADIO_BUTTON`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdm-click-radio-button) message within a | ||
[`TDN_RADIO_BUTTON_CLICKED`](https://docs.microsoft.com/en-us/windows/desktop/Controls/tdn-radio-button-clicked) notification. | ||
|
||
* Run the code and select the second radio button. | ||
* **Issue:** Notice that the GUI doesn't respond any more. When debugging, you can see that | ||
the callback is flooded with `TDN_RADIO_BUTTON_CLICKED` notifications even though we | ||
don't send any more messages to the dialog. | ||
|
||
```cpp | ||
#include "stdafx.h" | ||
#include "CommCtrl.h" | ||
|
||
bool done = false; | ||
HRESULT WINAPI TaskDialogCallbackProc(_In_ HWND hwnd, _In_ UINT msg, _In_ WPARAM wParam, _In_ LPARAM lParam, _In_ LONG_PTR lpRefData) | ||
{ | ||
switch (msg) { | ||
case TDN_RADIO_BUTTON_CLICKED: | ||
// When the user clicked the second radio button, select the first one. | ||
// However, do this only once. | ||
if (wParam == 101 && !done) { | ||
done = true; | ||
SendMessageW(hwnd, TDM_CLICK_RADIO_BUTTON, 100, 0); | ||
} | ||
break; | ||
} | ||
|
||
return S_OK; | ||
} | ||
|
||
void ShowTaskDialogRadioButtonsBehavior() | ||
{ | ||
TASKDIALOGCONFIG* tConfig = new TASKDIALOGCONFIG; | ||
*tConfig = { 0 }; | ||
tConfig->cbSize = sizeof(TASKDIALOGCONFIG); | ||
|
||
tConfig->dwFlags = TDF_NO_DEFAULT_RADIO_BUTTON; | ||
tConfig->pszMainInstruction = L"Radio Button Example"; | ||
tConfig->pfCallback = TaskDialogCallbackProc; | ||
|
||
// Create 2 radio buttons. | ||
int radioButtonCount = 2; | ||
|
||
TASKDIALOG_BUTTON* radioButtons = new TASKDIALOG_BUTTON[radioButtonCount]; | ||
for (int i = 0; i < radioButtonCount; i++) { | ||
radioButtons[i].nButtonID = 100 + i; | ||
radioButtons[i].pszButtonText = i == 0 ? L"Radio Button 1" : L"Radio Button 2"; | ||
} | ||
tConfig->pRadioButtons = radioButtons; | ||
tConfig->cRadioButtons = radioButtonCount; | ||
|
||
int nButton, nRadioButton; | ||
BOOL checkboxSelected; | ||
TaskDialogIndirect(tConfig, &nButton, &nRadioButton, &checkboxSelected); | ||
|
||
delete radioButtons; | ||
delete tConfig; | ||
} | ||
|
||
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, | ||
_In_opt_ HINSTANCE hPrevInstance, | ||
_In_ LPWSTR lpCmdLine, | ||
_In_ int nCmdShow) | ||
{ | ||
UNREFERENCED_PARAMETER(hPrevInstance); | ||
UNREFERENCED_PARAMETER(lpCmdLine); | ||
|
||
ShowTaskDialogRadioButtonsBehavior(); | ||
|
||
return 0; | ||
} | ||
``` |
Oops, something went wrong.