DOBON.NET DOBON.NETプログラミング掲示板過去ログ

他アプリのキャレット位置を取得したい

環境/言語:[Windows 7 64bit C#.NET (2013)]
分類:[.NET]

はじめまして。
普段はVBA使いなんですが、最近C#を触り始めたものです。

他アプリのキャレット位置をAPIの GetCaretPos で取得しようとしているのですが、
下記のコードでは、0, 0 しか返りません。

[DllImport("user32.dll", EntryPoint="GetCaretPos")]
static extern bool GetCaretPos(ref Point lpPoint);

'タイマーイベント
Point p = new Point();
GetCaretPos(ref p);
label1.Text = String.Format("Caret position: {0}, {1}", p.X, p.Y) + Environment.NewLine + label1.Text;

何かコードで不足しているものがあるのでしょうか。
それともC#からでは他アプリの情報を取得するのは不可能ということでしょうか。

アドバイスよろしくお願いします。
対象が別プロセスの場合、GetCaretPos関数を呼び出す前に、AttachThreadInput関数を呼び出す必要があるようです。
この関数には呼び出し元のスレッドのスレッドIDと、ターゲットのウィンドウが属するスレッドのIDが必要です。
呼び出し元のスレッドIDはGetCurrentThreadId関数、ターゲットのスレッドIDはウィンドウハンドルさえ見つかっていればGetWindowThreadProcessId関数で取得できます。
ターゲットのウィンドウハンドルはWindowFromPoint関数とかGetForegroundWindow関数とかで取得できるでしょう。

ただ、GetCaretPos関数はWindows標準のキャレットを使用している場合のみ有効です。
アプリケーションが独自に矩形を点滅させているだけのような場合、外部から取得するのは(GetCaretPosに限らず)不可能でしょう。
たとえばブラウザとか、あるいはWPF製のコントロール類とか。
■No32472に返信(Hongliangさんの記事)
> たとえばブラウザとか、あるいはWPF製のコントロール類とか。

その場合は、IAccessible.accLocation あたりでどうでしょう。(未確認)

http://msdn.microsoft.com/en-us/library/windows/desktop/dd318010.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/dd318472.aspx


■No32471に返信(hatenaさんの記事)
> [DllImport("user32.dll", EntryPoint="GetCaretPos")]
> static extern bool GetCaretPos(ref Point lpPoint);

出力引数なので、本来は(ref ではなく)out かな。
2014/07/17(Thu) 19:48:16 編集(投稿者)

Hongliangさん、魔界の仮面弁士さん、返信ありがとうございます。

アドバイスを参考に下記のようにしてみたのですが、相変わらず、0、0 しか返りません。
メモ帳やエディターなどをアクティブにしてためしてみました。

どこか間違い、不足がありますでしょうか。

  [DllImport("user32.dll", EntryPoint = "GetCaretPos")]
  static extern bool GetCaretPos(out Point lpPoint);


  [DllImport("user32.dll")]
  static extern IntPtr GetForegroundWindow();

  [DllImport("user32.dll")]
  static extern IntPtr AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);

  [DllImport("user32.dll")]
  static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

  [DllImport("kernel32.dll")]
  static extern uint GetCurrentThreadId();

  private void timer1_Tick(object sender, EventArgs e)
  {
    IntPtr hWnd = GetForegroundWindow();
    uint toid;
    GetWindowThreadProcessId(hWnd, out toid);

    uint thisid = GetCurrentThreadId();

    label1.Text = DateTime.Now.ToString("HH時mm分ss秒") + String.Format("ThreadId: {0}, to {1}", thisid, toid) + Environment.NewLine + label1.Text;

    AttachThreadInput(thisid, toid, true);

    Point p = new Point();
    GetCaretPos(out p);
    label1.Text = String.Format("Caret position: {0}, {1}", p.X, p.Y) + Environment.NewLine + label1.Text;
  }
2014/07/17(Thu) 20:57:16 編集(投稿者)

■No32474に返信(hatenaさんの記事)
> メモ帳やエディターなどをアクティブにしてためしてみました。
GetCaret はもともと、自アプリ上に対して使うものです。
Form 上に TextBox を貼って、動作を確認してみてください。


> どこか間違い、不足がありますでしょうか。
スレッドID とプロセスID を混同している上に、
スレッドのデタッチ作業が抜けているように見えます。

[DllImport("user32.dll", EntryPoint = "GetCaretPos")]
static extern bool GetCaretPos(out Point lpPoint);

[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();

[DllImport("user32.dll")]
static extern IntPtr AttachThreadInput(IntPtr idAttach, IntPtr idAttachTo, bool fAttach);

[DllImport("user32.dll")]
static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId);

