C# / WPFのデスクトップアプリで通知を出す（2案）

# 概要

MaterialDesign ToolKit for xamlを使っていて、標準ダイアログでは我慢できなかった。
Snackbarはちょっと物足りないし、Windows10の通知みたいなものも使いたい。

調べたところ、MaterialDesignになじみそうな[Notifications.Wpf](https://github.com/Federerer/Notifications.Wpf)と、名前がよく似ている（というかフォーク）の[Notification.Wpf](https://github.com/Platonenkov/Notifications.Wpf)が見つかった。

2017年から更新されていない前者に対し、ProgressBarとかを追加して2020年現在メンテされてる模様。
いずれもNuGetからインストールできる。

ただMDに馴染むがWindowsの通知機能ではないので、そちらも使える方法を探すことにした。


## 前提環境

- WPF
- Windows(8.1↑)
- .NET Framework (4.7↑)

# Notification.Wpfによる通知

[GitHub](https://github.com/Platonenkov/Notifications.Wpf)にサンプルが動画であがっているので、そちらを参考にした。
完成系がこちら↓  

## Window内への通知
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23242/9ad29139-875d-1c52-fd48-0bb9beb40dc7.png)

ここで通知を押すとさらに↓画面右下に以下のような通知が出る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23242/93a06934-4bee-69a8-cf0f-192f75f87043.png)

# WinRTによる通知

[日曜研究室](https://peta.okechan.net/blog/archives/4045)さんの情報、およびリンク先で紹介されている[WinRTの参照方法](https://ufcpp.wordpress.com/2012/09/18/%E3%83%87%E3%82%B9%E3%82%AF%E3%83%88%E3%83%83%E3%83%97-%E3%82%A2%E3%83%97%E3%83%AA%E3%81%8B%E3%82%89%E3%81%AEwinrt-api%E5%88%A9%E7%94%A8/)を参考にした。

[みかづきメモ](https://mikazuki.hatenablog.jp/entry/2015/11/16/015833)によると8種類ほどテンプレートを[Windowsが用意して](https://docs.microsoft.com/ja-jp/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts)くれていて、独自にToastをカスタマイズするにはXMLで行う模様。

完成系がこちら↓

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23242/58bce7c5-cd77-4ffb-26e8-2af1d9243177.png)

Windowsでの通知なのでアクションセンターで履歴も表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23242/1a6c0719-7bda-4baa-20be-81324af4ebd9.png)

# ソースコード

（Livetのプロジェクトで開発しはじめたのに、普通にコードビハインドつかってたりするのは無視してください…）

## 設定


Windows通知Toastを利用するには[WindowsSDK](https://developer.microsoft.com/ja-jp/windows/downloads/sdk-archive/)が必要な様子。はいってなければインストールする。

WinRTを読みこむため元情報にあるようにVisualStudio外でcsprojを編集した。
続いてVisualStudioで、「プロジェクト→プロジェクト参照の追加」で参照マネージャより`System.Runtime.WindowsRuntime`と`Windows.winmd`の参照の追加を行った。 [^1]
下のソースでなんとなく読み取れると思うが、それぞれProgramFiles(x86)以下のフォルダをたどって、直接指定している。

またNuGetでNotication.Wpfをインストールした。 [^2]

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23242/354ee157-5a31-ed72-b2f3-c384cc409c15.png)


