Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the Windows Task Dialog #1133

Merged
merged 169 commits into from
Apr 18, 2020
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
169 commits
Select commit Hold shift + click to select a range
ce917c5
Implement the Windows Task Dialog.
kpreisser Jun 17, 2019
08135a9
Don't specify SetLastError on TaskDialogIndirect() because we don't r…
kpreisser Jun 17, 2019
f2abf50
Follow-Up: Fix syntax error - sorry about that.
kpreisser Jun 17, 2019
b850098
Instead of using a try-catch block in WndProc() in the task dialog su…
kpreisser Jun 19, 2019
36813a5
PR feedback (code style)
kpreisser Jun 19, 2019
95dba75
PR review: Fix typo.
kpreisser Jun 19, 2019
606c421
PR feedback: Invert ifs/reduce nesting.
kpreisser Jun 19, 2019
e1e31c7
PR review: Apply suggestions
kpreisser Jun 19, 2019
591b6e8
Use discards for unused out parameters.
kpreisser Jun 19, 2019
b5779dc
PR feedback: Migrate gists to documentation.
kpreisser Jun 20, 2019
f3f49bb
PR feedback
kpreisser Jun 20, 2019
0bd8057
PR feedback: Rename event "Help" to "HelpRequest".
kpreisser Jun 20, 2019
a92ae5a
PR feedback: Use Linq for checks (and remove outdated comment).
kpreisser Jun 20, 2019
e7eff58
PR feedback: Use a temporary variable in ApplyInitialization() for co…
kpreisser Jun 20, 2019
7dbc875
Activate a theming scope when showing the task dialog.
kpreisser Jun 22, 2019
c974b6d
Update documentation.
kpreisser Jun 22, 2019
00e01ca
Improve documentation.
kpreisser Jun 23, 2019
a80f268
Verify that the enum value is valid.
kpreisser Jun 23, 2019
3056001
Update comment because the internal Marshal.SetLastWin32Error() doesn…
kpreisser Jun 23, 2019
e211c19
Add additional XML documentation.
kpreisser Jun 29, 2019
b999a74
Follow-Up: Fix typo.
kpreisser Jun 29, 2019
fd19292
Extract exception messages into resources.
kpreisser Jun 30, 2019
7c7bb22
Fix documentation.
kpreisser Jul 1, 2019
07b4aeb
Follow-Up: Improve exception message when visual styles (common contr…
kpreisser Jul 1, 2019
e44b4a5
Follow-Up: Use a single placeholder instead of two as it is a single …
kpreisser Jul 2, 2019
90f96b4
PR feedback: Invert property "DoNotSetForeground" to "SetToForeground".
kpreisser Jul 2, 2019
11b907c
Add more XML documentation.
kpreisser Jul 3, 2019
f317d67
Align the callback exception handling logic with the NativeWindow imp…
kpreisser Jul 6, 2019
23f5926
Minor documentation improvement.
kpreisser Jul 7, 2019
e5b2d22
Fix a NRE that can occur when explicitely setting the CheckBox, Expan…
kpreisser Jul 8, 2019
e8007d0
Simplify LINQ expressions.
kpreisser Jul 8, 2019
b0e2841
Minor API and doc tweaks
RussKie Jul 9, 2019
c028179
Call UpdateWindowSize() after updating an icon element, to ensure the…
kpreisser Jul 9, 2019
8eb5c5f
Merge 'master'.
kpreisser Jul 23, 2019
66234e5
Simplify.
kpreisser Jul 26, 2019
4109311
Enable nullable reference types for the TaskDialog classes.
kpreisser Jul 26, 2019
73655a9
Use a switch expression.
kpreisser Jul 26, 2019
48592bf
Follow-Up: Make the return value of the ToString() methods non-nullab…
kpreisser Jul 26, 2019
06e2fc2
Follow-Up: Also make the parameter of the implicit cast operator from…
kpreisser Jul 27, 2019
e1054fa
Improve comments.
kpreisser Jul 27, 2019
b364464
Avoid the CS8602 warning by adding a property "BoundTaskDialog" to th…
kpreisser Jul 28, 2019
b8aecb1
Fix Debug.Assert() call.
kpreisser Jul 30, 2019
1aff293
Shorten the exception message about visual styles not being enabled, …
kpreisser Aug 4, 2019
16fa2ac
Make events nullable.
kpreisser Aug 9, 2019
556c744
Rename method Show() to ShowDialog() to match existing terms.
kpreisser Aug 19, 2019
fac116b
Rename instruction to mainInstruction.
kpreisser Aug 19, 2019
f577d74
Rename title to caption.
kpreisser Aug 19, 2019
23adb26
Remove the TaskDialogButtonClickedEventArgs and instead add a new pro…
kpreisser Aug 22, 2019
0a33163
Refactor to remove the TaskDialogStandardIcon enum and the TaskDialog…
kpreisser Aug 22, 2019
449e3a8
Merge 'master' and resolve conflicts.
kpreisser Aug 25, 2019
4531d73
Follow-Up: Adjust for recent changes.
kpreisser Aug 25, 2019
e25b2f7
Merge remote-tracking branch 'dotnet/master' into taskdialog
kpreisser Sep 26, 2019
5b41752
Follow-Up: Adjust for recent Interop changes.
kpreisser Sep 26, 2019
b2115f6
Improve XML documentation.
kpreisser Sep 30, 2019
cc085e6
PR feedback: Refactor the task dialog interop code to align with exis…
kpreisser Oct 1, 2019
21e6dea
PR feedback: Use expression-bodied members where possible.
kpreisser Oct 1, 2019
5f7d8d3
PR feedback: Improve XML documentation and code formatting.
kpreisser Oct 4, 2019
58c143b
Merge remote-tracking branch 'dotnet/master' into taskdialog
kpreisser Oct 4, 2019
e4efa8e
Fix formatting.
kpreisser Oct 4, 2019
a7304d9
We don't own the window handle, so there is no need to use a HandleRe…
kpreisser Oct 4, 2019
16a4b56
Follow-Up: SetWindowTextW is not declared with SetLastError=True, so …
kpreisser Oct 5, 2019
302d4f5
Remove the call to SetLastError() as that isn't safe anyway.
kpreisser Oct 8, 2019
b02aca1
Apply suggestions from code review
kpreisser Oct 8, 2019
89c9fb3
PR feedback: Improve XML documentation.
kpreisser Oct 8, 2019
480ad28
Apply suggestions from code review
kpreisser Oct 9, 2019
4b6899f
Apply suggestions from code review (follow-up)
kpreisser Oct 9, 2019
d01a052
Apply further suggestions from code review.
kpreisser Oct 9, 2019
7ef21bd
PR review: Improve interop signatures.
kpreisser Oct 9, 2019
8b23f15
Merge remote-tracking branch 'dotnet/master' into taskdialog
kpreisser Oct 28, 2019
c7357e4
Adjust return type as #1981 has been fixed.
kpreisser Oct 28, 2019
52b4bd4
Follow-Up: Fix empty if statement.
kpreisser Oct 29, 2019
058adf5
When updating the dialog caption with an empty or null string, use th…
kpreisser Nov 3, 2019
54d5fbb
Simplify.
kpreisser Nov 17, 2019
59f4ac8
Fix typo.
kpreisser Nov 17, 2019
5d3014d
Merge remote-tracking branch 'dotnet/master' into taskdialog
kpreisser Nov 17, 2019
80161ca
Add an indexer to TaskDialogStandardButtonCollection which hides the …
kpreisser Nov 24, 2019
7f89ec2
Merge remote-tracking branch 'dotnet/master' into taskdialog
kpreisser Nov 24, 2019
de7f6d9
Adapt to changes in class ThemingScope.
kpreisser Nov 24, 2019
57b7a16
Use TASKDIALOGCONFIG* rather than IntPtr as parameter type for TaskDi…
kpreisser Dec 8, 2019
4d6db76
Rename `CanBeMinimized` -> `AllowMinimize`
RussKie Dec 10, 2019
52f831d
Rename `ShouldCloseDialog` -> `AllowCloseDialog`
RussKie Dec 10, 2019
0d419fa
Streamlime footer control (#1)
RussKie Dec 10, 2019
5b5c973
Fix typo and indentation.
kpreisser Dec 10, 2019
4985159
Add overloads for `defaultButton`, `enabled`, `allowCloseDialog`
RussKie Dec 10, 2019
fedf26a
Improve comment.
kpreisser Dec 17, 2019
d5dac3d
Improve documentation (the setting applies to all custom buttons, not…
kpreisser Dec 17, 2019
bb0b966
Switch boolean property TaskDialogExpander.ExpandFooterArea into an e…
kpreisser Dec 17, 2019
cc5f2e2
Explicitely specify values of the enum members.
kpreisser Dec 17, 2019
7262879
Improve documentation.
kpreisser Dec 17, 2019
065a5c2
Merge remote-tracking branch 'dotnet/master' into taskdialog
kpreisser Dec 17, 2019
c5d72cb
Avoid marshalling/copying a string when updating a task dialog text e…
kpreisser Dec 25, 2019
9ecf2ca
Add public properties TaskDialogPage.BoundDialog and TaskDialogContro…
kpreisser Dec 30, 2019
8c8456b
Improve documentation.
kpreisser Dec 30, 2019
d3d4cb7
Rename standard icons to not include the term "security" for shield i…
kpreisser Dec 30, 2019
9a0a9e0
Merge branch 'taskdialog' into Tweak_API
kpreisser Dec 30, 2019
5bf0c1a
Simplify EventHandler<EventArgs> to EventHandler.
kpreisser Dec 30, 2019
20ddf88
Don't apply the initialization in the TDN_NAVIGATED notification if t…
kpreisser Jan 1, 2020
949835d
Remove the additional check (which is also done by SendTaskDialogMess…
kpreisser Jan 2, 2020
1db3804
Buffer changes to TaskDialogPage.MainInstruction, TaskDialogPage.Text…
kpreisser Jan 4, 2020
c4bb63c
Refactor.
kpreisser Jan 4, 2020
e781130
Always set the progress bar position a second time to work reliably w…
kpreisser Jan 4, 2020
c40d770
Simplify.
kpreisser Jan 4, 2020
7c686c1
Don't allow to navigate the dialog if we already received the TDN_DES…
kpreisser Jan 5, 2020
2b55b28
Buffer changes to the TaskDialogProgressBar properties that are done …
kpreisser Jan 5, 2020
6f625b6
Merge remote-tracking branch 'dotnet/master' into taskdialog
kpreisser Jan 5, 2020
46ed83f
Update for changes in #2518.
kpreisser Jan 5, 2020
d45605d
Use a separate static method rather than a lambda expression for the …
kpreisser Jan 7, 2020
bd9063d
Follow-Up: Also use a separate method rather than a lambda expression…
kpreisser Jan 7, 2020
535e47f
Simplify, as there is no need to store the function pointer (Marshal.…
kpreisser Jan 7, 2020
5adb97b
Fix trailing whitespace.
kpreisser Jan 7, 2020
6b28789
Follow-Up: Fix trailing whitespace.
kpreisser Jan 7, 2020
c293f6f
Use actual pointer types in structs to align with other interop decla…
kpreisser Jan 21, 2020
74e5804
Use an assert to avoid one instance of the null-forgiving operator.
kpreisser Jan 21, 2020
9c45dd1
Remove outdated comment.
kpreisser Jan 21, 2020
0799e08
PR feedback: Validate enum arguments.
kpreisser Jan 21, 2020
8893f15
Merge remote-tracking branch 'dotnet/master' into taskdialog
kpreisser Jan 21, 2020
512e891
Update for changes in #2744.
kpreisser Jan 21, 2020
0ca364e
Fix XML documentation warnings.
kpreisser Jan 21, 2020
b01b98c
Update public API surface.
kpreisser Jan 21, 2020
79a2803
Suppress RS0026 for static TaskDialog.ShowDialog() overloads.
kpreisser Jan 21, 2020
b28955e
Merge remote-tracking branch 'dotnet/master' into taskdialog
kpreisser Jan 23, 2020
22aecc6
Update for changes in #2702 (NRT for System.Windows.Forms).
kpreisser Jan 23, 2020
ff6d07a
PR feedback: Use a separate struct for the icon unions.
kpreisser Jan 26, 2020
82e178c
Use Interop.PARAM where applicable, and simplify.
kpreisser Jan 26, 2020
187bdb4
Remove hyperlink functionality for the time being.
kpreisser Feb 9, 2020
22cec90
Follow-Up: Update public API surface.
kpreisser Feb 9, 2020
75af3ae
Fix typo.
kpreisser Mar 9, 2020
b3ebec7
Rework task dialog button API (#12)
kpreisser Mar 9, 2020
d665616
Correctly set the default button ID for standard buttons when the use…
kpreisser Mar 9, 2020
e5ef037
Don't throw an exception in the middle of Page.Validate().
kpreisser Mar 9, 2020
72a8285
PR review: Change flags enums to uint.
kpreisser Mar 9, 2020
02de400
Streamline single/multipage API (kpreisser/winforms#14)
RussKie Mar 23, 2020
55c17d4
Fix NRT warning.
kpreisser Mar 23, 2020
febb140
Remove code which is no longer necessary.
kpreisser Mar 23, 2020
22b1bfa
Fix incorrect check for IntPtr.Zero.
kpreisser Mar 23, 2020
61de9f4
Merge remote-tracking branch 'dotnet/master' into taskdialog
kpreisser Mar 23, 2020
5453a65
Follow-Up: Remove the check for IntPtr.Zero to allow to show a modele…
kpreisser Mar 23, 2020
dd3b7de
Set dialog properties via `ShowDialog` (kpreisser/winforms#16)
RussKie Apr 2, 2020
73b29b4
Clean up (kpreisser/winforms#17)
RussKie Apr 2, 2020
bf609cc
Remove dialog box command ID constants in favor of the existing ID enum.
kpreisser Apr 2, 2020
1adb46d
PR review: Inline properties.
kpreisser Apr 2, 2020
a5a9a04
Clean up.
kpreisser Apr 2, 2020
486da46
Merge remote-tracking branch 'dotnet/master' into taskdialog
kpreisser Apr 2, 2020
bed1611
Follow-Up: Fix merge issue.
kpreisser Apr 2, 2020
b3abdc6
Remove unnecessary 'unsafe' modifier.
kpreisser Apr 4, 2020
faf868d
Improve wording.
kpreisser Apr 4, 2020
245651f
Simplify setting dialog icon from bitmaps (kpreisser/winforms#15)
RussKie Apr 8, 2020
c2c5a9d
Merge branch 'taskdialog' of https://github.com/kpreisser/winforms in…
kpreisser Apr 8, 2020
f00f2ae
Rename property TaskDialogPage.MainInstruction to Heading.
kpreisser Apr 8, 2020
977a0bd
Rename term 'Footer' to 'Footnote'.
kpreisser Apr 8, 2020
4d12a2d
Changes to public API as proposed in dotnet/winforms#1133:
kpreisser Apr 12, 2020
9cdd007
Don't store the function pointer as can be retrieved with Marshal.Get…
kpreisser Apr 12, 2020
03f3372
Remove unnecessary cast.
kpreisser Apr 12, 2020
ac3eb3b
Follow-Up: Remove TaskDialogCheckBox.Focus() from public API.
kpreisser Apr 12, 2020
9b98691
Streamlime verification control (kpreisser/winforms#18)
RussKie Apr 13, 2020
9186dd3
PR feedback: Add tests verifying size and layout of interop structs.
kpreisser Apr 13, 2020
d8f8fc2
Merge branch 'taskdialog' of https://github.com/kpreisser/winforms in…
kpreisser Apr 13, 2020
8b3a981
PR review: Add exception documentation.
kpreisser Apr 13, 2020
4891a21
Remove property TaskDialogPage.Width as proposed in dotnet/winforms#1…
kpreisser Apr 16, 2020
0e1bd00
PR review:
kpreisser Apr 16, 2020
833687b
Rename operator parameter confirmationText to verificationText to mat…
kpreisser Apr 16, 2020
2615aee
Merge remote-tracking branch 'dotnet/master' into taskdialog
kpreisser Apr 16, 2020
f7063e3
Add remarks about updateability of the dialog directly after starting…
kpreisser Apr 17, 2020
ad3428e
Remove outdated comment which referred to the TaskDialog.Closing even…
kpreisser Apr 17, 2020
859d0d9
Remove unnecessary field.
kpreisser Apr 17, 2020
96f9149
PR feedback: Move static local functions to the bottom of the contain…
kpreisser Apr 17, 2020
6159bc4
Revert the change of not storing the function pointer in WindowSubcla…
kpreisser Apr 17, 2020
b6012e3
PR feedback: Move exception messages to resources.
kpreisser Apr 17, 2020
f586a5f
Remove unused parameter and improve code and comments.
kpreisser Apr 18, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
```
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;
}
```
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;
}
```
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;
}
```
Loading