5
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

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

概要

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

調べたところ、MaterialDesignになじみそうなNotifications.Wpfと、名前がよく似ている(というかフォーク)のNotification.Wpfが見つかった。

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

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

前提環境

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

Notification.Wpfによる通知

GitHubにサンプルが動画であがっているので、そちらを参考にした。
完成系がこちら↓

Window内への通知

image.png

ここで通知を押すとさらに↓画面右下に以下のような通知が出る。
image.png

WinRTによる通知

日曜研究室さんの情報、およびリンク先で紹介されているWinRTの参照方法を参考にした。

みかづきメモによると8種類ほどテンプレートをWindowsが用意してくれていて、独自にToastをカスタマイズするにはXMLで行う模様。

完成系がこちら↓

image.png

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

image.png

ソースコード

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

設定

Windows通知Toastを利用するにはWindowsSDKが必要な様子。はいってなければインストールする。

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

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

image.png

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

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

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

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

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を追加でインストールしている。

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


  1. .NET5からは楽になるみたい 

  2. Notification.Wpfが求めてくるのでCaliburn.Micro.Coreもインストールした。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
5
Help us understand the problem. What are the problem?