緊急呼び出し用Windowsデスクトップ常駐アプリをWPF+Amazon SNS+SQSで開発

2014.08.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。福田です。

唐突ですがみなさん長期休暇中に緊急で呼び出されるということがよくあると思います。そういったケースで便利なWindowsデスクトップ常駐アプリを作ってみました。主に長期休暇中にずーっとWindowsPCでゲームをやり続けて自宅に引きこもるタイプの方向けのアプリケーションになります。

#00 完成予想図

今回作成したアプリケーションは下記のようにWindowsのデスクトップ右下から大きめの『クラメソちゃん』ポップアップが表示され、赤字でメッセージが描かれており、ポップアップは意識的に非表示にしない限り表示しっぱなしというようなアプリケーションを作りました。

NotifyMe00

アプリケーション仕様

  • 緊急呼び出す側がSNSにメッセージを通知する
  • Amazon SNSからAmazon SQSにメッセージが通知される
  • WindowsデスクトップアプリケーションがAmazon SQSを監視してメッセージを取得する
  • 画面右下に大きめにメッセージが通知され、音楽が再生される。時間経過で自動消去されない=音楽がループ再生され続ける。
  • 緊急呼び出しされる側が気づいて会社に連絡を入れる

(音楽ファイル再生機能部分はまだ出来ていませんorz)

#01 タスクトレイにアイコンを表示する

それではどんどんアプリケーションを作っていくことにします。

  • 今回アプリケーションはVisual Studio 2012で作成しています。
  • 空のWPFプロジェクトの作成:プロジェクト名「NotifyMe」(.NET4.5)
  • NuGetで"Hardcodet WPF NotifyIcon"をインストールします。

今回、Windowsデスクトップ常駐通知アイコンを実装するために Hardcodet WPF NotifyIcon を利用しています。

NotifyMe01

  • タスクトレイ用のアイコンの用意:classmethod.ico
  • Iconsフォルダを作成し、classmethod.icoを配置します。
  • MainWindow.xamlでTaskbarIconの宣言を行います。
    • 参照の追加:xmlns:tb="http://www.hardcodet.net/taskbar"
    • 宣言行の追加。下記のようにTaskbarIconタグを追加しておくだけでタスクバー常駐アイコンの初期設定は完了です!
<tb:TaskbarIcon x:Name="MyNotifyIcon"
                IconSource="/Icons/classmethod.ico"
                ToolTipText="緊急通知" />

MainWindow.xaml

<Window x:Class="NotifyMe.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:tb="http://www.hardcodet.net/taskbar"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <tb:TaskbarIcon x:Name="MyNotifyIcon"
                  IconSource="/Icons/classmethod.ico"
                  ToolTipText="緊急通知">
        </tb:TaskbarIcon>
    </Grid>
</Window>
  • MainWindowクラスのonClosingメソッドに終了処理を追加します。
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
    MyNotifyIcon.Dispose();

    base.OnClosing(e);
}
  • アプリケーションを実行する。
  • タスクトレイにアイコンが表示され、マウスオーバーするとツールチップが表示されます。下記ではクラスメソッドアイコンが見辛いですが、アイコンにマウスオーバーするとツールチップが表示されます。MainWindowウィンドウは非表示化処理を行ってないので、表示されたままです。

NotifyMe02

#02 メインのアプリケーションウィンドウを消す

  • 初期起動時にアプリケーションを最小化します。そのため、MainWindow.xaml.csのコンストラクタに下記を追加します。
this.WindowState = System.Windows.WindowState.Minimized;
  • タスクバーからアイコンを消します。そのため、MainWindow.xaml.csのコンストラクタに下記を追加します。
this.ShowInTaskbar = false;
  • アプリケーションを実行する。
  • タスクトレイにアイコンが表示されますが、タスクバーにはアイコンは表示されず、ウィンドウを持ったアプリケーション本体も表示されない状態になります。

※この状態だとアプリケーションを終了する方法がなくなるので、アプリケーション終了機能は後ほどタスクトレイのアイコンの右クリックで可能になるようにします。

#03 イベントをきっかけに大きめの通知ウィンドウを表示する

タスクトレイのアイコンへのマウス左クリックに応じて、大きめの通知ウィンドウが表示されるようにします。

  • NotifyIconのTrayLeftMouseUpイベントハンドラを作成します。MainWindow.xaml.csのコンストラクタで下記を宣言します。
