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

  • 質問

  • 今作っているアプリケーションで Clipboard に格納させたテキストを、デスク
    トップ上の全く別なアプリケーション、例えばメモ帳などに、クリックしただ
    けで貼り付ける、というアプリケーションを試作しています。

     

    手順としては

     

    (1) 本アプリケーション上で文字列を選択しコピー (クリップボードに格納)
    (2) 文字列を貼り付ける外部アプリケーション上の TextBox をクリック
    (3) Ctrl-V をすることなく (2) のクリックだけで文字列をその TextBox に挿入

     

    と考えています。

     

    (2) において、貼り付けられないコントロールの選択は考えず、メモ帳や
    TextBox などの文字を挿入できる部分を必ず選択すると、今は限定しておきます。

     

    (3) は Alt との組み合わせ (Alt-Click) でも構いません。


    今まで作ってきたサンプルと違い、外部アプリケーション上のコントロールを
    どのようにして取得し、貼り付け処理をどのようにして行わせれば良いのか、
    見当が付いていません。

     

    お分かりの方がいらっしゃったら、お教え願えませんでしょうか。

     

    + 2007-12-28

    Alt-Click をホットキーにできるのかな?

    2007年12月28日 11:08

回答

  • 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 Sub

     

     

    WM_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 がなんなのか私にはわかりません。

    2007年12月29日 2:54

すべての返信

  • 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 Sub

     

     

    WM_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 がなんなのか私にはわかりません。

    2007年12月29日 2:54
  • ダッチ さん、お返事ありがとうございます。

     

    教えていただいた情報を解析中です。
    理解してサンプルを載せてみたいと思います。

     

    SendMessage() ですが、Platform SDK の情報になるのでしょうが、msdn2 で
    LParam, WParam の情報を見ようとすると見つかりませんね。winuser.h,
    windows.h をダウンロードして覗いてみましたが、不明のまま。重要じゃない
    のかもしれませんが、把握できていた方がすっきりしますので、調査中です。

    2007年12月29日 7:24
  • wParam, lParam の値は送るメッセージにより何の値を設定するかが変わってきます。

    今回の WM_PASTE メッセージの場合は wParam, lParam ともに 0 を送ります。つまり両方ともなにも使用しないということだと思います。この情報は「WinAPI Database for VB Programmer」こちらのサイトがら得ました。

     

    私もAPI 関連の詳しい情報がほしい場合がありますが、 msdn ライブラリからは見つけられないため Web 検索して探しています。

    2007年12月29日 8:46
  • ダッチ さんの情報を元に、年末散々資料を読み耽 (ふけ) っていましたが、
    元旦の深夜に無事出来上がりました。長かった。

     

    いろんな回り道をしながら、最終的には ダッチ さんの言ってた通りになって
    しまいました。まぁ、目的を持った回り道だったので、学習になりましたが。

     

    - 再現映像 (23秒)

     

    コードは次に。

    2008年1月6日 13:34
  • 以下を参考にマウスポインターの座標を追いかけるプログラムを作ります。

     

    これで 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

    2008年1月9日 10:42
  • - 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 外にマウスポインターが出ても座標が更新されます。

    2008年1月9日 10:45
  • 続いて、クリックしたらクリップボードの文字列がペーストされるようにします。

     

    ここで .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 とのにらめっこで結構学習になりました。

    2008年1月9日 10:53
  • まず C# のスレッドなのに VB でサンプルコードを書いてしまいました。ごめんなさい。

     

     custar さんからの引用
    以上で本目的は一応できたように見えますが、ここはこうした方がいい、
    ああした方がいいという助言があればお願いいたします。

    マウスフックの処理は詳しくないため何も言えませんが、
    今後のためにマウスフック処理をクラス化してみるというのはどうでしょうか。

     

    SetWindowsHookEx 関数でフックした後は、必ず UnhookWindowsHookEx 関数でフックプロシージャを削除(解除?)してやらなければいけないようです。


    例えば IDisposable インターフェイスを実装したマウスフッククラスを作成します。
    その Dispose メソッドで必ず UnhookWindowsHookEx 関数が呼ばれるようにするなどすれば、より使いやすくなると思います。また、マウスフッククラスを Component クラスを継承して作成すれば、フォームに貼り付けられるのでさらに使い勝手がよくなると思います。

     

    # 余談ですが画像や映像などが付いていると、状況を理解するのにとても助かりますね。

    2008年1月9日 13:35
  •  ダッチ さんからの引用

    C# のスレッドなのに VB でサンプルコードを書いてしまいました。ごめんなさい。

     

    全然問題ありませんでした。サンプルを多く見ていたら、取り敢えず VB も見
    れるようになりましたので。要は .net の共通部分だけしか見てません。VB の
    文法も分からなくはないので。

     

     ダッチ さんからの引用

    今後のためにマウスフック処理をクラス化してみるというのはどうでしょうか。

     

    将来を考えられば、自分の資産となるので、そうですね。

     

     ダッチ さんからの引用

    SetWindowsHookEx 関数でフックした後は、必ず UnhookWindowsHookEx 関数で
    フックプロシージャを削除(解除?)してやらなければいけないようです。

     

    動かすことのみに注意がいっていたので、後始末を忘れてしまっていますね。
    ご指摘ありがとうございます。

     

     ダッチ さんからの引用

    例えば IDisposable インターフェイスを実装したマウスフッククラスを作成し
    ます。その Dispose メソッドで必ず UnhookWindowsHookEx 関数が呼ばれるよ
    うにするなどすれば、より使いやすくなると思います。

     

    また、マウスフッククラスを Component クラスを継承して作成すれば、フォー
    ムに貼り付けられるのでさらに使い勝手がよくなると思います。

     

    了解。

     

     ダッチ さんからの引用

    画像や映像などが付いていると、状況を理解するのにとても助かりますね。

     

    ありがとうございます。face to face じゃないので、質問者ができる限り情報
    を提示しなきゃ応えられないだろう、と思って取り組んでます。

     

    いたずら防止のために承認作業を挟んでもいいので、フォーラム上に画像や表、
    動画を載せられたら一覧性もあり、見やすいのですが。そこら辺の注文は沢山
    あります。

    2008年1月9日 14:45
  • 他には,

    WindowFromPoint の戻り値をチェックしたり,PostMessage にしたりなど。

     

    あと,

    アンマネジドのハンドルリソースは,

    クラスにラップする場合は,IDisposable で利用者に任せるのでなく,

    SafeHandle系の自作クラス で,ハンドルをラップして,

    フックラップクラスのメンバにするのが 2.0 以降の流儀でしょうね。

    2008年1月9日 15:02
  •  yayadon さんからの引用

    WindowFromPoint の戻り値をチェックしたり,PostMessage にしたりなど。

     

    戻り値のチェック ......まずはそうする目的を調べます。
    PostMessage ........そんなの出てきましたね、msdn 中に。

     

     yayadon さんからの引用

    アンマネジドのハンドルリソースは,
    クラスにラップする場合は,IDisposable で利用者に任せるのでなく,
    SafeHandle系の自作クラス で,ハンドルをラップして,
    フックラップクラスのメンバにするのが 2.0 以降の流儀でしょうね。

     

    うぅぅ、新たな単語が出てきました。
    ゆっくりじっくり msdn を眺めつつ解析しなきゃ。


    yayadon さん、ご忠告ありがとうございます。

    2008年1月9日 15:16