[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentThreadId();

private void timer1_Tick(object sender, EventArgs e)
{
  IntPtr hWnd = GetForegroundWindow();

  IntPtr current = GetCurrentThreadId();
  IntPtr target = GetWindowThreadProcessId(hWnd, IntPtr.Zero);

  label1.Text = DateTime.Now.ToString("HH時mm分ss秒") + String.Format("ThreadId: {0} to {1}", current, target);

  Point p;
  AttachThreadInput(current, target, true);
  GetCaretPos(out p);
  AttachThreadInput(current, target, false);

  label1.Text = String.Format("Caret position: {0}\r\n{1}", p, label1.Text);
}
■No32475に返信(魔界の仮面弁士さんの記事)
> スレッドID とプロセスID を混同している上に、
> スレッドのデタッチ作業が抜けているように見えます。

提示のコードでうまく取得出来ました。
ありがとうございました。
C# は触り始めたばかりなので、
見よう見まねでコーディングしていますので、
まだまだ勉強不足を実感します。

入力支援ソフトのようなものを作成しようとしているのですが、
キャレット位置にウィンドウを表示させたいと思っているのです。

GetCaretPosで取得できる座標はウィンドウを基準としているようなので、
ClientToScreenでスクリーン基準に変換すればいいだろうと試してみると、
いい線まではいくのですが、少しずれます。
GetCaretPosはアプリウィンドウではなく入力域を基準にしているようなので、
GetForegroundWindow関数のウィンドウハンドルではなく、
GetFocus関数のハンドルでClientToScreen変換するとバッチリでした。

[DllImport("user32.dll", EntryPoint = "GetCaretPos")]
static extern bool GetCaretPos(out Point lpPoint);

[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();

[DllImport("user32.dll")]
static extern IntPtr AttachThreadInput(IntPtr idAttach, IntPtr idAttachTo, bool fAttach);

[DllImport("user32.dll")]
static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId);

[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentThreadId();

[DllImport("user32.dll")]
static extern IntPtr GetFocus();

[DllImport("user32.dll")]
static extern bool ClientToScreen(IntPtr hwnd, out Point lpPoint);


private void timer1_Tick(object sender, EventArgs e)
{
  IntPtr hWnd = GetForegroundWindow();

  IntPtr current = GetCurrentThreadId();
  IntPtr target = GetWindowThreadProcessId(hWnd, IntPtr.Zero);

  Point p;
  AttachThreadInput(current, target, true);
  GetCaretPos(out p);

  IntPtr fWnd = GetFocus();
  ClientToScreen(fWnd, out p);

  AttachThreadInput(current, target, false);

  this.SetDesktopLocation(p.X, p.Y);
}

このコードでアクティブなアプリのキャレット位置にフォームが移動します。

せっかくここまで来たのでできれば、キャレットの上部ではなく下部に移動させたいと思います。
キャレットの高さが取得できればいいのですが、これは可能でしょうか。
■No32477に返信(hatenaさんの記事)
> 入力支援ソフトのようなものを作成しようとしているのですが、
入力支援というと、ユーザー補助APIの出番?


> キャレット位置にウィンドウを表示させたいと思っているのです。
http://download.microsoft.com/download/activaxs/Install/2.0/W98NT42KMeXP/EN-US/inspect32.exe

上記ツール(Inspect)において、ツールバー上にあるボタンの:
 ・左から5番目、Iビームアイコン(Watch Caret)を On にする
 ・左から9番目、上下三角アイコン(Show Caret Hilight)を On にする
とすると、キャレット位置に三角アイコンが表示されるようになり、
ツール側にキャレットの大きさが表示されますが、これと同じような機能でしょうか。

だとすれば、No32473 でも少し触れていますが、MSAA(Microsoft Active Accessibility)について
調べてみて下さい。accLocation にて、キャレットの Left/Top/Width/Height が
得られると思います――多分。


また、関連で UI Automation についても調査しておくと良いかもしれません。

上記 URL の inspect32.exe は MSAA ベースのもの、また、
下記 URL で紹介されている Inspect.exe は MSAA / UI Automation 両対応です。
http://blogs.msdn.com/b/japan_platform_sdkwindows_sdk_support_team_blog/archive/2011/05/26/ui-automation.aspx
■No32480に返信(魔界の仮面弁士さんの記事)
> http://download.microsoft.com/download/activaxs/Install/2.0/W98NT42KMeXP/EN-US/inspect32.exe
>
> 上記ツール(Inspect)において、ツールバー上にあるボタンの:
>  ・左から5番目、Iビームアイコン(Watch Caret)を On にする
>  ・左から9番目、上下三角アイコン(Show Caret Hilight)を On にする
> とすると、キャレット位置に三角アイコンが表示されるようになり、
> ツール側にキャレットの大きさが表示されますが、これと同じような機能でしょうか。

そうですね。
まさにそれです。

> だとすれば、No32473 でも少し触れていますが、MSAA(Microsoft Active Accessibility)について
> 調べてみて下さい。accLocation にて、キャレットの Left/Top/Width/Height が
> 得られると思います――多分。

いろいろ調べてみました。
私にはかなり敷居が高そうですが、
チャレンジしてみます。

> また、関連で UI Automation についても調査しておくと良いかもしれません。

いろいろ有益な情報、ありがとうございます。

こちらも調べてみます。

とりあえず、何を使えばよいのか、めどがついたので、これで解決とさせていただきます。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板