如何通过鼠标单击将另一个应用程序的窗口句柄传递给Delphi

时间:2011-04-09 22:55:40

标签: delphi winapi mouse delphi-xe

如何通过用户选择窗口(可以是任何其他应用程序的窗口)通过单击鼠标来将窗口的句柄传递给Delphi。在我的Delphi应用程序中,我可以使用用户单击的按钮来启动此检测过程,以及在Delphi应用程序中显示单击窗口标题的标签。当用户满意时,他选择了正确的窗口,他可以点击我的Delphi应用程序中的按钮(这将是模态的)来停止选择过程,让我的应用程序开始向另一个窗口做它需要做什么...

3 个答案:

答案 0 :(得分:5)

评论中概述的用户STATUS_ACCESS_DENIED的方法可能是最简单的方法。我建议使用鼠标捕获而不是挂钩,因为它实现起来有点简单。

以下是对所涉及内容的更详细的概述:

首先要改变选择过程的工作方式。而不是让用户单击您的应用程序上的按钮来启动该过程,然后单击目标窗口,最后再次单击以确认;如果用户单击应用程序上的特定区域,然后拖动到目标窗口,然后在目标上方放开鼠标按钮,则实现起来要容易得多。这是因为Windows认为点击另一个应用程序属于该应用程序,您必须做额外的工作来拦截它。但是有一种简单的方法 - 称为鼠标捕获 - 来获取有关拖动/释放的信息,如果它只是点击你自己的应用程序

这也是Windows SDK Spy ++工具使用的方法;所以通过这种方式,你也可以与一个众所周知的工具保持一致。 (间谍++ here图片 - 注意对话框中的十字准线工具 - 这就是你点击并拖动到目标的东西。如果你以前没有这样做,强烈建议下载Windows SDK并使用这个工具;它也是一种非常有用的方式,可以看到其他应用程序如何构建得如Windows API学习工具一样出色。)

涉及的步骤:

  • 在您的应用中有一些控制功能可以响应鼠标按下事件(Win32 / C中的WM_LBUTTONDOWN,delphi中的OnMouseDown)。您可能希望在此处绘制十字准线图标或类似图标,以便用户知道点击的位置。
  • 当您关闭鼠标时,使用SetCapture“捕获”鼠标。这意味着控件将在鼠标移动时接收所有鼠标消息 - 直到用户释放按钮 - 即使它移出控件之外。
  • 将图标设置为十字光标,以便用户知道它们处于拖动模式
  • 当用户移动鼠标时,您将获得具有指针坐标的WM_MOUSEMOVE消息(Delphi中的OnMouseMove)。您需要使用ClientToScreen将这些转换为屏幕坐标,然后使用WindowFromPoint查找该点的窗口。 (请注意,这会在此时找到最里面的窗口,如果需要,可以从桌面窗口开始使用ChildWindowFromPoint来获取顶级窗口。)由您决定是否要在每次更新UI时鼠标在整个拖动过程中移动,或者只是在用户释放鼠标按钮时移动。
  • 当用户释放鼠标按钮时,您将获得WM_LBUTTONUP / OnMouseUp;在那个阶段,通过调用ReleaseCapture并将光标恢复到正常形状来包装。

请注意,您将在拖动过程中获得鼠标移动事件,并且如果用户恰好将鼠标指针移动到您的控件上,也可能是在去往其他控件的路上。分别告诉这两种情况的最简单方法是在控件中使用一个标记,当你将鼠标按下时设置,当你设置鼠标时清除它,并且只有在设置了该标志时才处理鼠标移动事件。

上面根据您从C / C ++调用的普通Win32 API描述了该过程;但看起来Delphi为大部分或全部提供直接支持。

编辑:可能的Delphi实施:

type
  TForm1 = class(TForm)
    Label1: TLabel;
    procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure FormPaint(Sender: TObject);
    procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    FCacheWnd: HWND;
    FCaptured: Boolean;
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

const  // the first item, the place where the crosshair is
  ClickRect: TRect = (Left: 10; Top: 10; Right: 44; Bottom: 44);

