寻找与通过 WinAPI 和 Skype 客户端使用当前活动的聊天框德尔福?

发布于 2024-11-02 15:56:22 字数 2340 浏览 5 评论 0原文

在 Delphi 中,通过使用 Skype API,我可以相当轻松地向联系人发送消息。但是,我想做的是在当前焦点联系人的聊天框中输入消息,而不发送消息。

通过使用Winspector,我发现Chatbox的Classname是TChatRichEdit,它被放置在TChatEntryControl上,它被放置在TConversationForm上,最后,它被放置在tSkMainForm上。 (显然Skype客户端是用Delphi编写的;))

通过使用Win API,我如何找到正确的tSkMainForm>TConversationForm>TChatEntryControl>TChatRichEdit,然后在其中输入消息?

解决这个问题的最佳方法是什么?

另外,TConversationForm 还包含联系人的姓名,所以我想这会让事情变得更容易一些?

编辑:这是 Windspector Spy 的屏幕截图,显示了 TChatRichEdit:

Winspector Spy

这是我当前的代码:

function GetConversationWindow(Wnd: HWnd; P: LParam): Bool; stdcall;
var
  Param: PGetConversationParam;
  ProcID: DWord;
  // WndClass docs say maximum class-name length is 256.
  ClassName: array[0..256] of Char;
  WindowTitle: array[0..256] of Char;
begin
  Result := True; // assume it doesn't match; keep searching
  Param := PGetConversationParam(P);

  GetWindowThreadProcessID(Wnd, @ProcID);
  if ProcID <> Param.ProcID then
    Exit;

  if GetClassName(Wnd, ClassName, Length(ClassName)) = 0 then
    Exit;
  if StrComp(ClassName, 'TConversationForm') <> 0 then
    Exit;

  if SendMessage(Wnd, wm_GetText, Length(WindowTitle), LParam(@WindowTitle[0])) = 0 then
    Exit;
  if Param.ContactName = WindowTitle then begin
    Param.Result := Wnd;
    Result := False;
  end;
end;



procedure TForm1.Button1Click(Sender: TObject);
var
  Param: TGetConversationParam;
  RichEditWnd, ControlWnd : HWND;
  ParentWnd : HWND;
begin
  //Param.ProcID := GetSkypeProcessID;
  Param.ContactName := 'xSky Admin';
  ParentWnd := FindWindowEx(0,0,'tSkMainForm',nil);

  if EnumChildWindows(ParentWnd,@GetConversationWindow, LParam(@Param)) then
    Abort; // Didn't find it.

  // Param.Result holds the conversation window's handle. Now walk its children.
  ControlWnd := FindWindowEx(Param.Result, 0, 'TChatEntryControl', nil);
  if ControlWnd = 0 then
    Abort; // Conversation doesn't have an entry control

  RichEditWnd := FindWindowEx(ControlWnd, 0, 'TChatRichEdit', nil);
  if RichEditWnd = 0 then
    Abort;

  ShowMessage('Got it!');
end;

我从未达到显示消息。

以下是我的 IDE 在调试模式下的屏幕截图:

IDE in Debug Mode

我在中止行添加了一个断点。

有什么想法吗?

In Delphi, by using the Skype API, I can send a message to a contact fairly easy. However, what I am trying to do, is enter the message in the Chat Box of the currently focused Contact, without sending the message.

By using Winspector, I found that the Classname of the Chatbox is TChatRichEdit, which is placed on a TChatEntryControl, which is placed on a TConversationForm, and finally, which is placed on the tSkMainForm. (Obviously the Skype Client is coded in Delphi ;) )

By using the Win API, how can I find the correct tSkMainForm>TConversationForm>TChatEntryControl>TChatRichEdit, and then enter a message into it?

What would be the best way to go about this?

Also, the TConversationForm contains the name of the contact aswell, so I guess that makes it a bit easier?

EDIT: Here is a screenshot of Windspector Spy, showing the TChatRichEdit:

Winspector Spy

Here is my current code:

function GetConversationWindow(Wnd: HWnd; P: LParam): Bool; stdcall;
var
  Param: PGetConversationParam;
  ProcID: DWord;
  // WndClass docs say maximum class-name length is 256.
  ClassName: array[0..256] of Char;
  WindowTitle: array[0..256] of Char;
