问题
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:
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:
I added a breakpoint at the Abort Line.
Any ideas?
回答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;
回答2:
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.
来源:https://stackoverflow.com/questions/5714542/finding-using-the-currently-active-chatbox-in-the-skype-client-thru-the-winapi