[.NET5 / Windows Forms] 新機能(!?) System.Windows.Forms.TaskDialog に触れてみた

# 前置き

今年リリースされた「.NET 5」。なんと Windows Forms に新しいコントロールが追加されていました。

https://docs.microsoft.com/ja-jp/dotnet/desktop/winforms/whats-new/?view=netdesktop-5.0

本記事ではそのうちの１つ「System.Windows.Forms.TaskDialog」をご紹介したいと思います。

# タスクダイアログ（TaskDialog）とは・・・

こちらの記事に詳しく書かれていますが、Vista 時点で Windows に搭載された機能のようです。
.NET Framework 時代でも Win32API を介せば使用できる機能だったようです。

https://www.infoq.com/jp/news/2021/03/WinForms-5/

Win32API での使用例はこちらのページで詳しく紹介されています。

http://grabacr.net/archives/105

.NET 5 で、フレームワークの標準機能として搭載されたということですね。

# 実例

TaskDialog のサンプルコードが公開されていたので、動作を確認してみました。

https://github.com/dotnet/samples/tree/main/windowsforms/TaskDialogDemo

※ 以下、掲載しているソースコードは上記のサンプルコードから引用しています

## 従来のMessageBox

従来のMessageBoxです。

![msgbox.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/130fe604-a1ce-6e6e-0417-b8d4674e2af0.png)

## TaskDialogでMessageBox

もちろん TaskDialog でも MessageBox と同様の表現が行えます。

![taskdialog.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/4bd26ecf-87a1-9c17-964e-54e97dc344c4.png)

タスクダイアログ上のコントロールは `TaskDialogPage` クラスのプロパティとして設定するようです。

```cs
            TaskDialogButton result = TaskDialog.ShowDialog(this, new TaskDialogPage()
            {
                Text = "Stopping the operation might leave your database in a corrupted state.",
                Heading = "Are you sure you want to stop?",
                Caption = "Confirmation (Task Dialog)",
                Buttons =
                {
                    TaskDialogButton.Yes,
                    TaskDialogButton.No
                },
                Icon = TaskDialogIcon.Warning,
                DefaultButton = TaskDialogButton.No
            });

            if (result == TaskDialogButton.Yes)
            {
                Console.WriteLine("User confirmed to stop the operation.");
            }
```

## 確認用CheckBox付きTaskDialog

「 CheckBox をONにすると、このメッセージを今後表示しない」という機能をもった、よく見かけるタイプのダイアログですね。

![taskveryify.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/b0e274e1-98c4-942e-fdab-43eded3ad277.png)

`TaskDialogVerificationCheckBox` をプロパティとして設定することで実装できます。
ダイアログ上でチェックがONになったかは`page.Verification.Checked`で取得できます。

```cs

            var page = new TaskDialogPage()
            {
                Heading = "Are you sure you want to stop?",
                Text = "Stopping the operation might leave your database in a corrupted state.",
                Caption = "Confirmation (Task Dialog)",
                Icon = TaskDialogIcon.Warning,
                AllowCancel = true,

                Verification = new TaskDialogVerificationCheckBox()
                {
                    Text = "Do not show again"
                },

                Buttons =
                {
                    TaskDialogButton.Yes,
                    TaskDialogButton.No
                },

                DefaultButton = TaskDialogButton.No
            };

            var resultButton = TaskDialog.ShowDialog(this, page);

            if (resultButton == TaskDialogButton.Yes)
            {
                if (page.Verification.Checked)
                    Console.WriteLine("Do not show this confirmation again.");

                Console.WriteLine("User confirmed to stop the operation.");
            }
```

## ボタンが３つあるTaskDialog

これもよく見かけるタイプですね。
入力画面から遷移する際に「保存する（保存して遷移）／保存しない（保存しないで遷移）／キャンセル（画面にとどまる）」と選択肢を表示し、ユーザーに編集中の内容をどう扱うか確認させる、といった機能で見かけます。
これが MessageBox だとボタン名をカスタムできないので、表現できないんですよね。
TaskDialog であればわざわざデザイナーでダイアログを作成せずとも、表現できます。