begin
  Result := True; // assume it doesn't match; keep searching
  Param := PGetConversationParam(P);

  GetWindowThreadProcessID(Wnd, @ProcID);
  if ProcID <> Param.ProcID then
    Exit;

  if GetClassName(Wnd, ClassName, Length(ClassName)) = 0 then
    Exit;
  if StrComp(ClassName, 'TConversationForm') <> 0 then
    Exit;

  if SendMessage(Wnd, wm_GetText, Length(WindowTitle), LParam(@WindowTitle[0])) = 0 then
    Exit;
  if Param.ContactName = WindowTitle then begin
    Param.Result := Wnd;
    Result := False;
  end;
end;



procedure TForm1.Button1Click(Sender: TObject);
var
  Param: TGetConversationParam;
  RichEditWnd, ControlWnd : HWND;
  ParentWnd : HWND;
begin
  //Param.ProcID := GetSkypeProcessID;
  Param.ContactName := 'xSky Admin';
  ParentWnd := FindWindowEx(0,0,'tSkMainForm',nil);

  if EnumChildWindows(ParentWnd,@GetConversationWindow, LParam(@Param)) then
    Abort; // Didn't find it.

  // Param.Result holds the conversation window's handle. Now walk its children.
  ControlWnd := FindWindowEx(Param.Result, 0, 'TChatEntryControl', nil);
  if ControlWnd = 0 then
    Abort; // Conversation doesn't have an entry control

  RichEditWnd := FindWindowEx(ControlWnd, 0, 'TChatRichEdit', nil);
  if RichEditWnd = 0 then
    Abort;

  ShowMessage('Got it!');
end;

I never reach the ShowMessage.

Here is a screenshot of my IDE in Debug Mode:

IDE in Debug Mode

I added a breakpoint at the Abort Line.

Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

夏日落 2024-11-09 15:56:22

像这样:

var
  aHandle   : cardinal;
begin
   aHandle := FindWindow(PWideChar('TChatRichEdit'), nil);
   result  := aHandle <> 0;
   if result then
      PostMessage(aHandle, WM_...); 

然后你就拥有了该窗口的句柄。您可以使用WM_SETTEXT或其他东西来输入文本。
但Skype使用WM_COPYDATA与其他程序通信,反之亦然。
您应该在 StackOverflow 中搜索该内容。

Something like this:

var
  aHandle   : cardinal;
begin
   aHandle := FindWindow(PWideChar('TChatRichEdit'), nil);
   result  := aHandle <> 0;
   if result then
      PostMessage(aHandle, WM_...); 

Then you have a handle of that window. You can use WM_SETTEXT or something to input text.
But Skype uses WM_COPYDATA to communicate with other programs, and vice versa.
You should search StackOverflow for that.

眉黛浅 2024-11-09 15:56:22

我猜 TConversationForm 是一个顶级窗口。使用EnumWindows来找到它。 (暂时不用费心FindWindow;它总是返回它找到的第一个窗口,因此,如果有多个活动对话,您无法控制将获得哪些对话。)

type
  PGetConversationParam = ^TGetConversationParam;
  TGetConversationParam = record
    ProcID: DWord;
    ContactName: string;
    Result: HWnd;
  end;

function GetConversationWindow(Wnd: HWnd; P: LParam): Bool; stdcall;
var
  Param: PGetConversationParam;
  ProcID: DWord;
  // WndClass docs say maximum class-name length is 256.
  ClassName: array[0..256] of Char;
  WindowTitle: array[0..256] of Char;
begin
  Result := True; // assume it doesn't match; keep searching
  Param := PGetConversationParam(P);

  GetWindowThreadProcessID(Wnd, @ProcID);
  if ProcID <> Param.ProcID then
    Exit;

  if GetClassName(Wnd, ClassName, Length(ClassName)) = 0 then
    Exit;
  if StrComp(ClassName, 'TConversationForm') <> 0 then
    Exit;

  if SendMessage(Wnd, wm_GetText, Length(WindowTitle), LParam(@WindowTitle[0])) = 0 then
    Exit;
  if Param.ContactName = WindowTitle then begin
    Param.Result := Wnd;
    Result := False;
  end;
end;

该函数会检查几件事以确保它正在查看所需的窗口。它检查窗口是否属于 Skype 进程、是否具有预期的窗口类以及其标题是否是目标联系人的姓名。如果 Skype 在窗口标题中添加了其他文本,您需要确保它看起来“足够接近”。不要只是调用 Pos 来查看联系人姓名是否出现在标题中的某个位置;如果任何联系人的姓名是对话窗口标题的子字符串,您可能会无意中找到不应该找到的匹配项。

