[C#] トースト通知とリマインダを実装する方法

[C#] トースト通知とリマインダを実装する方法

トースト通知とリマインダを実装したい

C# でトースト通知を実装する方法をまとめます。トースト通知というのは Windows OS で右下に出てくる通知のことです。

チャットアプリの通知やスケジューラーの通知なんかでよく使われるやつですね。これを C# のコード上から実装する方法をまとめます。

また、単純に通知するだけでなくスケジュールとして登録して、リマインダ的に通知を使える方法もまとめます。

実装前の準備

C# アプリからローカルのトースト通知を送信する – Windows apps | Microsoft Docs

上のページにいろいろまとまっているので実装する際には確認することをお勧めします。

環境設定

  • .NET 5.0
  • WPF

今回使用するのは .NET 5.0 を使用します。また、コンソールアプリでも動作するのですが、わかりやすいので WPF アプリで実装していきます。

プロジェクトファイルの編集

まずは WPF アプリを作成し、プロジェクトファイルを以下のように編集します。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
+    <TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
-    <TargetFramework>net5.0</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
</Project>

いわゆるおまじない的なものです。

“-windows10.0.19041.0” の部分がないと実装例のところの Show()Schedule() のところでコンパイルエラーになります。

Microsoft.Toolkit.Uwp.Notifications を Nuget

Microsoft.Toolkit.Uwp.Notifications を Nuget でインストールします。

実装例

では準備ができたら実装を進めていきます。幾つかのパターンでトースト通知を出してみます。

トースト通知を発行

まずは最初に C# のコード上からシンプルなトースト通知を発行する方法です。

new ToastContentBuilder()
    .AddText("自作アプリからの通知です!")
    .AddText("ボタン押下時に即時トースト通知が発行されます。")
    .Show();

基本は ToastContentBuilder() でトーストを作成し、Show() を実行することでトースト通知が表示されます。

AddText() の引数でトーストに表示する文字列を指定します。

スケジュールを登録して通知する

単純に通知するのではなく、リマインダのように通知させることも可能です。DateTimeOffset で通知するタイミングを指定できます。

new ToastContentBuilder()
    .AddText("自作アプリからのリマインドによる通知です!")
    .Schedule(DateTime.Now.AddSeconds(10), toast =>
    {
        toast.Group = "My Remind Group";
        toast.Tag = "12345";
    });

Show() の代わりに Schedule() を指定します。引数の DateTimeOffset で通知するタイミング、デリゲートでトーストを一意にできるキー情報を指定します。

Group がリマインダのグループ、Tag がグループ内で一意になるデータをそれぞれ文字列で指定します。2つのデータを合わせて一意にデータを特定できるようにします。あとでキャンセルしたりするときなんかに使えます。

上の例では10秒後にトースト通知を表示する例です。

スケジュールに登録した通知をキャンセルする

上の例で登録したスケジュールをキャンセルする方法もあります。

// スケジュールされたトーストを取得する
var notifier = ToastNotificationManagerCompat.CreateToastNotifier();
var scheduledToasts = notifier.GetScheduledToastNotifications();

// Group, Tag で対象トーストを取得する
var toRemove = scheduledToasts.FirstOrDefault(i => i.Tag == "12345" && i.Group == "My Remind Group");
if (toRemove != null)
{
    // スケジュールから削除する
    notifier.RemoveFromSchedule(toRemove);
}

GroupTag でスケジュールされたトースト全体から対象を抽出しています。

その後対象トーストを RemoveFromSchedule() でスケジュール上から削除しています。

入力要素をもったトースト

単純な通知だけでなく、入力要素を持ったインタラクティブなトーストを実装することも可能です。例えば、チャットアプリでメンションをもらったときの通知で、いちいちアプリを開かずにトースト内で返信チャットを打って送信するみたいな使い方ができたりします。

以下、トースト通知にテキストボックスとコンボボックス、ボタンをそれぞれ配置し、トーストで入力された内容を画面に表示する例です。

// テキストボックス、コンボボックス、OK/Cancelボタンを配置したトースト
new ToastContentBuilder()
    .AddText("入力してね!")
    .AddInputTextBox("textbox", "this is placeholder ...", "手入力欄")
    .AddComboBox("combobox", "選択欄", "a", new (string, string)[]{
        ("a", "選択肢A"),
        ("b", "選択肢B"),
        ("c", "選択肢C"),
    })
    .AddButton(new ToastButton("OK", "ok"))
    .AddButton(new ToastButton("キャンセル", "cancel"))
    .Show();

AddInputTextBox(), AddComboBox() ではコントロールのIDを指定します。コンボボックスの場合はタプルの配列で選択肢のIDと表示コンテンツを指定します。

上のコードだけではあくまでトーストを表示するだけです。ボタン押下時の処理を定義するには ToastNotificationManagerCompat クラスの OnActivated イベントをリッスンする必要があります。

// ボタン押下時のイベントをリッスン
ToastNotificationManagerCompat.OnActivated += this.ToastNotificationManagerCompat_OnActivated;

// ..

private void ToastNotificationManagerCompat_OnActivated(ToastNotificationActivatedEventArgsCompat e)
{
    // e.Argument で押されたボタンを確認
    var arg = e.Argument;
    if (arg == "cancel") return;

    // e.UserInput がトースト内にあるコントロールの入力値を保持
    var textboxValue = e.UserInput["textbox"].ToString();
    var comboboxValue = e.UserInput["combobox"].ToString();

    // お好きな処理をどうぞ ..
}

気を付ける点としてUIに入力値を反映させたい場合、当然トーストの処理はUIスレッドと異なるスレッドで動作しているので、コントロールを直接触ろうとするとエラーになります。Dispatcher なりを使って処理する必要があります。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        ToastNotificationManagerCompat.OnActivated += this.ToastNotificationManagerCompat_OnActivated;
    }

    private void ToastNotificationManagerCompat_OnActivated(ToastNotificationActivatedEventArgsCompat e)
    {
        var arg = e.Argument;
        if (arg == "cancel") return;

        this.Dispatcher.Invoke(() =>
        {
            // ここでUIを触る
        });
    }
}

