Windows GUIプログラミング入門27 ヘルプ画面

# ■はじめに

今回は画面内にヘルプページを表示するサンプルと、画面パーツを別xamlにするやり方を学びます。
これらの実装例として、正規表現の動作確認ツールを作ります。
![000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/3ae08759-408e-66f5-32b5-7a96d0a08f5b.png)

# ■開発環境

* Windows 10
* Visual Studio Community 2019 （Version 16.8.2）
* .NET Framework 4.6.2 / .NET Core 3.1 / .NET 5.0

# ■プロジェクト作成

`RegexTester`という名前でプロジェクトを作成します。

## ◇.NET Frameworkの場合

![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/89055a4c-42bc-5eda-267f-260951c9e5fb.png)

![002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/c12093cb-74f9-12c3-f0e9-a870404083ea.png)

## ◇.NET Core 3.1 / .NET 5.0の場合

![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/87a2a79f-811b-8234-ca58-d5698ada5cb5.png)

![004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/bc47a57f-86af-cc96-3f45-bc17ddd557af.png)

# ■プロジェクトの設定

## ◇.NET Core 3.1の場合

プロジェクトを右クリック、「プロパティ」で「対象のフレームワーク」を`.NET Core 3.1`に設定します。
![005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/d2fb90a1-2792-a100-041d-a290267f62b9.png)

## ◇.NET 5.0の場合

プロジェクトを右クリック、「プロパティ」で「対象のフレームワーク」を`.NET 5.0`に設定します。
![006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/16419dfe-75ee-e91c-9855-d0b5bb559041.png)

プロジェクトをダブルクリックし、
csprojファイルのSdkが`Microsoft.NET.Sdk.WindowsDesktop`になっていたら`Microsoft.NET.Sdk`に編集します。
![007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/87067674-92f8-245d-71ee-f7383cd2cce2.png)

# ■メイン画面の枠組み作成

Gridを分割し、上にタブ、下に閉じるボタンを配置します。
![010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/6f2dfef9-13cb-3daa-5a77-34642c7c925b.png)

```xml:MainWindow.xaml
<Window
    x:Class="RegexTester.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:RegexTester"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="Regex Tester .NET"
    Width="800"
    Height="450"
    ResizeMode="CanResizeWithGrip"
    mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <!--  タブ行  -->
            <RowDefinition />
            <!--  ボタン行  -->
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <TabControl Margin="6">
            <TabItem Header="マッチング">
            </TabItem>
            <TabItem Header="ヘルプ">
            </TabItem>
        </TabControl>

        <StackPanel Grid.Row="1">
            <Button
                Width="150"
                Margin="12,6"
                HorizontalAlignment="Right"
                Content="閉じる(_C)" />
        </StackPanel>

    </Grid>
</Window>
```

# ■画面パーツ用意

![015_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/5bab8820-ee86-9322-7e7b-a0baa72e8474.png)
プロジェクトを右クリック、「追加」-「新しいフォルダー」で`Views`と入力してください。
「Views」フォルダー内に`Pages`フォルダーを作成してください。

![020_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/65c50982-7ead-9837-0624-a2772bf5fcd2.png)
「Pages」フォルダーを右クリック、「追加」-「ユーザーコントロール(WPF)」で`MatchingContent`と入力してください。
同様に「Pages」フォルダにユーザーコントロール`HelpContent`を追加してください。
![025_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/6182715a-fff5-1e33-e222-77b2b6721ac2.png)

# ■タブのコンテンツ組み込み

追加したコントロールをメイン画面の各タブで参照します。

MainWindow.xamlのWindowに
`xmlns:page="clr-namespace:RegexTester.Views.Pages"`を追加してください。

```xml:MainWindow.xaml
<Window
:
    xmlns:page="clr-namespace:RegexTester.Views.Pages"
:
```

そして、TabItemの中に`page:`の後に先ほど追加したユーザーコントロールの名前を書きます。

```xml:MainWindow.xaml
<TabControl Margin="6">
    <TabItem Header="マッチング">
        <page:MatchingContent />
    </TabItem>

    <TabItem Header="ヘルプ">
        <page:HelpContent />
    </TabItem>
</TabControl>
```

`page`の部分は任意の名前を付けられます。
`xmlns:abc="clr-namespace:RegexTester.Views.Pages"`としていたら
`<abc:MatchingContent />`という書き方でPagesフォルダのコントロールを参照できます。

ここまでで一度ビルドしておきましょう。