![closedoc.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/ecccd068-0751-1be3-30cc-ef9a3e5ce4b6.png)

## 選択肢のあるTaskDialog

`TaskDialogCommandLinkButton` を配置することで、選択肢のあるダイアログを表現することもできます。
ボタンの`Tag`プロパティによって、どのボタンを選択したかを判定できます。

```cs

                Buttons =
                {
                    new TaskDialogCommandLinkButton("&Beginner", "10 mines, 9 x 9 tile grid")
                    {
                        Tag = 10
                    },
                    new TaskDialogCommandLinkButton("&Intermediate", "40 mines, 16 x 16 tile grid")
                    {
                        Tag = 40
                    },
                    new TaskDialogCommandLinkButton("&Advanced", "99 mines, 16 x 30 tile grid")
                    {
                        Tag = 99
                    }
                }
```
```cs

            TaskDialogButton result = TaskDialog.ShowDialog(this, page);

            if (result.Tag is int resultingMines)
                Console.WriteLine($"Playing with {resultingMines} mines...");
            else
                Console.WriteLine("User canceled.");
        }
```

## プログレスバーを設置したTaskDialog

`TaskDialogProgressBar`を用いて、TaskDialog 上に プログレスバーを設置することも可能です。
このコードは一定時間経過後に自動で TaskDailog を閉じる実装になっています。
`reconnectButton.PerformClick();`により、ボタンクリックをコードで発火させることで、ダイアログを閉じることができるようです。

![autoclose.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/bb1a82d1-20d6-d5f3-7bfb-4e35a1668ade.png)

```cs

            var page = new TaskDialogPage()
            {
                Heading = "Connection lost; reconnecting...",
                Text = string.Format(textFormat, (remainingTenthSeconds + 9) / 10),
                // Display the form's icon in the task dialog.
                // Note however that the task dialog will not scale the icon.
                Icon = new TaskDialogIcon(this.Icon),
                ProgressBar = new TaskDialogProgressBar()
                {
                    State = TaskDialogProgressBarState.Paused
                },
                Buttons =
                {
                    reconnectButton,
                    cancelButton
                }
            };

            // Create a WinForms timer that raises the Tick event every tenth second.
            using (var timer = new Timer()
            {
                Enabled = true,
                Interval = 100
            })
            {
                timer.Tick += (s, e) =>
                {
                    remainingTenthSeconds--;
                    if (remainingTenthSeconds > 0)
                    {
                        // Update the remaining time and progress bar.
                        page.Text = string.Format(textFormat, (remainingTenthSeconds + 9) / 10);
                        page.ProgressBar.Value = 100 - remainingTenthSeconds * 2;
                    }
                    else
                    {
                        // Stop the timer and click the "Reconnect" button - this will
                        // close the dialog.
                        timer.Enabled = false;
                        reconnectButton.PerformClick();
                    }
                };

                TaskDialogButton result = TaskDialog.ShowDialog(this, page);
                if (result == reconnectButton)
                    Console.WriteLine("Reconnecting.");
                else
                    Console.WriteLine("Not reconnecting.");
            }
```

## ページ遷移するTaskDialog

１つの TaskDialog 内で ページを切り替えることが可能です。
サンプルコードでは「処理の実行確認 → 処理実行中 → 処理完了」といったパターンの例が実装されていました。
`initialPage.Navigate(inProgressPage);`といった形で`TaskDialogPage`間で遷移させることができるようです。

```cs
            // When the user clicks "Yes", navigate to the second page.
            initialButtonYes.Click += (sender, e) =>
            {
                initialPage.Navigate(inProgressPage);
            };
```

### ページ1
![mulit1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/39d5da88-1bc6-abf3-90da-b1a9fc51b78b.png)