こんな感じです。

全体のサンプル

ここまでのサンプルをまとめた全体のサンプルです。

MainWindow.xaml

<Window x:Class="WpfApp1.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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="300">
    <StackPanel Orientation="Vertical">
        <Button Click="ToastNotificationButton_Click">トースト通知</Button>
        <Button Click="RemindButton_Click">通知リマインド</Button>
        <Button Click="RemindCancelButton_Click">リマインドキャンセル</Button>
        <Button Click="InputToastButton_Click">入力可能なトースト</Button>
        <TextBlock x:Name="TextboxBlock"></TextBlock>
        <TextBlock x:Name="ComboboxBlock"></TextBlock>
    </StackPanel>
</Window>

MainWindow.xaml.cs

using System;
using System.Linq;
using System.Windows;
using Microsoft.Toolkit.Uwp.Notifications;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            ToastNotificationManagerCompat.OnActivated += this.ToastNotificationManagerCompat_OnActivated;
        }

        private void ToastNotificationButton_Click(object sender, RoutedEventArgs e)
        {
            new ToastContentBuilder()
                .AddText("自作アプリからの通知です!")
                .AddText("ボタン押下時に即時トースト通知が発行されます。")
                .Show();
        }

        private void RemindButton_Click(object sender, RoutedEventArgs e)
        {
            new ToastContentBuilder()
                .AddText("自作アプリからのリマインドによる通知です!")
                .Schedule(DateTime.Now.AddSeconds(10), toast =>
                {
                    toast.Group = "My Remind Group";
                    toast.Tag = "12345";
                });
        }

        private void RemindCancelButton_Click(object sender, RoutedEventArgs e)
        {
            // スケジュールされたトーストを取得する
            var notifier = ToastNotificationManagerCompat.CreateToastNotifier();
            var scheduledToasts = notifier.GetScheduledToastNotifications();

            // Group, Tag で対象トーストを取得する
            var toRemove = scheduledToasts.FirstOrDefault(i => i.Tag == "12345" && i.Group == "My Remind Group");
            if (toRemove != null)
            {
                // スケジュールから削除する
                notifier.RemoveFromSchedule(toRemove);
            }
        }

        private void InputToastButton_Click(object sender, RoutedEventArgs e)
        {
            new ToastContentBuilder()
                .AddText("入力してね!")
                .AddInputTextBox("textbox", "this is placeholder ...", "手入力欄")
                .AddComboBox("combobox", "選択欄", "a", new (string, string)[]{
                    ("a", "選択肢A"),
                    ("b", "選択肢B"),
                    ("c", "選択肢C"),
                })
                .AddButton(new ToastButton("OK", "ok"))
                .AddButton(new ToastButton("キャンセル", "cancel"))
                .Show();
        }

        private void ToastNotificationManagerCompat_OnActivated(ToastNotificationActivatedEventArgsCompat e)
        {
            var arg = e.Argument;
            if (arg == "cancel") return;

            this.Dispatcher.Invoke(() =>
            {
                this.TextboxBlock.Text = e.UserInput["textbox"].ToString();
                this.ComboboxBlock.Text = e.UserInput["combobox"].ToString();
            });
        }
    }
}

.csproj ファイル

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.0.2" />
  </ItemGroup>

</Project>

以上。

参考URL

C#カテゴリの最新記事