SynchronizingObject
SynchronizingObject on powetshell
Start-ProcessのExitedイベントの終了処理で、wpfのLabel.Contentを変更したい。
それだけ。
$proc = Start-Process "poi.bat" -passThru $ev = Register-ObjectEvent $proc -EventName Exited -Action { AddText("hoge") } function AddText($moji){ $label.Content+=$moji} $label = New-Object System.windows.controls.label
だがしかし、火の粉が降りかかる。
イベントが遅い
イベントが出るが、プロセスExit時にlabelの内容が変わらず、unregister-event時にlabelが変わっている様な。
また、ISEでステップ実行すると、プロセス終了時にlabelの内容が変わる場合がある。
イベントハンドラの実行時刻とイベントの発生時刻$e.TimeGeneratedを比べると大きくズレていた。
スレッドが違う?
非同期実行が問題の可能性があるので、$procのSynchronizingObjectにwpfのコントロールを設定しようとするが。
型変換に失敗
Windows.FormsではformなどをSynchronizingObjectに設定できるが、wpfでは型変換に失敗する。
IsynchronizeInvokeが必要だが、WPFでこれを実装しているクラスはない。
次…
と言う事で次の策を検討。
- 無理無理ISync…を実装
- ハンドラでDispatcher経由で実行
ここでは、2. を採用するとして、
- uiスレッドからDispatcherをハンドラに渡す。
- Debug用にDispatcher の前後にWrite-Host
- 渡されたDispatcherを使って、ハンドラがUIを操作。
Dispatcherを試す前に
$proc.EnableRaisingEvent について。
Start-Processの直後はfalse.但し、Register-ObjectEvent後はTrueに変化。此奴が原因ではない模様。
また、wpfとは無関係な下記の手順ではプロセス終了に合わせてイベント発生。
- $proc=Start-Proces cmd -PassThru
- Register-ObjectEvent $proc -EventName Exited -action { write-host “hoa” }
- cmd終了
- “hoa” がコンソールに表示
この場合はうまくいったがwpfだと如何に。
さて、ここでDispatcher登場
結果は少し後で。
とは言え、どのスレッドのDispatcherを使うのかが問題。UIスレッドのDispatcherを別スレッドに渡すとするなら、Register-ObjectEventの-MessageDataで渡すか、グローバルを使うか。
https://msdn.microsoft.com/ja-jp/library/cc647500(v=vs.110).aspx
によると、
In WPF, only the thread that created a DispatcherObject may access that object. For example, a background thread that is spun off from the main UI thread cannot update the contents of a Button that was created on the UI thread. In order for the background thread to access the Content property of the Button, the background thread must delegate the work to the Dispatcher associated with the UI thread. This is accomplished by using either Invoke or BeginInvoke.Invoke is synchronous and BeginInvoke is asynchronous. The operation is added to the event queue of the Dispatcher at the specified DispatcherPriority.
この中の一節。"Dispatcher associated with the UI thread.“を使えとあるので、UIスレッドのDispatcherを使う必要があると読める。どうやって渡すかが課題。
In order for the background thread to access the Content property of the Button, the background thread must delegate the work to the Dispatcher associated with the UI thread.
まずはコード、その一。クラスのDispatcher
$proc = Start-Process "poi.bat" -passThru $ev = Register-ObjectEvent $proc -EventName Exited -Action { AddText("hoge") } function AddText($moji){ [System.windows.Threading.Dispatcher]::BeginInvoke({ $label.Content+=$moji}) } $label = New-Object System.windows.controls.label
その二
UIスレッドのDispatcher をMessageDataを使ってハンドラに引き渡し、ハンドラが本Dispatcher を使用。
$proc = Start-Process "poi.bat" -passThru $ev = Register-ObjectEvent $proc -EventName Exited -Action { AddText("hoge", $event.MessageData) } -MessageData [system.windows.threading.Dispatcher]::CurrentDispatcher function AddText($moji, $dp){ $dp.BeginInvoke({ $label.Content+=$moji}) } $label = New-Object System.windows.controls.label
結局、PowerShellでは
Dispatcherにスクリプトブロックを渡すと、delegate型への変換不可の例外発生。スクリプトブロックは動的メソッドらしく変換できないらしい。C#で書けば問題ないのだが、残念ながら今はPowershellがターゲット。つまり、再びの失敗。
そう言えば、$e.TimeGenerated
の発生時刻とハンドラ実行時刻がズレていた、という事は、真の問題はイベント発生後、直ぐにはハンドラが実行されないこと。ならば、UIスレッドでGet-Eventすれば良い?
当たり!
イベント発生後、ハンドラ実行前にPowershell のGet-Eventを実行するとヒョコッとハンドラが実行され、label.Contentにメッセージが追加された。だがしかし、ハンドラでlabelを書き換えており、ハンドラが非同期実行であれば、上手く行ったのは偶然の可能性がある。運が悪ければ非同期実行の罠にはまる。
仕方ない、インチキするしか
まさにインチキなのだが、
- グローバル変数/オブジェクトで非同期プロセスの結果を受け渡す。
ここでは結果をWpfのタイマーDispatcherTimerを使う。完全修飾名はSystem.Windows.Threading.DispatcherTimer。引き渡すデータはDispatcherTimer のTagを使う。非同期のExitedハンドラでTagにデータを登録し、UIスレッドのTimerハンドラで引き取りlabelを更新。
非同期スレッド用のFIFO/queueなどがあればそれでも良いが、上記で動いたので、一旦締め。
暇があればWPFで使えるqueueを探すか、Dispatcher をもう少し深掘りしてみるともっと良い解決策が見つかるかもしれない。
以降は少し前の日記/流れが悪いので、お蔵入り。
Start-Processで生成した[System.Diagnostics.Process]のインスタンスのSynchronizingObjectプロパティにWpfのコンポーネントを設定できない。設定しようとすると、IsynchronizeInvokeに変換できないとエラーになる。WPFには実装しているクラスがないらしい。むむ。
- $menuitem
- $window
- Dispatcher
のどれもダメ。
下記のソースでは、Windows.Forms.Formは大丈夫な模様。
そもそもの問題はprocessのexited eventで、Labelの文字を変更するアクションを設定したものの、process終了時すぐには書き換えが発生せず、イベントハンドラクリア時にLabelが書き換わる。
WPFのスレッド処理が今一理解できていない。もっとシンプルなカーネルとかセマフォとかを見つけられない所が、まだ浅いと言うことか?
#FileWatchSample.ps1 param ( [string]$Directory = "D:\", [switch]$IncludeSubDirectory = $true ) #制御用フォーム準備 $label = New-Object -TypeName "System.Windows.Forms.Label" $label.Text = "監視終了時は、このフォームを閉じてください。" $label.Size = New-Object -TypeName "System.Drawing.Size" ` -ArgumentList @( $label.PreferredWidth, $label.PreferredHeight ) $form = New-Object -TypeName "System.Windows.Forms.Form" $form.Text = "ファイル監視中" $form.AutoSize = $true $form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::Fixed3D [void]$form.Controls.Add( $label ) #FileSystemWatcher準備 $fileSystemWatcher = New-Object -TypeName "System.IO.FileSystemWatcher" ` -ArgumentList @( $Directory ) $fileSystemWatcher.IncludeSubdirectories = $IncludeSubDirectory $fileSystemWatcher.NotifyFilter = [System.IO.NotifyFilters]::CreationTime.value__ ` -bor [System.IO.NotifyFilters]::FileName.value__ $fileSystemWatcher.Filter = "*.*" $fileSystemWatcher.add_Changed( ` { Write-Host ( "{0} changed: {1}" -f [DateTime]::Now.ToString(), $_.FullPath ) } ) $fileSystemWatcher.add_Created( ` { Write-Host ( "{0} created: {1}" -f [DateTime]::Now.ToString(), $_.FullPath ) } ) $fileSystemWatcher.add_Deleted( ` { Write-Host ( "{0} deleted: {1}" -f [DateTime]::Now.ToString(), $_.FullPath ) } ) $fileSystemWatcher.add_Renamed( ` { Write-Host ( "{0} renamed: {1} => {2}" ` -f [DateTime]::Now.ToString(), $_.OldFullPath, $_.FullPath ) } ) $fileSystemWatcher.SynchronizingObject = $form #ファイル監視開始 $fileSystemWatcher.EnableRaisingEvents = $true #終了時はフォームを閉じること。 $form.ShowDialog() | Out-Null
参考
http://dobon.net/vb/dotnet/process/openfile.html
http://cyberboy6.blog.fc2.com/blog-entry-445.html
WPF eventargs
https://msdn.microsoft.com/ja-jp/library/system.eventargs(v=vs.110).aspx
teraterm log auto close
teraterm log auto close
logautoclosemode 1 logopen 'D:\teraterm_sample.log' 0 0 ; do something... end ``` # 参考 https://ttssh2.osdn.jp/manual/ja/macro/command/logautoclosemode.html
git 作業ディレクトリの変更を戻す
作業ディレクトリの変更を戻す by git
作業したが変更を戻したい。
git checkout .
これでカレント作業ディレクトリの変更を戻してくれるが、新規に作成したファイルは削除してくれず、手作業で削除する。なお、飽くまで作業ディレクトリの内容なので親ディレクトリの内容は元に戻さない。親まで戻したい場合は、親ディレクトリにてgit checkout .
マージ失敗時に元に戻す。
git merge tanin-branch
此奴は失敗だなぁと思ったら
git reset --hard ORIG_HEAD
参考
git checkout .
http://qiita.com/yukikimoto/items/2ec0cf5bfe614b32cf2f
git reset –hard ORIG_HEAD
http://qiita.com/yukikimoto/items/429817ba98994d64a5dd
gut reset
https://www.atlassian.com/ja/git/tutorial/undoing-changes#!reset
WPF Clipboard
Clipboad on wpf by powershell
PowerShellでWPFのクリップボードを使う。
[Windows.Clipboard] のGetText(), SetText()を使う。
$get = [Windows.Clipboard]::GetText()
[Windows.Clipboard]::SetText("oha")
[Windows.Clipboard]::Clear()
但し、下記が必要
Add-Type -AssemblyName PresentationCore
参考
https://learn-powershell.net/2014/07/24/building-a-clipboard-history-viewer-using-powershell/
こちらはWindows.Forms/clip.exe
https://letspowershell.blogspot.jp/2016/03/powershell_12.html?m=1
Windows forms
http://stackoverflow.com/questions/13127578/pipe-output-to-the-clipboard-using-powershell
http://d.hatena.ne.jp/newpops/touch/20070120/p1
http://stackoverflow.com/questions/13127578/pipe-output-to-the-clipboard-using-powershell
http://stackoverflow.com/questions/34724412/text-selection-on-textblock-and-label-wpf
.Netで作るPowerShellコンソール
Poweshellコンソール by wpf
まさに欲しかったもの。PowerShellコンソールをWPFに組み込む。
肝はPowershll.Create()でPowershell を生成する事。
using (var powershell = PowerShell.Create()) { powershell.AddCommand("Get-ChildItem"); powershell.AddCommand("Out-String"); foreach (var result in powershell.Invoke()) { Console.WriteLine(result); } }
参考
Exited Event非同期対応
Start-ProcessのExitedEvent Thread
Strart-ProcessのExited EventはデフォルトではSystem thread pool から実行される。UI系とは異なるため、UI系を制御すると問題が発生しやすい。
そのため、Formなどで使用するためにSynchronizingObject にUIオブジェクトを設定する。
this.process1.StartInfo.Domain = ""; this.process1.StartInfo.LoadUserProfile = false; this.process1.StartInfo.Password = null; this.process1.StartInfo.StandardErrorEncoding = null; this.process1.StartInfo.StandardOutputEncoding = null; this.process1.StartInfo.UserName = ""; this.process1.SynchronizingObject = this;
使用例
ここでは、ボタン自身をSynchronized Objectに設定している。
private MyButton button1; private void button1_Click(object sender, System.EventArgs e) { Process myProcess = new Process(); ProcessStartInfo myProcessStartInfo= new ProcessStartInfo("mspaint"); myProcess.StartInfo = myProcessStartInfo; myProcess.Start(); myProcess.Exited += new EventHandler(MyProcessExited); // Set 'EnableRaisingEvents' to true, to raise 'Exited' event when process is terminated. myProcess.EnableRaisingEvents = true; // Set method handling the exited event to be called ; // on the same thread on which MyButton was created. myProcess.SynchronizingObject = button1; // ここで自身を設定 MessageBox.Show("Waiting for the process 'mspaint' to exit...."); myProcess.WaitForExit(); myProcess.Close(); } private void MyProcessExited(Object source, EventArgs e) { MessageBox.Show("The process has exited."); } } public class MyButton:Button { }
参考
http://dobon.net/vb/dotnet/process/openfile.html
毛並みが異なるが、Window.Formsのイベント
http://d.hatena.ne.jp/newpops/touch/20070120/p1
普通のイベント
http://blog.okazuki.jp/entry/2014/08/22/211021
System.Windows.Threading.DispatcherTimer in WindowsBase assembly