MyNotifyIcon.TrayLeftMouseUp += (sender, e) => ShowBalloon();

以下、大きめの通知ウィンドウ=カスタムバルーンの表示機能を作ります。

  • Balloonsフォルダを作成し、MyBalloon.xamlカスタムコントロールを作成します。
  • 下記要素を持つMyBalloon.xamlをデザインします。Expression Blendを利用してデザインしてもOKです。
    • CloseButtonImage(Image型)
    • LogoImage(Image型)
    • MessageTextBlock(TextBlock型)

MyBalloon.xaml

<UserControl x:Class="NotifyMe.Balloons.MyBalloon"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             Height="500" Width="400">
    <Grid>
        <Border BorderThickness="1" Margin="0" Background="White" CornerRadius="10">
        	<Border.Effect>
        		<DropShadowEffect/>
        	</Border.Effect>
        </Border>
        <Image Margin="177.949,129,10,10" Source="/NotifyMe;component/Icons/201408_yukata.jpg" Stretch="None"/>
        <Image x:Name="LogoImage" HorizontalAlignment="Left" Height="48" Margin="10,10,0,0" VerticalAlignment="Top" Width="48" Source="/NotifyMe;component/Icons/classmethod.ico" Stretch="None"/>
        <TextBlock x:Name="MessageTextBlock" HorizontalAlignment="Right" Margin="0,0,173,10" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Bottom" Height="416" Width="217" Foreground="Red" FontWeight="Bold" FontSize="26.667"/>

    </Grid>
</UserControl>

MyBalloonイメージ

NotifyMe03

  • ShowBalloonメソッドを作成し、カスタムバルーンを表示します。
/// <summary>
/// 指定したメッセージでカスタムバルーンを表示します。
/// </summary>
/// <param name="message"></param>
private void ShowBalloon(string message)
{
    MyBalloon balloon = new MyBalloon();
    balloon.MessageTextBlock.Text = message;

    MyNotifyIcon.ShowCustomBalloon(balloon, PopupAnimation.Slide, null); // null=do not close balloon automatically
}
  • アプリケーションを実行する。
  • 画面右下に先ほどのイメージのカスタムバルーンが指定したメッセージ付きで表示されます。

#04 右クリックメニューを作成する

アプリケーションの終了機能を付けたいので、右クリックメニューを実装します。

  • MainWindow.xamlでContextMenuを設定する。TaskbarIconタグの子要素として下記を追加します
<tb:TaskbarIcon.ContextMenu>
    <ContextMenu>
        <MenuItem x:Name="CloseMenuItem" Header="閉じる">
        </MenuItem>
        <MenuItem x:Name="TerminateMenuItem" Header="終了">
        </MenuItem>
    </ContextMenu>
</tb:TaskbarIcon.ContextMenu>

MainWindow.xaml

<Window x:Class="NotifyMe.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:tb="http://www.hardcodet.net/taskbar"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <tb:TaskbarIcon x:Name="MyNotifyIcon"
                  IconSource="/Icons/classmethod.ico"
                  ToolTipText="緊急通知">
            <tb:TaskbarIcon.ContextMenu>
                <ContextMenu>
                    <MenuItem x:Name="CloseMenuItem" Header="閉じる">
                    </MenuItem>
                    <MenuItem x:Name="TerminateMenuItem" Header="終了">
                    </MenuItem>
                </ContextMenu>
            </tb:TaskbarIcon.ContextMenu>
        </tb:TaskbarIcon>
    </Grid>
</Window>
  • MainWindow.xaml.csのコンストラクタでContextMenuのイベントハンドラを設定する。
CloseMenuItem.Click += (sender, e) => CloseBalloon();
TerminateMenuItem.Click += (sender, e) => TerminateApplication();
  • カスタムバルーンを閉じる処理とアプリケーション終了処理を実装する。
/// <summary>
/// カスタムバルーンを終了します。
/// </summary>
private void CloseBalloon()
{
    MyNotifyIcon.CloseBalloon();
}

/// <summary>
/// アプリケーションを終了します。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TerminateApplication()
{
    this.Close();
}
  • アプリケーションを実行する。
  • タスクトレイのアイコンを右クリックするとコンテキストメニューが出てきて、バルーンを閉じたり、アプリケーションを終了したり出来るようになりました。

#05 AWS SDK for .NETをインストールする

