9
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

私powershellだけどタスクトレイの片隅でアイを叫ぶ

Last updated at Posted at 2018-12-28

はじめに

こちらの記事に記載したPowerShellでタスクトレイに常駐するスクリプトを運用していたところ、バルーン表示以外にもタスクトレイのアイコンを操作してユーザー通知する仕組みが欲しくなったので作ってみました。

アイコンを差し替える方法

アイコンはSystem.Windows.Forms.NotifyIconのIconプロパティに指定しています。

私のテンプレートではExtractAssociatedIconを利用してアプリケーションアイコンを表示しています。
ネットのサンプルもこの方式が多いようでした。


$path = Get-Process -id $pid | Select-Object -ExpandProperty Path
$icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path)

切り替える場合は、アイコンを取得して指定すればよいです。
アイコンの取得先は何でもよいですが、以下の2種が簡単そうです。

ICOファイルを読み込む方法


$icon = new-object System.Drawing.Icon .\sample.ico

画像ファイルをアイコンとして利用する方法


$image = [System.Drawing.Image]::FromFile(".\sample.png")
$icon = [System.Drawing.Icon]::FromHandle($image.GetHicon())
$image.Dispose()

通知したい場合に、アイコンの色を変える場合は上記の処理で問題ないと思います。

文字を切り替えて表示する

個人的には配布用する際にアイコンファイルや、画像ファイルをセットで配布するのが嫌なのでスクリプト内で解決する方法を実装しました。

スクリプト内で画像を描画して、アイコンに利用します。
文字列を描画することで、メッセージ表示を可能にしました。


Function ShowTaskTrayString {
    [CmdletBinding()]
    PARAM (
        [Parameter(Mandatory=$true)][String] $message,
        [Parameter()][int] $interval = 1000
    )

    Write-Host $message

    Add-Type -AssemblyName System.Windows.Forms

    $application_context = New-Object System.Windows.Forms.ApplicationContext
    $timer = New-Object Windows.Forms.Timer

    # タスクトレイアイコン
    $notify_icon = New-Object System.Windows.Forms.NotifyIcon
    $notify_icon.Visible = $true
    $notify_icon.Text = $message
    function get_char_icon($image_text){

        Write-Host $image_text
        $image_pixel = 16 # アイコンの幅、高さのピクセル数 
        
        $brush_bg = New-Object Drawing.SolidBrush([System.Drawing.Color]::FromArgb(0,0,0,0)) # Aを0にして透過させる 透過なのでRGBは何でもよい。
        $brush_text = New-Object Drawing.SolidBrush([System.Drawing.Color]::FromArgb(255,0,255,0)) # Aは255で透過させない 
        $font = new-object System.Drawing.Font("メイリオ", $image_pixel, "Bold","Pixel")
        
        $icon_image = new-object System.Drawing.Bitmap([int]($image_pixel)),([int]($image_pixel))
        $image_graphics = [System.Drawing.Graphics]::FromImage($icon_image)
        
        $format = [System.Drawing.StringFormat]::GenericDefault
        $format.Alignment = [System.Drawing.StringAlignment]::Center
        $format.LineAlignment = [System.Drawing.StringAlignment]::Center
        $rect = [System.Drawing.RectangleF]::FromLTRB(0, 0, $image_pixel, $image_pixel)

        # テキスト全体を描画する
        $image_graphics.FillRectangle($brush_bg, $rect)
        $image_graphics.DrawString($image_text, $font, $brush_text, $rect, $format)
        $icon_image.save("icon.bmp", [System.Drawing.Imaging.ImageFormat]::Bmp)
        
        $icon = [System.Drawing.Icon]::FromHandle($icon_image.GetHicon())
        $icon_image.Dispose()

        return $icon
    }
      
    $script:message_char_index = 0
    $notify_icon.Icon = get_char_icon($message.substring(0,1))

    # タイマーイベント.
    $timer.Enabled = $true
    $timer.Add_Tick({
        $timer.Stop()

        if( $message.Length -le $script:message_char_index ){
            $application_context.ExitThread()
            return
        }

        # アイコンを入れ替える
        $notify_icon.Icon = get_char_icon($message.substring($script:message_char_index,1))

        # インデックスを更新
        $script:message_char_index += 1

        # インターバルを再設定してタイマー再開
        $timer.Interval = $interval
        $timer.Start()
    })

    $timer.Interval = 1
    $timer.Start()

    [void][System.Windows.Forms.Application]::Run($application_context)

    $timer.Stop()
    $notify_icon.Visible = $false
}

動作確認


ShowTaskTrayString -message あいしてる

anime2.gif

スクロール式