[^1]: [.NET5からは楽になる](http://qiita.com/okazuki/items/acf95b3ebb21d4d5083b)みたい
[^2]: Notification.Wpfが求めてくるのでCaliburn.Micro.Coreもインストールした。

```xml:hoge.csproj
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net472</TargetFramework>

    <TargetPlatformVersion>8.0</TargetPlatformVersion><!-- ←これ -->

    <UseWPF>true</UseWPF>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Caliburn.Micro.Core" Version="3.2.0" />
    <PackageReference Include="LivetCask" Version="3.2.1" />
    <PackageReference Include="Notification.WPF" Version="1.0.2.1" />
  </ItemGroup>

  <ItemGroup>
    <Reference Include="System.Runtime.WindowsRuntime">
      <HintPath>..\..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.WindowsRuntime.dll</HintPath>
    </Reference>
    <Reference Include="Windows">
      <HintPath>..\..\..\..\..\..\..\..\Program Files (x86)\Windows Kits\8.1\References\CommonConfiguration\Neutral\Annotated\Windows.winmd</HintPath>
      <IsWinMDFile>true</IsWinMDFile>
    </Reference>
  </ItemGroup>

  <ItemGroup>
    <None Update="Resources\giant.png">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>
```

## App.xaml

```xml:App.xaml
<Application x:Class="LivetNotifyWpfTest01.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:controls="clr-namespace:Notification.Wpf.Controls;assembly=Notifications.Wpf"
             xmlns:system="clr-namespace:System;assembly=mscorlib"
             xmlns:local="clr-namespace:LivetNotifyWpfTest01"
             ShutdownMode="OnMainWindowClose"
             StartupUri="Views\MainWindow.xaml"
             Startup="Application_Startup">
    <Application.Resources>
        <local:CustomTemplateSelector x:Key="CustomTemplateSelector" />

        <Style TargetType="controls:Notification">
            <Setter Property="ContentTemplateSelector" Value="{StaticResource CustomTemplateSelector}" />
            <Style.Resources>
                <DataTemplate DataType="{x:Type system:String}" x:Key="PinkStringTemplate">
                    <Label MinHeight="80"
                           Content="{Binding}"
                           Foreground="DeepPink" HorizontalContentAlignment="Center"
                           VerticalContentAlignment="Center" />
                </DataTemplate>
            </Style.Resources>
        </Style>
    </Application.Resources>
</Application>
```

## MainWindow.xaml

ボタン２こおいて、通知エリア用にすこしウィンドウを広くしている。
一番したの controles:NotificationArea が完成系の青い通知に相当。

```xml:MainWindow.xaml
<Window
    x:Class="LivetNotifyWpfTest01.Views.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors"
    xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:v="clr-namespace:LivetNotifyWpfTest01.Views"
    xmlns:controls="clr-namespace:Notification.Wpf.Controls;assembly=Notifications.Wpf"
    xmlns:local="clr-namespace:LivetNotifyWpfTest01"
    xmlns:vm="clr-namespace:LivetNotifyWpfTest01.ViewModels"
    Title="MainWindow"
    Width="525"
    Height="350">

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

    <behaviors:Interaction.Triggers>
        <behaviors:EventTrigger EventName="ContentRendered">
            <l:LivetCallMethodAction MethodName="Initialize" MethodTarget="{Binding}" />
        </behaviors:EventTrigger>

        <behaviors:EventTrigger EventName="Closed">
            <l:DataContextDisposeAction />
        </behaviors:EventTrigger>
    </behaviors:Interaction.Triggers>

    <Grid >
        <Button x:Name="button" Content="Notification.Wpf" HorizontalAlignment="Left" Height="35" Margin="57,83,0,0" VerticalAlignment="Top" Width="138" Click="button_Click"/>
        <Button x:Name="button2" Content="WinRT" HorizontalAlignment="Left" Height="35" Margin="57,150,0,0" VerticalAlignment="Top" Width="138" Click="button_Click2" />

        <controls:NotificationArea x:Name="WindowArea" Position="BottomRight" MaxItems="3" Margin="0 0 10 10"/>
    </Grid>
</Window>
```

## MainWindow.xaml.cs



```csharp:MainWindow.xaml.cs
using System.Diagnostics;
using System.IO;
using System.Windows;
using Windows.UI.Notifications;
using Notification.Wpf;

namespace LivetNotifyWpfTest01.Views
{
    public partial class MainWindow
    {
        private readonly NotificationManager _notificationManager = new NotificationManager();

        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Notification.Wpfでの通知
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button_Click(object sender, RoutedEventArgs e)
        {
            var content = new NotificationContent {Title = "ウィンドウ内の通知やでー", Message = "推して"};
            var clickContent = new NotificationContent
            {
                Title = "推された！💛",
                Message = "う、うおーーー。にっぽごーー",
                Type = NotificationType.Error
            };
            _notificationManager.Show(content, "WindowArea", onClick: () => _notificationManager.Show(clickContent));

        }

        /// <summary>
        /// WinRTを使った通知
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button_Click2(object sender, RoutedEventArgs e)
        {
            var template = ToastTemplateType.ToastImageAndText02;
            var xml = ToastNotificationManager.GetTemplateContent(template);

            var images = xml.GetElementsByTagName("image");
            var src = images[0].Attributes.GetNamedItem("src");
            if (src != null)
            {
                src.InnerText = "file:///" + Path.GetFullPath("Resources\\giant.png");
                Debug.WriteLine(src.InnerText);
            }

            var texts = xml.GetElementsByTagName("text");
            texts[0].AppendChild(xml.CreateTextNode("のび太～。"));
            texts[1].AppendChild(xml.CreateTextNode("ジャイ子が家に来てほしいってさ～"));

            var toast = new ToastNotification(xml);

            ToastNotificationManager.CreateToastNotifier("🤩 通 知 🤩").Show(toast);
        }
        
    }
}
```

## その他

Notification.WpfはCaliburn.Microに依存しているらしく、サンプルコピペでビルドしたらdependエラーとなった。
csprojにあるように、NuGetでCaliburn.Micro.Coreを追加でインストールしている。

かっこよさをとるか、標準仕様をとるかという選択肢があっていいですね。
はい/いいえ等ユーザからのフォードバックもとれそうなので、ダイアログ替わりにも使えそう。