procedure TForm1.FormPaint(Sender: TObject);
begin
  // draw the control and the crosshair if no capturing
  if GetCapture <> Handle then begin  
    DrawFrameControl(Canvas.Handle, ClickRect, 0, DFCS_BUTTONPUSH);
    DrawIcon(Canvas.Handle, ClickRect.Left, ClickRect.Top,
              Screen.Cursors[crCross]);
  end;
end;

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if (Button = mbLeft) and (Shift = [ssLeft])
      and PtInRect(ClickRect, Point(X, Y)) then begin
    // the second item, draw the control pressed,
    // set the flag and the capture. FCacheWnd is used not to get
    // window information for every mouse move - if the window under the
    // mouse is not changed.
    DrawFrameControl(Canvas.Handle, ClickRect, 0, DFCS_PUSHED);
    FCacheWnd := 0;
    FCaptured := True;
    SetCapture(Handle);
    Screen.Cursor := crCross; // the third item, set the cursor to crosshair.
  end;
end;

function GetWndFromClientPoint(ClientWnd: HWND; Pt: TPoint): HWND;
begin
  MapWindowPoints(ClientWnd, GetDesktopWindow, Pt, 1);
  Result := WindowFromPoint(Pt);
end;

function GetWndInfo(Wnd: HWND): string;
var
  ClassName: array [0..256] of Char;
begin
  Result := '';
  if IsWindow(Wnd) then begin
    GetClassName(Wnd, ClassName, 256);
    Result := Format('Window: %x [%s]', [Wnd, ClassName]);
    if (GetWindowLong(Wnd, GWL_STYLE) and WS_CHILD) = WS_CHILD then begin
      Wnd := GetAncestor(Wnd, GA_ROOT);
      GetClassName(Wnd, ClassName, 256);
      Result := Format(Result + sLineBreak + 'Top level: %x [%s]', [Wnd, ClassName]);
    end;
  end;
end;

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  Wnd: HWND;
begin
  if FCaptured then begin
    // fourth item, convert coordinates and find the window under the cursor
    Wnd := GetWndFromClientPoint(Handle, Point(X, Y));
    if Wnd <> FCacheWnd then 
      Label1.Caption := GetWndInfo(Wnd);
    FCacheWnd := Wnd;
  end;
end;

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if FCaptured then begin
    // fifth item
    FCaptured := False;
    ReleaseCapture;
    InvalidateRect(Handle, @ClickRect, False); // invalidate pressed look
    Screen.Cursor := crDefault;
  end;
end;

答案 1 :(得分:4)

如果您知道窗口标题中的文本是什么,则此代码将为您解决问题:

var
  WindowList: TList;

function GetHandle (windowtitle: string): HWND;
var
  h, TopWindow: HWND;
  Dest: array[0..80] of char;
  i: integer;
  s: string;

  function getWindows(Handle: HWND; Info: Pointer): BOOL; stdcall;
    begin
      Result:= True;
      WindowList.Add(Pointer(Handle));
    end;

begin
  result:= 0;

  try
    WindowList:= TList.Create;
    TopWindow:= Application.Handle;
    EnumWindows(@getWindows, Longint(@TopWindow));
    i:= 0;
    while (i < WindowList.Count) and (result = 0) do
      begin
        GetWindowText(HWND(WindowList[i]), Dest, sizeof(Dest) - 1);
        s:= dest;
        if length(s) > 0 then
          begin
            if (Pos(UpperCase(Windowtitle), UpperCase(s)) >= 1) then
              begin
                h:= HWND(WindowList[i]);
                if IsWindow(h) then
                  result:= h
             end
           end;
        inc(i)
      end
    finally
      WindowList.Free;
    end;
end;

示例中的用法(记事本将打开文件的名称放在窗口标题中):

h:= getHandle('text.txt');
if (h = 0)
  // Oops not found
else 
  begin
    // you got the handle!
  end;

我使用此代码检查我的应用程序是否已启动并正在运行。但它可以用于任何已启动的应用程序。

答案 2 :(得分:1)

编辑:它已经消失了,但你曾经能够从delphipages.com下载Eddie Shipman的Delphi Window Spy,它已经变成了一堆无用的linkbait。