UWSCでタスクトレイ常駐する
公式掲示板にUWSCでタスクトレイ常駐する方法はないか、という質問があった。
需要があるようなので、考えてみた。
TEXTBLOCKの部分はPowershellなので、PowershellでNotifyIconを作成するサンプルでもあります。
追記 2014/11/21
高機能版に、親プロセス(=UWSC)がいなくなったら終了する処理をつけた。
これにより、UWSCを停止ボタンで止めてしまっても、UWSCを終了すればアイコンは消える。
スクリプト
notifyIcon.uws
OPTION EXPLICIT IFB GET_UWSC_NAME = "notifyIcon.uws" THEN MSGBOX("ESCキーでタスクトレイ常駐終了します") DIM id = NotifyIcon.Start("NotifyIcon.SampleCallBack(p)", "iconApp.ico") WHILE !GETKEYSTATE(VK_ESC) SLEEP(1) WEND CTRLWIN(id, CLOSE) ENDIF MODULE NotifyIcon TEXTBLOCK _script Add-Type -AssemblyName System.Windows.Forms $formMain = New-Object System.Windows.Forms.Form $formMain.Opacity = 0 $formMain.Text = '$$formName$$' $textBox = New-Object System.Windows.Forms.TextBox $formMain.Controls.Add($textBox) $components = New-Object System.ComponentModel.Container $notifyIcon = New-Object System.Windows.Forms.NotifyIcon($components) $notifyIcon.Icon = New-Object System.Drawing.Icon('$$iconPath$$') $notifyIcon.Text = "$$tooltip$$" $notifyIcon.Add_MouseClick({ Param ( $sender, $event ) $textBox.Text = $event.Button }) $notifyIcon.Add_MouseDoubleClick({ Param ( $sender, $event ) $textBox.Text = 'D' + $event.Button }) $notifyIcon.Visible = $true $formMain.ShowDialog() $components.Dispose() $formMain.Dispose() ENDTEXTBLOCK FUNCTION Start(callback, iconPath, tooltip=EMPTY, formName=EMPTY, wait=-1) IF tooltip=EMPTY THEN tooltip = GET_UWSC_NAME IF formName=EMPTY THEN formName = "NotifyIcon form " + GET_UWSC_NAME POWERSHELL(REPLACE(REPLACE(REPLACE(_script, "$$formName$$", formName), "$$iconPath$$", iconPath), "$$tooltip$$", tooltip), TRUE) RESULT = GETID(formName, , wait) THREAD _Watch(RESULT, formName, callback) FEND PROCEDURE _Watch(id, formName, callback) DIM p WHILE STATUS(id, ST_TITLE) = formName p = GETSTR(id) IFB LENGTH(p) THEN EVAL(callback) SENDSTR(id, "", 1, TRUE) ELSE SLEEP(0.1) ENDIF WEND FEND PROCEDURE SampleCallBack(p) MSGBOX(p) FEND ENDMODULE
注意
- UWSCを停止ボタン等で終了すると、NotifyIcon用Powershellプロセスが残ったままとなり、アイコンが消えません
- 非表示フォームを閉じれば、アイコンは消えPowershellプロセスも終了します
- 初期状態のフォームなら「CTRLWIN(GETID("NotifyIcon form "), CLOSE)」を実行してください
- Powershellのプロセスをタスクマネージャー等で終了しても良いですが、次の注意にひっかかります
- NotifyIcon用Powershellプロセスをタスクマネージャー等から強引に終了してもアイコンが残ります
- ただしこれはポインターを重ねると消えます
解説他
使い方は簡単、、、かな?
NotifyIcon.Start関数を呼ぶだけ。
リターンがIDなので、終わったらCTRLWINでCLOSEしてやってください。
NotifyIcon.Start関数のパラメーターは、
隠しフォームの名前は通常指定する必要はありませんが、複数の通知アイコンを作成したい場合は、指定してください。
タスクトレイアイコンを、クリックしたりダブルクリックしたりすると、コールバック関数が呼ばれます。
コールバック関数は、_Wait関数を実行するスレッドから呼ばれます。
サンプルだと、引数の値をMSGBOXで表示するので、サンプル実行して動作を見てください。
一応、
- 左クリックで、「Left」
- 右クリックで、「Right」
- 左ダブルクリックで、「DLeft」
- 右ダブルクリックで、「DRight」
が入ってくるはずです。
公式掲示板では、Win32APIを呼ぶ方向で話が進んでましたが、それは面倒です。
Powershell使いましょうよ。とても優秀ですから。
高機能版
上のはシンプルなので残していますが、ついでに高機能版を作成しました。
上のとの違いは、
- アイコンは、関連付けからの抽出に対応した
- アイコンのパスを省略すると、自スクリプトから取り出すようにした
- アイコンをBase64文字列でも指定可能にした(アイコンファイルをBase64文字列にしたもの)
- 不明なものを指定された場合、(スクリプト内に書かれた)適当なアイコンを表示する
- アイコンを後から変更可能にした
- ツールチップも後から変更可能にした
- SampleCallback関数を右クリックメニューに対応した
- 親プロセスが終了したら終了する
notifyIcon.uws
OPTION EXPLICIT IFB GET_UWSC_NAME = "notifyIcon.uws" THEN MSGBOX("右クリックメニューでタスクトレイ常駐終了できます") DIM id = NotifyIcon.Start(), id2=NotifyIcon.Start(EMPTY, "c:\windows\system32\calc.exe", "電卓ではありません", "NotifyIcon form second") WHILE STATUS(id, ST_ISID) OR STATUS(id2, ST_ISID) SLEEP(1) WEND ENDIF MODULE NotifyIcon TEXTBLOCK _script Add-Type -AssemblyName System.Windows.Forms $formMain = New-Object System.Windows.Forms.Form $formMain.Opacity = 0 $formMain.Text = '$$formName$$' $textBox = New-Object System.Windows.Forms.TextBox $formMain.Controls.Add($textBox) $components = New-Object System.ComponentModel.Container $notifyIcon = New-Object System.Windows.Forms.NotifyIcon($components) $notifyIcon.Add_MouseClick({ Param ($sender, $event) $textBox.Text = $event.Button }) $notifyIcon.Add_MouseDoubleClick({ Param ($sender, $event) $textBox.Text = 'D' + $event.Button }) $textBoxTooltip = New-Object System.Windows.Forms.TextBox $textBoxTooltip.Add_TextChanged({ $notifyIcon.Text = $textBoxTooltip.Text }) $textBoxTooltip.Location = New-Object System.Drawing.Point(0, $textBox.Bottom) $formMain.Controls.Add($textBoxTooltip) $textBoxTooltip.Text = "$$tooltip$$" $textBoxIcon = New-Object System.Windows.Forms.TextBox $textBoxIcon.Add_TextChanged({ try { $icon = New-Object System.Drawing.Icon($textBoxIcon.Text) } catch { try { $icon = [System.Drawing.Icon]::ExtractAssociatedIcon($textBoxIcon.Text) } catch { $icon = $null } } if($icon -eq $null) { try { $icon = New-Object System.Drawing.Icon(New-Object System.IO.MemoryStream([Convert]::FromBase64String($textBoxIcon.Text), $false)) } catch { $icon = New-Object System.Drawing.Icon(New-Object System.IO.MemoryStream([Convert]::FromBase64String(@' AAABAAEAEBAQAAAABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAEAAAABA AAAAAAAAAAACAAACAAAAAgIAAgAAAAIAAgACAgAAAgICAAMDAwAAAAP8AAP8AAAD//wD/AAAA/wD/AP //AAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAD//wAA888AAO23AAD9vwAA878AAO+/AADttwAA888AAP//AADn1wAA29c AANvXAADbqwAA26sAANurAAD//wAA '@ ), $false)) } } if($icon -ne $null) { if($notifyIcon.Icon -ne $null) { $notifyIcon.Visible = $false $notifyIcon.Icon.Dispose() } $notifyIcon.Icon = $icon $notifyIcon.Visible = $true } }) $textBoxIcon.Location = New-Object System.Drawing.Point(0, $textBoxTooltip.Bottom) $formMain.Controls.Add($textBoxIcon) $textBoxIcon.Text = '$$iconData$$' $textBoxPpid = New-Object System.Windows.Forms.TextBox $textBoxPpid.Location = New-Object System.Drawing.Point(0, $textBoxIcon.Bottom) $pp = $null $textBoxPpid.Add_TextChanged({ if($pp -ne $null) { $pp.Dispose() } $pp = [System.Diagnostics.Process]::GetProcessById($textBoxPpid.Text) $pp.Add_Exited({ $formMain.Close() }) $pp.SynchronizingObject = $formMain $pp.EnableRaisingEvents = $true }) $formMain.Controls.Add($textBoxPpid) $formMain.ShowDialog() if($pp -ne $null) { $pp.Dispose() } $components.Dispose() $formMain.Dispose() ENDTEXTBLOCK PROCEDURE _Watch(id, callback) DIM p WHILE STATUS(id, ST_ISID) p = GETSTR(id, 1) IFB LENGTH(p) THEN SENDSTR(id, "", 1, TRUE) EVAL(callback) ELSE SLEEP(0.1) ENDIF WEND FEND FUNCTION Start(callback=EMPTY, iconData=EMPTY, tooltip=EMPTY, formName=EMPTY, wait=-1) IF callback=EMPTY THEN callback = "SampleCallBack(id, p)" IF iconData=EMPTY THEN iconData = GET_UWSC_NAME IF tooltip=EMPTY THEN tooltip = GET_UWSC_NAME IF formName=EMPTY THEN formName = "NotifyIcon form " + GET_UWSC_NAME POWERSHELL(REPLACE(REPLACE(REPLACE(_script, "$$formName$$", formName), "$$iconData$$", iconData), "$$tooltip$$", tooltip), TRUE) RESULT = GETID(formName, , wait) SENDSTR(RESULT, STATUS(GETID(GET_THISUWSC_WIN), ST_PROCESS), 4, TRUE) THREAD _Watch(RESULT, callback) FEND PROCEDURE ChangeTooltip(id, tip) SENDSTR(id, tip, 2, TRUE) FEND FUNCTION GetTooltip(id) RESULT = GETSTR(id, 2) FEND PROCEDURE ChangeIcon(id, icon) SENDSTR(id, icon, 3, TRUE) FEND FUNCTION GetIcon(id) RESULT = GETSTR(id, 3) FEND PROCEDURE SampleCallBack(id, p) SELECT p CASE "Right" SELECT POPUPMENU(SPLIT("ChangeIcon ChangeTooltip Exit")) CASE 0 ChangeIcon(id, INPUT("icon?", GetIcon(id))) CASE 1 ChangeTooltip(id, INPUT("tooltip?", GetTooltip(id))) CASE 2 CTRLWIN(id, CLOSE) SELEND DEFAULT MSGBOX(p) SELEND FEND ENDMODULE
何故か、Process.Exitedイベントを使うとPowershellが動作を停止するという、、、。
しかたないので、タイマー監視。
追記 2014/11/28
Process.Exitedイベントの使い方がわかったので修正