进程 ID 并不是严格必需的,因此如果您不知道进程 ID,可以忽略该部分。

EnumWindows 函数将为每个顶级窗口调用一次上述函数。如果该窗口是您要查找的窗口,GetConversationWindow 将返回 False,表示“我已找到我想要的内容,因此请停止再询问”。否则,它返回True:“那个不是,所以请给我另一个。”如果 GetConversationWindow 返回 False,则 EnumWindows 也将返回 FalseParam.Result 字段将保存您正在查找的窗口的句柄。获得后,使用 FindWindowEx 来导航窗口层次结构的其余部分:

var
  Param: TGetConversationParam;
begin
  Param.ProcID := GetSkypeProcessID;
  Param.ContactName := GetSkypeContactName;
  if EnumWindows(@GetConversationWindow, LParam(@Param)) then
    Abort; // Didn't find it.

  // Param.Result holds the conversation window's handle. Now walk its children.
  ControlWnd := FindWindowEx(Param.Result, 0, 'TChatEntryControl', nil);
  if ControlWnd = 0 then
    Abort; // Conversation doesn't have an entry control

  RichEditWnd := FindWindowEx(ControlWnd, 0, 'TChatRichEdit', nil);
  if RichEditWnd = 0 then
    Abort;

  // Voila!
end;

I guess TConversationForm is a top-level window. Use EnumWindows to find that. (Don't bother with FindWindow yet; it always returns the first window it finds, so if there are multiple conversations active, you have no control over which you'll get.)

type
  PGetConversationParam = ^TGetConversationParam;
  TGetConversationParam = record
    ProcID: DWord;
    ContactName: string;
    Result: HWnd;
  end;

function GetConversationWindow(Wnd: HWnd; P: LParam): Bool; stdcall;
var
  Param: PGetConversationParam;
  ProcID: DWord;
  // WndClass docs say maximum class-name length is 256.
  ClassName: array[0..256] of Char;
  WindowTitle: array[0..256] of Char;
begin
  Result := True; // assume it doesn't match; keep searching
  Param := PGetConversationParam(P);

  GetWindowThreadProcessID(Wnd, @ProcID);
  if ProcID <> Param.ProcID then
    Exit;

  if GetClassName(Wnd, ClassName, Length(ClassName)) = 0 then
    Exit;
  if StrComp(ClassName, 'TConversationForm') <> 0 then
    Exit;

  if SendMessage(Wnd, wm_GetText, Length(WindowTitle), LParam(@WindowTitle[0])) = 0 then
    Exit;
  if Param.ContactName = WindowTitle then begin
    Param.Result := Wnd;
    Result := False;
  end;
end;

That function checks several things to make sure it's looking at the desired window. It checks that the window belongs to the Skype process, that it has the expected window class, and that its title is the name of the target contact. If Skype puts additional text in the window title, you'll need to make sure it looks "close enough." Don't just call Pos to see whether the contact name appears somewhere in the title; if any contact has a name that's a substring of the a conversation window's title, you might inadvertently find a match when you shouldn't.

The process ID isn't strictly required, so you can omit that part if you don't know the process ID.

The EnumWindows function will call the above function once for each top-level window. If the window is the one you're looking for, GetConversationWindow returns False to say, "I've found what I want, so please stop asking about any more." Otherwise, it returns True: "That one wasn't it, so please give me another." If GetConversationWindow ever returns False, then EnumWindows will also return False and the Param.Result field will hold the handle of the window you were looking for. Once you have it, use FindWindowEx to navigate the rest of the window hierarchy:

var
  Param: TGetConversationParam;
begin
  Param.ProcID := GetSkypeProcessID;
  Param.ContactName := GetSkypeContactName;
  if EnumWindows(@GetConversationWindow, LParam(@Param)) then
    Abort; // Didn't find it.

  // Param.Result holds the conversation window's handle. Now walk its children.
  ControlWnd := FindWindowEx(Param.Result, 0, 'TChatEntryControl', nil);
  if ControlWnd = 0 then
    Abort; // Conversation doesn't have an entry control

  RichEditWnd := FindWindowEx(ControlWnd, 0, 'TChatRichEdit', nil);
  if RichEditWnd = 0 then
    Abort;

  // Voila!
end;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文