2

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

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

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

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

此外,TConversationForm 还包含联系人的姓名,所以我想这会更容易一些吗?

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

温斯派特间谍

这是我当前的代码:

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;

我从来没有到达 ShowMessage。

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

IDE 处于调试模式

我在 Abort Line 添加了一个断点。

有任何想法吗?

4

2 回答 2

4

像这样的东西:

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

然后你就有了那个窗口的句柄。您可以使用 WM_SETTEXT 或其他东西来输入文本。但是 Skype 使用 WM_COPYDATA 与其他程序进行通信,反之亦然。您应该为此搜索 StackOverflow。

于 2011-04-19T11:20:46.160 回答
1

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;
于 2011-04-19T13:34:37.237 回答