nakamurakko’s blog

仕事で覚えたこと、勉強したことを自分のメモ代わりに書いていこうかなと。

テキスト情報を読み上げる WPF アプリケーションを作成する

環境

  • Windows 11
  • Visual Studio 2022
  • .NET 7

テキスト情報を読み上げるUWPアプリケーションを作成するテキスト情報を読み上げるWindows Formsアプリケーションを作成する でテキストを読み上げるアプリケーションを作成していたけど、 GitHub にリポジトリーを作成していなかった事、手軽に実行したい事(UWP だとインストールされてしまうし、 .NET Framework より .NET で作った方が今後は良さそうだし)を理由に WPF 版として再度作ってみた。

今回作成したサンプルコードは https://github.com/nakamurakko/WpfTextReader を参照。

プロジェクト作成

Visual Studio を起動して、 WPF アプリケーションのプロジェクトを新規作成する。 プロジェクトに下記 NuGet パッケージを追加する。

パッケージ 説明
System.Speech 音声出力に必要なパッケージ。使用している SpeechSynthesizer クラスのドキュメントが .NET Framework 4.8.1 で止まっているので、 .NET 7 で使用すべきパッケージではないかもしれない。
CommunityToolkit.Mvvm MVVM の実装をサポートするパッケージ。サンプルでは ObservablePropertyRelayCommand を使用している。

ViewModel 実装

MainView 用 の ViewModel を用意し、システムで利用可能な音声一覧、選択した音声用のプロパティを用意する。

/// <summary>
/// 音声一覧。
/// </summary>
[ObservableProperty]
private ObservableCollection<InstalledVoice> _voices = new ObservableCollection<InstalledVoice>();

/// <summary>
/// 選択した音声。
/// </summary>
[ObservableProperty]
private InstalledVoice _selectedVoice;

コンストラクターで、 SpeechSynthesizer.GetInstalledVoices() を使って音声一覧を取得して、1つ目を選択状態にしておく。

public MainWindowViewModel()
{
    using (SpeechSynthesizer synthesizer = new SpeechSynthesizer())
    {
        // 利用可能な音声を一覧に追加する。
        foreach (InstalledVoice voice in synthesizer.GetInstalledVoices())
        {
            this.Voices.Add(voice);
        }
    }

    // 音声の1つ目を選択状態にする。
    this.SelectedVoice = this.Voices.FirstOrDefault();
}

テキストを読み上げるコマンドを用意する。

/// <summary>
/// テキストを読み上げる。
/// </summary>
[RelayCommand]
private void ReadText()
{
    if ((this.SelectedVoice == null) || string.IsNullOrWhiteSpace(this.TargetText))
    {
        return;
    }

    Task.Run(() =>
    {
        using (SpeechSynthesizer synthesizer = new SpeechSynthesizer())
        {
            // 選択音声を設定。
            synthesizer.SelectVoice(this.SelectedVoice.VoiceInfo.Name);
            // テキストを読み上げる。
            synthesizer.Speak(this.TargetText);
        }
    });
}

SpeechSynthesizer クラス の説明に、

SpeechSynthesizerへの最後の参照を解放する前に、必ずDisposeを呼び出してください。 そうしないと、ガベージ コレクターが SpeechSynthesizer オブジェクトの Finalize メソッドを呼び出すまで、使用されているリソースは解放されません。

とあるため、メソッド内で SpeechSynthesizer をインスタンス化して、かつテキスト読み上げ中のアプリケーションフリーズを避けるには Task.Run()SpeechSynthesizer.Speak() を呼び出すのが良いと思う。

View 実装

ViewModel の各機能を View に紐づければ完成。

<Window x:Class="WpfTextReader.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfTextReader"
        mc:Ignorable="d"
        xmlns:viewModels="clr-namespace:WpfTextReader"
        Title="{Binding Title}"
        d:DataContext="{d:DesignInstance viewModels:MainWindowViewModel}"
        d:Height="400"
        d:Width="600">

    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="auto" />
                <ColumnDefinition Width="auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <!-- ①テキストを読み上げるコマンドを呼び出すボタン。 -->
            <Button Grid.Column="0"
                    Content="読み上げ"
                    Command="{Binding ReadTextCommand}" />
            <TextBlock Grid.Column="1"
                       HorizontalAlignment="Center"
                       Text="音声選択"
                       VerticalAlignment="Center" />
            <!-- ②音声を選択するコンボボックス。 -->
            <ComboBox Grid.Column="2"
                      ItemsSource="{Binding Voices}"
                      SelectedItem="{Binding SelectedVoice}">
                <ComboBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding VoiceInfo.Name}" />
                    </DataTemplate>
                </ComboBox.ItemTemplate>
            </ComboBox>
        </Grid>

        <!-- ③読み上げたいテキスト。 -->
        <TextBox Grid.Row="1"
                 Text="{Binding TargetText}"
                 TextWrapping="Wrap" />

    </Grid>

</Window>

実行結果

読ませたいテキストを入力し、読み上げボタンをクリックすれば、 Windows にインストールされている音声で読み上げてくれる。

フランス語など別言語の音声を使いたい場合、「設定 > 時刻と言語 > 音声認識」で音声を追加すれば可能。