#06 Amazon SNS、Amazon SQSを準備する

  • アプリケーション用のIAMユーザーを作成する。
  • カスタムポリシーは下記のように設定する。
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt1408452297000",
      "Effect": "Allow",
      "Action": [
        "sqs:DeleteMessage",
        "sqs:ReceiveMessage"
      ],
      "Resource": [
        "arn:aws:sqs:ap-northeast-1:123456789012:NotifyMeQueue"
      ]
    }
  ]
}
  • Amazon SQSのQueueを新規作成する:NotifyMeQueue
  • Amazon SNSのTopicを新規作成する:NotifyMeTopic
  • Amazon SNSのSubscriptionとして先ほどのSQSのQueueを設定する。
    • Create Subscriptionダイアログで、Protocol=Amazon SQS、Endpoint=QueueのARNを指定する
  • 作成したQueueで権限を追加しておく。
    • Queue ActionでAdd a Permission
    • Effect:Allow
    • Principal:Everybody
    • Actions:SendMessage
    • Conditions:Condition=ArnEquals,Key=aws:SourceArn,Value=SNSのTopicのARN
  • Amazon SNSからメッセージをパブリッシュしてみる。
  • Amazon SQSのビューアでメッセージが届いていることを確認する

#07 WindowsアプリケーションからAmazon SQSをポーリングし、メッセージを取得したら画面に表示する

  • タイマーを作成する。ここまでのMainWindow.xaml.csのコンストラクタ:
private DispatcherTimer timer;

public MainWindow()
{
    InitializeComponent();

    this.WindowState = System.Windows.WindowState.Minimized;
    this.ShowInTaskbar = false;

    CloseMenuItem.Click += (sender, e) => CloseBalloon();
    TerminateMenuItem.Click += (sender, e) => TerminateApplication();

    timer = new DispatcherTimer();
    timer.Interval = new System.TimeSpan(0, 1, 0);
    timer.Tick += (sender, e) => RecieveMessage();
    timer.Start();    
}

※コンストラクタでのカスタムバルーン呼び出しは削除してあります。

  • RecieveMessageメソッドでSQSからメッセージを取得する。
  • メッセージが取得できたら、ShowBalloonメソッドを呼び出してカスタムバルーンを表示する。
  • 最後に、取得したメッセージを削除しておく。
  • ※APIからの応答はJSON形式なので、予めNuGetでDynamicJsonをダウンロードしておく。
  • ※予めSQSのQueueのURLを控えておく

RecieveMessageメソッド全体

private void RecieveMessage()
{
    // SQSアクセスの準備
    var amazonSQSConfig = new AmazonSQSConfig();
    amazonSQSConfig.ServiceURL = AppConsts.AWS_SQS_SERVICE_URL;

    var amazonSQSClient = new AmazonSQSClient(amazonSQSConfig);
    
    // メッセージ受信リクエストを作成する
    var receiveMessageRequest = new ReceiveMessageRequest();
    receiveMessageRequest.QueueUrl = AppConsts.AWS_SQS_QUEUE_URL;
    receiveMessageRequest.MaxNumberOfMessages = 1; // default
    
    // SQSのメッセージを取得する
    var receiveMessageResponse = amazonSQSClient.ReceiveMessage(receiveMessageRequest);

    if (receiveMessageResponse.Messages.Count > 0)
    {
        // レスポンスからメッセージを取得する
        Message message = receiveMessageResponse.Messages[0];
        
        // メッセージJSONを操作しやすいように変換
        var json = DynamicJson.Parse(message.Body);
        
        // 画面にメッセージを表示する
        ShowBalloon(json.Message);
        
        // メッセージ削除リクエストを作成する
        var deleteMessageRequest = new DeleteMessageRequest();
        deleteMessageRequest.QueueUrl = AppConsts.AWS_SQS_QUEUE_URL;
        deleteMessageRequest.ReceiptHandle = message.ReceiptHandle;
        
        // メッセージを削除しておく
        var deleteMessageResponse = amazonSQSClient.DeleteMessage(deleteMessageRequest);
    }
}
  • AWS_SQS_SERVICE_URL = "https://sqs.ap-northeast-1.amazonaws.com/"
  • AWS_SQS_QUEUE_URL = "https://sqs.ap-northeast-1.amazonaws.com/123456789012/NotifyMeQueue"
  • アプリケーションを実行する。

AWS Management ConsoleからAmazon SNSのメッセージをPublishすると、Windowsのデスクトップにメッセージが表示されたらアプリケーション完成です!

NotifyMe00