# ■ヘルプ内容作成

## ◇画面

「ヘルプ」タブに表示する内容を作ります。
HTMLのテーブル風のリファレンスとURLリンクを作ります。
![030.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/26b2267a-f25b-ef7d-b4c0-4ffa50cd1a00.png)

Xamlの書き方もHTMLのTableと少し似ています。

```xml:HelpContent.xaml
<UserControl
    x:Class="RegexTester.Views.Pages.HelpContent"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:RegexTester.Views.Pages"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    d:DesignHeight="500"
    d:DesignWidth="800"
    mc:Ignorable="d">
    <FlowDocumentScrollViewer>
        <FlowDocument>
            <Section>
                <Paragraph>
                    <Bold>■正規表現クイックリファレンス</Bold>
                </Paragraph>
                <Table FontFamily="JetBrains Mono, Cascadia Code, BIZ UDGothic, Yu Gothic Medium, Meiryo">
                    <Table.Resources>
                        <Style TargetType="TableCell">
                            <Setter Property="BorderBrush" Value="Silver" />
                            <Setter Property="BorderThickness" Value="0.5" />
                        </Style>
                    </Table.Resources>
                    <Table.Columns>
                        <TableColumn Width="*" />
                        <TableColumn Width="4*" />
                    </Table.Columns>
                    <TableRowGroup>
                        <TableRow>
                            <TableCell>
                                <Paragraph>.</Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>任意の1文字</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                    <TableRowGroup>
                        <TableRow>
                            <TableCell>
                                <Paragraph>*</Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>直前の要素の0回以上繰り返し</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                    <TableRowGroup>
                        <TableRow>
                            <TableCell>
                                <Paragraph>+</Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>直前の要素の1回以上繰り返し</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                    <TableRowGroup>
                        <TableRow>
                            <TableCell>
                                <Paragraph>?</Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>直前の要素の0～1回繰り返し</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                    <TableRowGroup>
                        <TableRow>
                            <TableCell>
                                <Paragraph>^</Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>行頭</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                    <TableRowGroup>
                        <TableRow>
                            <TableCell>
                                <Paragraph>$</Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>行末</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                    <TableRowGroup>
                        <TableRow>
                            <TableCell>
                                <Paragraph>{🥝}</Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>直前の要素の🥝回繰り返し</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                    <TableRowGroup>
                        <TableRow>
                            <TableCell>
                                <Paragraph>{🥝,}</Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>直前の要素の🥝回以上繰り返し</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                    <TableRowGroup>
                        <TableRow>
                            <TableCell>
                                <Paragraph>{🥝,🍉}</Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>直前の要素の🥝～🍉回繰り返し</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                    <TableRowGroup>
                        <TableRow>
                            <TableCell>
                                <Paragraph>[🥝]</Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>🥝内の任意の1文字</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                    <TableRowGroup>
                        <TableRow>
                            <TableCell>
                                <Paragraph>[^🥝]</Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>🥝内にない任意の1文字</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                    <TableRowGroup>
                        <TableRow>
                            <TableCell>
                                <Paragraph>[🥝-🍉]</Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>文字範囲🥝から🍉までの任意の1文字</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                    <TableRowGroup>
                        <TableRow>
                            <TableCell>
                                <Paragraph>()</Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>グループ</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                    <TableRowGroup>
                        <TableRow>
                            <TableCell>
                                <Paragraph>(?:🥝)</Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>非キャプチャ グループ</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                    <TableRowGroup>
                        <TableRow>
                            <TableCell>
                                <Paragraph>
                                    (?&lt;🍇&gt;🥝)
                                </Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>名前付きグループ。🍇がグループ名</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                </Table>
            </Section>
            <Section>
                <Paragraph>
                    <Bold>■もっと詳しく</Bold>
                </Paragraph>
                <Paragraph>
                    <Hyperlink
                        NavigateUri="https://docs.microsoft.com/ja-jp/dotnet/standard/base-types/regular-expressions"
                        RequestNavigate="Hyperlink_RequestNavigate">
                        .NET の正規表現 | Microsoft Docs
                    </Hyperlink>
                </Paragraph>
            </Section>
        </FlowDocument>
    </FlowDocumentScrollViewer>
</UserControl>
```

## ◇ロジック

### ◎.NET Frameworkの場合