文字を描画することでメッセージ表示が可能になりましたが、1文字づつ切り替えるとスペース入りの文字列などで読みにくいので、電光掲示板のようにスクロールする方式も作りました。


Function ScrollTaskTrayString {
    [CmdletBinding()]
    PARAM (
        [Parameter(Mandatory=$true)][String] $message,
        [Parameter()][int] $interval = 100 # 1ピクセルスクロールする間隔 ミリ秒
    )

    Add-Type -AssemblyName System.Windows.Forms

    function get_text_bitmap($message,$dist_pixel){
        $brush_bg = New-Object Drawing.SolidBrush([System.Drawing.Color]::FromArgb(0,0,0,0)) # Aを0にして透過させる 透過なのでRGBは何でもよい。
        $brush_text = New-Object Drawing.SolidBrush([System.Drawing.Color]::FromArgb(255,255,0,0))  # Aは255で透過させない 
        $font = new-object System.Drawing.Font("メイリオ", $dist_pixel, "Bold","Pixel")

        # テキスト描画に必要なサイズを取得
        $font_size = [System.Windows.Forms.TextRenderer]::MeasureText($message, $font)
        $image_width = $font_size.Width + ($dist_pixel * 2) # 左右に余白を用意したサイズを指定
        $image_height = $font_size.Height
        $text_image = new-object System.Drawing.Bitmap([int]($image_width)),([int]($image_height))
        $text_image_graphics = [System.Drawing.Graphics]::FromImage($text_image)

        $format = [System.Drawing.StringFormat]::GenericDefault
        $format.Alignment = [System.Drawing.StringAlignment]::Center
        $format.LineAlignment = [System.Drawing.StringAlignment]::Center
        $rect = [System.Drawing.RectangleF]::FromLTRB(0, 0, $image_width, $image_height)

        # テキスト全体を描画する
        $text_image_graphics.FillRectangle($brush_bg, $rect)
        $text_image_graphics.DrawString($message, $font, $brush_text, $rect, $format)

        return $text_image
    }

    function get_icon_from_image($image,$dist_pixel,$scroll_index){

        $icon_image = new-object System.Drawing.Bitmap([int]($dist_pixel)),([int]($dist_pixel))
        $icon_image_graphics = [System.Drawing.Graphics]::FromImage($icon_image)

        # タスクトレイに描画する部分を切り出し
        $rect = New-Object Drawing.Rectangle 0, 0, $dist_pixel ,$dist_pixel
        $icon_image_graphics.DrawImage($image, $rect, $scroll_index, 0, $dist_pixel, $dist_pixel, ([Drawing.GraphicsUnit]::Pixel))

        $icon = [System.Drawing.Icon]::FromHandle($icon_image.GetHicon())
        $icon_image.Dispose()

        return $icon
    }

    $application_context = New-Object System.Windows.Forms.ApplicationContext
    $timer = New-Object Windows.Forms.Timer

    # タスクトレイアイコン
    $notify_icon = New-Object System.Windows.Forms.NotifyIcon
    $notify_icon.Visible = $true
    $notify_icon.Text = $message

    $script:scroll_index = 0 # 描画位置
    $image_pixel = 16 # アイコンの幅、高さのピクセル数

    # テキスト全体のイメージを生成
    $src_image = get_text_bitmap $message $image_pixel
    $notify_icon.Icon = get_icon_from_image $src_image $image_pixel 0

    # タイマーイベント.
    $timer.Enabled = $true
    $timer.Add_Tick({
        $timer.Stop()

        # 端まで描画したら終了
        if( $src_image.Width -le $script:scroll_index ){
            $application_context.ExitThread()
            return
        }

        # アイコンを入れ替える
        $notify_icon.Icon = get_icon_from_image $src_image $image_pixel $script:scroll_index

        # インデックスを更新(1ピクセルのスクロール)
        $script:scroll_index += 1

        # インターバルを再設定してタイマー再開
        $timer.Interval = $interval
        $timer.Start()
    })

    $timer.Interval = 1
    $timer.Start()

    [void][System.Windows.Forms.Application]::Run($application_context)

    $timer.Stop()
    $notify_icon.Visible = $false
}

動作確認


ScrollTaskTrayString -message あいしてる -interval 50

scroll3.gif

さいごに

作ってみたけど、あんまり使い道はないかも!

私PowerShellだけど…シリーズ

私PowerShellだけど、君のタスクトレイで暮らしたい
私PowerShellだけど「送る」からファイルを受け取りたい(コンテキストメニュー登録もあるよ)
私PowerShellだけど子を持つ親になるのはいろいろ大変そう

9
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?