### ページ2
![multi2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/809145ec-6332-54cf-15d3-109ebca36757.png)

ページ2の「詳細の表示」をクリックした状態

![multi4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/8b316b93-2e6b-fbf6-975e-3a7316aeb826.png)

### ページ3
![multi3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/ea81a6fc-0ede-8c36-9509-7d655b3655eb.png)

## UAC（ユーザーアカウント制御）の確認画面を表示するTaskDialog

「Restart now」をクリックすると、Windows のUAC（ユーザーアカウント制御）の確認画面が表示され、
「はい」をクリックすると TaskDialog は閉じられコマンドプロンプトが起動されます。
「いいえ」をクリックすると TaskDialog は表示されたままの状態でいます。

![elevated.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/57f85517-bd5e-a4f7-b7e9-07f3f66ddff1.png)

```cs
            restartNowButton.Click += (s, e) =>
            {
                restartNowButton.AllowCloseDialog = true;
                restartNowButton.Enabled = false;

                // Try to start an elevated cmd.exe.
                var psi = new ProcessStartInfo("cmd.exe", "/k echo Hi, this is an elevated command prompt.")
                {
                    UseShellExecute = true,
                    Verb = "runas"
                };

                try
                {
                    Process.Start(psi)?.Dispose();
                }
                catch (Win32Exception ex) when (ex.NativeErrorCode == 1223)
                {
                    // The user canceled the UAC prompt, so don't close the dialog and
                    // re-enable the restart button.
                    restartNowButton.AllowCloseDialog = false;
                    restartNowButton.Enabled = true;
                    return;
                }
            };
```

## TaskDialog 上のコントロールのイベント補足

TaskDialog 上に RadioButton ボタンを配置することも可能です。
RadioButton ボタンの`CheckChanged`イベントをはじめ、配置したコントロールのイベントを補足することが可能です。

![event.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/e6be1d5a-4edb-4a2e-7ddc-0b35011a0fca.png)

![event2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/109eb020-54d7-1f50-aeeb-2678f006063b.png)

![event3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/4c320bc7-edae-0201-a0d1-aea117445372.png)

```cs

            buttonNavigate.Click += (s, e) =>
            {
                Console.WriteLine($"Button '{s}' Click");

                // Navigate to a new page.
                var page2 = new TaskDialogPage()
                {
                    Heading = "AfterNavigation.",
                    Buttons =
                    {
                        TaskDialogButton.Close
                    }
                };
                page2.Created += (s, e) => Console.WriteLine("Page2 Created");
                page2.Destroyed += (s, e) => Console.WriteLine("Page2 Destroyed");

                page1.Navigate(page2);
            };

            page1.Verification = new TaskDialogVerificationCheckBox("&CheckBox");
            page1.Verification.CheckedChanged += (s, e) => Console.WriteLine("CheckBox CheckedChanged: " + page1.Verification.Checked);

            var radioButton1 = page1.RadioButtons.Add("Radi&oButton 1");
            var radioButton2 = page1.RadioButtons.Add("RadioB&utton 2");

            radioButton1.CheckedChanged += (s, e) => Console.WriteLine("RadioButton1 CheckedChanged: " + radioButton1.Checked);
            radioButton2.CheckedChanged += (s, e) => Console.WriteLine("RadioButton2 CheckedChanged: " + radioButton2.Checked);
```

# あとがき

以上、「System.Windows.Forms.TaskDialog に触れてみた」でした。
TaskDialog を使用すれば、よくある機能のパターンを簡単に実装できそうですね。
数年前、Windowsフォームの案件に従事していた時代だったら、ぜひ使ってみたかったなと思います。

しかし、これから .NET5 + Windowsフォームの案件は果たして出てるくのでしょうか・・・
私感ですが、Windowsフォーム自体の案件も少なくなってきているのかな・・・とは思います・・・