```csharp:HelpContent.xaml.cs
using System.Windows.Controls;
using System.Windows.Navigation;

namespace RegexTester.Views.Pages
{
    /// <summary>
    /// HelpContent.xaml の相互作用ロジック
    /// </summary>
    public partial class HelpContent : UserControl
    {
        public HelpContent()
        {
            InitializeComponent();
        }

        private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
        {
            // URLを開く
            System.Diagnostics.Process.Start(e.Uri.AbsoluteUri);
        }
    }
}
```
### ◎.NET Core 3.1 / .NET 5.0の場合

```csharp:HelpContent.xaml.cs
using System.Windows.Controls;
using System.Windows.Navigation;

namespace RegexTester.Views.Pages
{
    /// <summary>
    /// HelpContent.xaml の相互作用ロジック
    /// </summary>
    public partial class HelpContent : UserControl
    {
        public HelpContent()
        {
            InitializeComponent();
        }

        private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
        {
            var p = new System.Diagnostics.Process();
            p.StartInfo.FileName = e.Uri.AbsoluteUri;
            p.StartInfo.UseShellExecute = true;
            // URLを開く
            p.Start();
        }
    }
}
```

作り終わったらビルドし、MainWindowで「ヘルプ」タブを選択してみましょう。
ちゃんと「ヘルプ」タブ内に表示されました。
![035.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/bddb901b-dfd0-833e-46dc-91dab8bbb62e.png)

# ■メインコンテンツ作成

「マッチング」タブに表示する内容を作ります。
![040.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/6449751a-1272-09e0-42ae-8ff6696e64e0.png)

```xml:MatchingContent.xaml
<UserControl
    x:Class="RegexTester.Views.Pages.MatchingContent"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:RegexTester.Views.Pages"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    d:DesignHeight="450"
    d:DesignWidth="800"
    mc:Ignorable="d">
    <UserControl.Resources>
        <Style TargetType="Label">
            <Setter Property="Margin" Value="6,6,6,0" />
        </Style>
        <Style TargetType="TextBox">
            <Setter Property="Margin" Value="6,0,6,6" />
            <Setter Property="FontFamily" Value="JetBrains Mono, Cascadia Code, BIZ UDGothic, Yu Gothic Medium, Meiryo" />
        </Style>
        <Style TargetType="CheckBox">
            <Setter Property="Margin" Value="6" />
        </Style>
    </UserControl.Resources>
    <Grid FocusManager.FocusedElement="{Binding ElementName=InTxt}">
        <Grid.RowDefinitions>
            <!--  上部（検索文字列）  -->
            <RowDefinition Height="Auto" MinHeight="60" />
            <!--  スプリッター  -->
            <RowDefinition Height="Auto" />
            <!--  下部（正規表現パターン～結果）  -->
            <RowDefinition />
        </Grid.RowDefinitions>

        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <!--  文字列テキスト行  -->
                <RowDefinition />
            </Grid.RowDefinitions>

            <Label Content="文字列(_I)" Target="{Binding ElementName=InTxt}" />
            <TextBox
                x:Name="InTxt"
                Grid.Row="1"
                AcceptsReturn="True"
                AcceptsTab="False"
                Text=""
                TextWrapping="Wrap"
                VerticalScrollBarVisibility="Visible" />
        </Grid>

        <GridSplitter
            Grid.Row="1"
            Height="6"
            Margin="0,1"
            HorizontalAlignment="Stretch" />

        <Grid Grid.Row="2">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <!--  結果のテキスト行  -->
                <RowDefinition />
            </Grid.RowDefinitions>

            <StackPanel>
                <Label Content="正規表現パターン(_P)" Target="{Binding ElementName=RegPtn}" />
                <TextBox x:Name="RegPtn" Text="" />

                <StackPanel Orientation="Horizontal">
                    <CheckBox x:Name="ROptIgnoreCase" Content="大文字小文字の区別をしない(_O)" />
                    <CheckBox x:Name="ROptMultiLine" Content="複数行モード(_M)" />
                    <CheckBox x:Name="ROptSingleLine" Content="単一行モード(_S)" />

                    <Button
                        Margin="12,0"
                        Padding="18,0"
                        Click="RunButton_Click"
                        Content="実行(_E)"
                        ToolTip="マッチングを実行します" />
                </StackPanel>

                <Label Content="結果(_R)" Target="{Binding ElementName=OutTxt}" />
            </StackPanel>

            <TextBox
                x:Name="OutTxt"
                Grid.Row="1"
                IsReadOnly="True"
                Text=""
                VerticalScrollBarVisibility="Visible" />
        </Grid>
    </Grid>
</UserControl>
```

