追記
記事を書いたものの、Vista以降は別の方法(AddClipboardFormatListener
)のほうが安定する(チェイン管理をOSがやってくれる)ようである。
クリップボードの監視
→ https://gist.github.com/glombard/7986317
概略
Windows APIであるSetClipboardViewer
とChangeClipboardChain
を使ってクリップボード監視チェインへの登録・削除をし、
WindowメッセージWM_DRAWCLIPBOARD
とWM_CHANGECBCHAIN
を処理して、クリップボードの変更通知受け取りと、チェインの維持を行う。
キーボードフックとかに近い構造のようだが、チェインの維持をしないといけない点が異なる。
サンプルコード
参考サイトの1つ目をベースに作成。1
文字列をクリップボードにコピーするたびに、コンソールにその文字列を出力します。
usingSystem;usingSystem.Windows.Forms;usingSystem.Runtime.InteropServices;usingSystem.Security.Permissions;publicclassClipboardEventArgs:EventArgs{privatestringtext;publicstringText{get{returnthis.text;}}publicClipboardEventArgs(stringstr){this.text=str;}}publicdelegatevoidcbEventHandler(objectsender,ClipboardEventArgsev);[PermissionSet(SecurityAction.Demand,Name="FullTrust")]internalclassMyClipboardListener:NativeWindow{classNativeMethods{[DllImport("user32")]publicstaticexternIntPtrSetClipboardViewer(IntPtrhWndNewViewer);[DllImport("user32")]publicstaticexternboolChangeClipboardChain(IntPtrhWndRemove,IntPtrhWndNewNext);[DllImport("user32")]publicexternstaticintSendMessage(IntPtrhWnd,intMsg,IntPtrwParam,IntPtrlParam);}privateconstintWM_DRAWCLIPBOARD=0x0308;privateconstintWM_CHANGECBCHAIN=0x030D;privateIntPtrnextHandle;privateIntPtrnwHandle;//private Form parent;publiceventcbEventHandlerClipboardHandler;publicMyClipboardListener(Formf){f.HandleCreated+=OnHandleCreated;f.HandleDestroyed+=OnHandleDestroyed;//this.parent = f;}internalvoidOnHandleCreated(objectsender,EventArgse){AssignHandle(((Form)sender).Handle);// NativeWindowクラスへのForm登録(メッセージフック開始)nwHandle=this.Handle;nextHandle=NativeMethods.SetClipboardViewer(this.Handle);// クリップボードチェインに登録}internalvoidOnHandleDestroyed(objectsender,EventArgse){NativeMethods.ChangeClipboardChain(this.Handle,nextHandle);// クリップボードチェインから削除ReleaseHandle();// NativeWindowクラスの後始末(Formに対してのメッセージフック解除)}// ハンドル変更されるシーンがあるか不明。//override void OnHandleChange()//{// ChangeClipboardChain(nwHandle, nextHandle); // クリップボードチェインから削除// nwHandle = this.Handle;// nextHandle = SetClipboardViewer(this.Handle); // クリップボードチェインに登録//}protectedoverridevoidWndProc(refMessagemsg){switch(msg.Msg){caseWM_DRAWCLIPBOARD:if(Clipboard.ContainsText()){// Note: ここを変更すれば、テキスト以外も通知可能// クリップボードの内容がテキストの場合のみif(ClipboardHandler!=null){// クリップボードの内容を取得してハンドラを呼び出すClipboardHandler(this,newClipboardEventArgs(Clipboard.GetText()));}}if(nextHandle!=IntPtr.Zero){NativeMethods.SendMessage(nextHandle,msg.Msg,msg.WParam,msg.LParam);}break;// クリップボード・ビューア・チェーンが更新されたcaseWM_CHANGECBCHAIN:if(msg.WParam==nextHandle){nextHandle=msg.LParam;}elseif(nextHandle!=IntPtr.Zero){NativeMethods.SendMessage(nextHandle,msg.Msg,msg.WParam,msg.LParam);}break;}base.WndProc(refmsg);}}publicclassClipboardMonitorTest:Form{MyClipboardListenerviewer;publicClipboardMonitorTest(){viewer=newMyClipboardListener(this);viewer.ClipboardHandler+=this.OnClipBoardChanged;// イベントハンドラを登録}// クリップボードにテキストがコピーされると呼び出されるprivatevoidOnClipBoardChanged(objectsender,ClipboardEventArgsargs){Console.WriteLine(args.Text);}[STAThread]staticvoidMain(string[]args){Application.Run(newClipboardMonitorTest());}}
注意点
試してみたところでは、SetClipboardViewer
に登録すると、ユーザーがクリップボードにコピーする操作をしていなくても、クリップボードの更新通知(WM_DRAWCLIPBOARD
)が来るようである。
参考サイト
- .NET TIPS クリップボードの内容をリアルタイムに取得するには?[C#、VB] - @IT
- クリップボードビューアを作る - Wisdom Soft・・・WindowsAPIの解説
- NativeWindowクラス - Microsoft Docs
そのままだと64bit環境でハマる可能性あり。
IntPtr
をint
にキャストして0
と比較しているとこは、int
キャストをやめてIntPtr.Zero
との比較に修正した。 ↩