トップ回答者
クリックしただけで別アプリケーション上の TextBox に文字列をペーストしたい

質問
-
今作っているアプリケーションで Clipboard に格納させたテキストを、デスク
トップ上の全く別なアプリケーション、例えばメモ帳などに、クリックしただ
けで貼り付ける、というアプリケーションを試作しています。手順としては
(1) 本アプリケーション上で文字列を選択しコピー (クリップボードに格納)
(2) 文字列を貼り付ける外部アプリケーション上の TextBox をクリック
(3) Ctrl-V をすることなく (2) のクリックだけで文字列をその TextBox に挿入と考えています。
(2) において、貼り付けられないコントロールの選択は考えず、メモ帳や
TextBox などの文字を挿入できる部分を必ず選択すると、今は限定しておきます。(3) は Alt との組み合わせ (Alt-Click) でも構いません。
今まで作ってきたサンプルと違い、外部アプリケーション上のコントロールを
どのようにして取得し、貼り付け処理をどのようにして行わせれば良いのか、
見当が付いていません。お分かりの方がいらっしゃったら、お教え願えませんでしょうか。
+ 2007-12-28
Alt-Click をホットキーにできるのかな?
回答
-
custar さん、こんにちは
ダッチです。まず、特定のアプリケーションの TextBox に対してクリップボード上のデータを貼り付けるには SendMessage 関数を使用して WM_PASTE メッセージを送れば可能です。
使い方はこのようになります。Button1 と TextBox1 をフォーム上に配置してある状態で動きます。
SendMessage 関数のサンプル<SYSTEM.RUNTIME.INTEROPSERVICES.DLLIMPORT("user32.dll", CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _
Private Shared Function SendMessage( _
ByVal hWnd As IntPtr, _
ByVal Msg As Integer, _
ByVal wParam As Integer, _
ByVal lParam As Integer) As Integer
End Function
Private Const WM_PASTE As Integer = &H302
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
SendMessage(Me.TextBox1.Handle, WM_PASTE, 0, 0)
End SubWM_PASTE メッセージにより特定のウィンドウに対してクリップボードのデータを貼り付けます。ここでいうウィンドウとはフォームやコントロールなどの、ウィンドウハンドルを持っているものです。
次に特定の TextBox のウィンドウハンドルをどのように取得するかですが、WindowFromPoint 関数を使用すれば特定の座標上にあるウィンドウのハンドルを取得する事が出来ます。
WindowFromPoint 関数のサンプル<System.Runtime.InteropServices.DllImport("user32.dll", CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _
Private Shared Function WindowFromPoint( _
ByVal lpPoint As POINT) As IntPtr
End Function
Private Structure POINT
Public X As Integer
Public Y As Integer
End Structure
Private Sub なんらかのタイミングでTextBoxの座標にマウスがあるときに処理を動かす()
Dim p As POINT
p.X = 100 ' TextBox のある X 座標
p.Y = 100 ' TextBox のある Y 座標
Dim hwnd As IntPtr = WindowFromPoint(p)
SendMessage(hwnd, WM_PASTE, 0, 0)
End Subあとはどのタイミングで座標上のウィンドウハンドルを取得するかですが、外部アプリケーションをクリックしただけではクリックイベントが発生しないため、座標を取得する事はできません。(マウスをフックすればできると思いますが大変だと思います)
なので、自分のアプリケーションからドラッグして特定の TextBox にドロップしたタイミングで、その座標のウィンドウハンドルを取得し、そのウィンドウハンドルに対して SendMessage で WM_PASTE を送るなど、他のやり方も考慮に入れたほうがいいかもしれません。 Alt + Click がなんなのか私にはわかりません。
すべての返信
-
custar さん、こんにちは
ダッチです。まず、特定のアプリケーションの TextBox に対してクリップボード上のデータを貼り付けるには SendMessage 関数を使用して WM_PASTE メッセージを送れば可能です。
使い方はこのようになります。Button1 と TextBox1 をフォーム上に配置してある状態で動きます。
SendMessage 関数のサンプル<SYSTEM.RUNTIME.INTEROPSERVICES.DLLIMPORT("user32.dll", CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _
Private Shared Function SendMessage( _
ByVal hWnd As IntPtr, _
ByVal Msg As Integer, _
ByVal wParam As Integer, _
ByVal lParam As Integer) As Integer
End Function
Private Const WM_PASTE As Integer = &H302
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
SendMessage(Me.TextBox1.Handle, WM_PASTE, 0, 0)
End SubWM_PASTE メッセージにより特定のウィンドウに対してクリップボードのデータを貼り付けます。ここでいうウィンドウとはフォームやコントロールなどの、ウィンドウハンドルを持っているものです。
次に特定の TextBox のウィンドウハンドルをどのように取得するかですが、WindowFromPoint 関数を使用すれば特定の座標上にあるウィンドウのハンドルを取得する事が出来ます。
WindowFromPoint 関数のサンプル<System.Runtime.InteropServices.DllImport("user32.dll", CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _
Private Shared Function WindowFromPoint( _
ByVal lpPoint As POINT) As IntPtr
End Function
Private Structure POINT
Public X As Integer
Public Y As Integer
End Structure
Private Sub なんらかのタイミングでTextBoxの座標にマウスがあるときに処理を動かす()
Dim p As POINT
p.X = 100 ' TextBox のある X 座標
p.Y = 100 ' TextBox のある Y 座標
Dim hwnd As IntPtr = WindowFromPoint(p)
SendMessage(hwnd, WM_PASTE, 0, 0)
End Subあとはどのタイミングで座標上のウィンドウハンドルを取得するかですが、外部アプリケーションをクリックしただけではクリックイベントが発生しないため、座標を取得する事はできません。(マウスをフックすればできると思いますが大変だと思います)
なので、自分のアプリケーションからドラッグして特定の TextBox にドロップしたタイミングで、その座標のウィンドウハンドルを取得し、そのウィンドウハンドルに対して SendMessage で WM_PASTE を送るなど、他のやり方も考慮に入れたほうがいいかもしれません。 Alt + Click がなんなのか私にはわかりません。 -
wParam, lParam の値は送るメッセージにより何の値を設定するかが変わってきます。
今回の WM_PASTE メッセージの場合は wParam, lParam ともに 0 を送ります。つまり両方ともなにも使用しないということだと思います。この情報は「WinAPI Database for VB Programmer」こちらのサイトがら得ました。
私もAPI 関連の詳しい情報がほしい場合がありますが、 msdn ライブラリからは見つけられないため Web 検索して探しています。
-
ダッチ さんの情報を元に、年末散々資料を読み耽 (ふけ) っていましたが、
元旦の深夜に無事出来上がりました。長かった。いろんな回り道をしながら、最終的には ダッチ さんの言ってた通りになって
しまいました。まぁ、目的を持った回り道だったので、学習になりましたが。コードは次に。
-
以下を参考にマウスポインターの座標を追いかけるプログラムを作ります。
これで Form がアクティブで、マウスポインターが Form 上にある間は、座標
が表示・更新され続けます。- [HOWTO] Visual C# .NET で Windows フックを設定する方法
コード ブロックpublic partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
static int hHook = 0;
public const int WH_MOUSE = 7;
HookProc MouseHookProcedure;[StructLayout(LayoutKind.Sequential)]
public class POINT
{
public int x;
public int y;
}[StructLayout(LayoutKind.Sequential)]
public class MouseHookStruct
{
public POINT pt;
public int hwnd;
public int wHitTestCode;
public int dwExtraInfo;
}[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);private void button1_Click(object sender, EventArgs e)
{
if (hHook == 0)
{
MouseHookProcedure = new HookProc(Form1.MouseHookProc);hHook = SetWindowsHookEx(
WH_MOUSE,
MouseHookProcedure,
(IntPtr)0,
AppDomain.GetCurrentThreadId());if (hHook == 0)
{
MessageBox.Show("SetWindowsHookEx Failed");
return;
}
button1.Text = "UnHook Windows Hook";
}
else
{
bool ret = UnhookWindowsHookEx(hHook);if (ret == false)
{
MessageBox.Show("UnhookWindowsHookEx Failed");
return;
}hHook = 0;
button1.Text = "Set Windows Hook";
this.Text = "Mouse Hook";
}
}public static int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
MouseHookStruct MyMouseHookStruct
= (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));if (nCode < 0)
{
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
else
{
String strCaption = String.Format(
"x = {0} y = {1}",
MyMouseHookStruct.pt.x.ToString("d"),
MyMouseHookStruct.pt.y.ToString("d"));Form tempForm = Form.ActiveForm;
tempForm.Text = strCaption;
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
}
}但し、警告が出ます。次で書き換えるので、ここは無視。
コード ブロック警告 1 'System.AppDomain.GetCurrentThreadId()' は古い形式です:
'AppDomain.GetCurrentThreadId has been deprecated
because it does not provide a stable Id
when managed threads are running on fibers
(aka lightweight threads).
To get a stable identifier for a managed thread,
use the ManagedThreadId property on Thread.
http://go.microsoft.com/fwlink/?linkid=14202'
C:\Documents and Settings\custar
\My Documents\Visual Studio 2008\Projects\WindowsFormsApplication1
\WindowsFormsApplication1\Form1.cs 60 11 WindowsFormsApplication1 -
- Hooks
- Install global hook without dll | CodeLibrary
- Stephen Toub : Low-Level Mouse Hook in C#
hook には一般的に dll が必要なんだそうですが、SetWindowsHook() codes と
して WH_MOUSE_LL を使えば、要らないそうです。WH_MOUSE_LL は winuser.h
にちゃんと存在していました。ということで該当箇所を書き換えます。まず以下を追加。
コード ブロックpublic const int WH_MOUSE_LL = 14;
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);更に変更
コード ブロックprivate void button1_Click(object sender, EventArgs e)
{
if (hHook == 0)
{
MouseHookProcedure = new HookProc(Form1.MouseHookProc);using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
hHook = SetWindowsHookEx(
WH_MOUSE_LL,
MouseHookProcedure,
GetModuleHandle(curModule.ModuleName),
0);
}if (hHook == 0)
以上で Form 外にマウスポインターが出ても座標が更新されます。
-
続いて、クリックしたらクリップボードの文字列がペーストされるようにします。
ここで .net からのギャップを感じました。.net だとコントロールを扱うの
ですが、ActiveForm から外れると、途端に別な扱い方をしなければならなくな
るのですね。ここで「ハンドル」の存在を知る。考えてみれば、デスクトップ上には .net で作られたアプリケーションばかり
ではありませんので。で、下記対比を眺めていくと、
- Microsoft Win32 と Microsoft .NET Framework API との対応
コード ブロック指定された点を含むウィンドウへのハンドルを取得
.NET Framework API ....... System.Windows.Forms.Form.GetChildAtPoint
Win32 関数 ............... WindowFromPointという .net で使えそうな api に対する windows api が示されていました。
これで貼り付けようとする部分の「ハンドル」を取得し、それに対して
SendMessage() を実行します。左クリックを捕まえるのと、ペーストのために以下を追加します。それぞれ
winuser.h から抜き出しています。コード ブロックpublic const int WM_LBUTTONDOWN = 0x0201;
public const int WM_PASTE = 0x0302;[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)]
public static extern IntPtr WindowFromPoint(POINT Point);
そして MouseHookProc() に左クリック時の処理を追加します。座標表示はフォー
ムがアクティブな時のみ有効なので、消しておきます。コード ブロックif (nCode < 0)
{
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
else
{
if (WM_LBUTTONDOWN == (int)wParam)
{
IntPtr hWnd = WindowFromPoint(MyMouseHookStruct.pt);
SendMessage(hWnd, WM_PASTE, IntPtr.Zero, IntPtr.Zero);
}return CallNextHookEx(hHook, nCode, wParam, lParam);
}同時に、POINT を class から struct に変更していないと動きません。
コード ブロック[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x;
public int y;
}
以上で本目的は一応できたように見えますが、ここはこうした方がいい、
ああした方がいいという助言があればお願いいたします。msdn とのにらめっこで結構学習になりました。
-
まず C# のスレッドなのに VB でサンプルコードを書いてしまいました。ごめんなさい。
custar さんからの引用 以上で本目的は一応できたように見えますが、ここはこうした方がいい、
ああした方がいいという助言があればお願いいたします。
マウスフックの処理は詳しくないため何も言えませんが、
今後のためにマウスフック処理をクラス化してみるというのはどうでしょうか。SetWindowsHookEx 関数でフックした後は、必ず UnhookWindowsHookEx 関数でフックプロシージャを削除(解除?)してやらなければいけないようです。
例えば IDisposable インターフェイスを実装したマウスフッククラスを作成します。
その Dispose メソッドで必ず UnhookWindowsHookEx 関数が呼ばれるようにするなどすれば、より使いやすくなると思います。また、マウスフッククラスを Component クラスを継承して作成すれば、フォームに貼り付けられるのでさらに使い勝手がよくなると思います。# 余談ですが画像や映像などが付いていると、状況を理解するのにとても助かりますね。
-
ダッチ さんからの引用
C# のスレッドなのに VB でサンプルコードを書いてしまいました。ごめんなさい。全然問題ありませんでした。サンプルを多く見ていたら、取り敢えず VB も見
れるようになりましたので。要は .net の共通部分だけしか見てません。VB の
文法も分からなくはないので。ダッチ さんからの引用
今後のためにマウスフック処理をクラス化してみるというのはどうでしょうか。将来を考えられば、自分の資産となるので、そうですね。
ダッチ さんからの引用
SetWindowsHookEx 関数でフックした後は、必ず UnhookWindowsHookEx 関数で
フックプロシージャを削除(解除?)してやらなければいけないようです。動かすことのみに注意がいっていたので、後始末を忘れてしまっていますね。
ご指摘ありがとうございます。ダッチ さんからの引用
例えば IDisposable インターフェイスを実装したマウスフッククラスを作成し
ます。その Dispose メソッドで必ず UnhookWindowsHookEx 関数が呼ばれるよ
うにするなどすれば、より使いやすくなると思います。また、マウスフッククラスを Component クラスを継承して作成すれば、フォー
ムに貼り付けられるのでさらに使い勝手がよくなると思います。了解。
ダッチ さんからの引用
画像や映像などが付いていると、状況を理解するのにとても助かりますね。ありがとうございます。face to face じゃないので、質問者ができる限り情報
を提示しなきゃ応えられないだろう、と思って取り組んでます。いたずら防止のために承認作業を挟んでもいいので、フォーラム上に画像や表、
動画を載せられたら一覧性もあり、見やすいのですが。そこら辺の注文は沢山
あります。 -
yayadon さんからの引用
WindowFromPoint の戻り値をチェックしたり,PostMessage にしたりなど。戻り値のチェック ......まずはそうする目的を調べます。
PostMessage ........そんなの出てきましたね、msdn 中に。yayadon さんからの引用
アンマネジドのハンドルリソースは,
クラスにラップする場合は,IDisposable で利用者に任せるのでなく,
SafeHandle系の自作クラス で,ハンドルをラップして,
フックラップクラスのメンバにするのが 2.0 以降の流儀でしょうね。うぅぅ、新たな単語が出てきました。
ゆっくりじっくり msdn を眺めつつ解析しなきゃ。
yayadon さん、ご忠告ありがとうございます。 -