```csharp:MatchingContent.xaml.cs
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;

namespace RegexTester.Views.Pages
{
    /// <summary>
    /// MatchingContent.xaml の相互作用ロジック
    /// </summary>
    public partial class MatchingContent : UserControl
    {
        public MatchingContent()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 実行ボタンクリック
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RunButton_Click(object sender, RoutedEventArgs e)
        {
            this.IsEnabled = false;
            ExecuteMatching(InTxt.Text, RegPtn.Text, GetRegexOptions());
            this.IsEnabled = true;
        }

        /// <summary>
        /// マッチング実行
        /// </summary>
        /// <param name="input">文字列</param>
        /// <param name="regPattern">正規表現パターン</param>
        /// <param name="regOpt">検索オプション</param>
        public void ExecuteMatching(string input, string regPattern, RegexOptions regOpt)
        {
            OutTxt.Clear();

            // 未入力がないかチェック
            if (IsAnyNullOrEmpties(input, regPattern))
            {
                MessageBox.Show("未入力の項目があります。");
                return;
            }

            try
            {
                var reg = new Regex(regPattern, regOpt);
                // マッチング実行
                MatchCollection matches = reg.Matches(input);
                if (matches.Count == 0)
                {
                    OutTxt.Text = "★マッチなし★";
                    return;
                }

                var sb = new StringBuilder();
                sb.Append($"★{matches.Count}件マッチ★\n");
                foreach (Match m in matches)
                {
                    sb.Append($"Value ⇒ {m.Value}\n");

                    sb.Append("【グループ名】\n");
                    foreach (string gName in reg.GetGroupNames())
                    {
                        sb.Append($"{gName} => {m.Groups[gName].Value}\n");
                    }
                    sb.Append("--------------------\n");
                }

                // 結果を設定
                OutTxt.Text = sb.ToString();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        /// <summary>
        /// 未入力チェック
        /// </summary>
        /// <param name="values"></param>
        /// <returns>全部入力されていればfalse</returns>
        private bool IsAnyNullOrEmpties(params string[] values)
        {
            foreach (string value in values)
            {
                if (string.IsNullOrEmpty(value))
                {
                    // 未入力
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 画面から正規表現オプション取得
        /// </summary>
        /// <returns></returns>
        private RegexOptions GetRegexOptions()
        {
            var opt = RegexOptions.None;

            // 大文字小文字の区別をしない
            if (ROptIgnoreCase.IsChecked == true)
            {
                opt |= RegexOptions.IgnoreCase;
            }
            // 複数行モード
            if (ROptMultiLine.IsChecked == true)
            {
                opt |= RegexOptions.Multiline;
            }
            // 単一行モード
            if (ROptSingleLine.IsChecked == true)
            {
                opt |= RegexOptions.Singleline;
            }

            return opt;
        }

    }
}
```

作り終わったらビルドし、MainWindowで「マッチング」タブを見てみましょう。

# ■メイン画面外枠部分の実装

「閉じる」ボタンの処理を書きます。

```xml:MainWindow.xaml
:
<StackPanel Grid.Row="1">
    <Button
        Width="150"
        Margin="12,6"
        HorizontalAlignment="Right"
        Click="CloseButton_Click"
        Content="閉じる(_C)" />
</StackPanel>
```

```csharp:MainWindow.xaml.cs
using System.Windows;

namespace RegexTester
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void CloseButton_Click(object sender, RoutedEventArgs e)
        {
            Close();
        }
    }
}
```

# ■実行

「文字列」のテキストボックスに検索対象の文字列を入力し、
「正規表現パターン」のテキストボックスに検索する正規表現を入力し、実行ボタンを押します。
![045.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/d6240e70-076e-a883-c528-fa55b405622b.png)
![050_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/a6fefa17-cfe2-f96c-453a-8071ef91fd4f.png)
![055_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/d890294e-72ea-8e94-f1b2-72319ea07980.png)
![060_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142879/5a8c2c44-e11d-e581-f12e-46a7820fd897.png)

おしまい

---

[＜＜ 最初の記事](http://qiita.com/Kosen-amai/items/f9e3df2aa80363f5af5b "Windows GUIプログラミング入門1 メッセージボックス表示") 　 [＜ 前の記事](https://qiita.com/Kosen-amai/items/e852840f8f4b30680458 "Windows GUIプログラミング入門26 画面キャプチャ") 　 ~~次の記事 ＞~~