2013-04-17 42 views
2

我试图使用WinAPI更改另一应用程序窗口中的页面控件中的选项卡。如何在TCM_SETCURSEL消息后更新选项卡的内容

我发送了一个TCM_SETCURSEL消息给页面控件,它确实改变了标签,但没有改变标签的内容。例如:Pagecontrol位于选项卡0上,我向页面控件发送一个TCM_SETCURSEL索引:1,页面控件现在位于选项卡1上,但继续显示选项卡0的内容而不是选项卡1的内容。

我曾尝试:

  • 发送WM_PAINT到标签1 TCM_SETCURSEL后。
  • 发送WM_NCPAINT到TCM_SETCURSEL后面的选项卡1。
  • 在TCM_SETCURSEL和WM_NOTIFY + TCN_SELCHANGE之后发送WM_NOTIFY + TCN_SELCHANGING到页面控制之后。
  • 对页面控件的父项做上述操作。

我使用的是delphi 2010,目标应用程序也是delphi应用程序。

这是最后的代码迭代,这将通知发送到页面控件的父:

procedure ChangeTab(PageControlHandle: HWND; TabIndex: Integer); 
var 
    Info: TNMHdr; 
begin 
    Info.hwndFrom := PageControlHandle; 
    Info.idFrom := GetWindowLongPtr(PageControlHandle, GWL_ID); 
    Info.code := TCN_SELCHANGING; 
    if SendMessage(GetParent(PageControlHandle), WM_NOTIFY, PageControlHandle, lParam(@Info)) <> 0 then 
    raise Exception.Create('Page control didn''t allow tab to change.'); 

    if SendMessage(PageControlHandle, TCM_SETCURSEL, TabIndex, 0) = -1 then 
    raise Exception.Create('Failed to change tab.'); 

    Info.code := TCN_SELCHANGE; 
    SendMessage(GetParent(PageControlHandle), WM_NOTIFY, PageControlHandle, lParam(@Info)) 
end; 

当我点击选项卡1 WinSpy表明,它接收到这些信息:

<000001> 001D0774 S WM_WINDOWPOSCHANGING lpwp:0018F308 
<000002> 001D0774 R WM_WINDOWPOSCHANGING 
<000003> 001D0774 S WM_CHILDACTIVATE 
<000004> 001D0774 R WM_CHILDACTIVATE 
<000005> 001D0774 S WM_WINDOWPOSCHANGED lpwp:0018F308 
<000006> 001D0774 R WM_WINDOWPOSCHANGED 
<000007> 001D0774 S WM_WINDOWPOSCHANGING lpwp:0018EF7C 
<000008> 001D0774 R WM_WINDOWPOSCHANGING 
<000009> 001D0774 S WM_NCPAINT hrgn:00000001 
<000010> 001D0774 R WM_NCPAINT 
<000011> 001D0774 S WM_ERASEBKGND hdc:33011920 
<000012> 001D0774 R WM_ERASEBKGND fErased:True 
<000013> 001D0774 S WM_WINDOWPOSCHANGED lpwp:0018EF7C 
<000014> 001D0774 R WM_WINDOWPOSCHANGED 
<000015> 001D0774 P WM_PAINT hdc:00000000 
<000016> 001D0774 S WM_CTLCOLORSTATIC hdcStatic:FB01097B hwndStatic:001507D0 
<000017> 001D0774 R WM_CTLCOLORSTATIC hBrush:261011F7 
<000018> 001D0774 S WM_CTLCOLORSTATIC hdcStatic:FB01097B hwndStatic:001507D0 
<000019> 001D0774 R WM_CTLCOLORSTATIC hBrush:261011F7 
<000020> 001D0774 S WM_CTLCOLORSTATIC hdcStatic:530112DB hwndStatic:000608C2 
<000021> 001D0774 R WM_CTLCOLORSTATIC hBrush:261011F7 
<000022> 001D0774 S WM_CTLCOLORSTATIC hdcStatic:530112DB hwndStatic:000608C2 
<000023> 001D0774 R WM_CTLCOLORSTATIC hBrush:261011F7 
<000024> 001D0774 S WM_DRAWITEM idCtl:395458 lpdis:0018F728 
<000025> 001D0774 R WM_DRAWITEM fProcessed:False 
<000026> 001D0774 S WM_CTLCOLOREDIT hdcEdit:FB01097B hwndEdit:000808A8 
<000027> 001D0774 R WM_CTLCOLOREDIT hBrush:3810149A 
<000028> 001D0774 S WM_CTLCOLOREDIT hdcEdit:FB01097B hwndEdit:000808A8 
<000029> 001D0774 R WM_CTLCOLOREDIT hBrush:3810149A 
<000030> 001D0774 S WM_DRAWITEM idCtl:526504 lpdis:0018F728 
<000031> 001D0774 R WM_DRAWITEM fProcessed:False 
<000032> 001D0774 S WM_CTLCOLORSTATIC hdcStatic:530112DB hwndStatic:001A06F2 
<000033> 001D0774 R WM_CTLCOLORSTATIC hBrush:261011F7 
<000034> 001D0774 S WM_CTLCOLORSTATIC hdcStatic:530112DB hwndStatic:001A06F2 
<000035> 001D0774 R WM_CTLCOLORSTATIC hBrush:261011F7 
+0

如果您正在窥探PageControl窗口本身,您将看不到WM_NOTIFY消息,因为它们不是直接发送到PageControl窗口,而是发送到其父窗口。然而,'CN_NOTIFY'消息直接发送到PageControl窗口,但Spy ++不明白这些消息,因为它们是VCL特定的(但只要你没有过滤它们,Spy ++仍然会显示它们被接收到)。 –

回答

2

发现,通过使用该TCM_SETCURFOCUS消息,而不是TCM_SETCURSEL是足以改变选项卡的内容。

procedure ChangeTab(PageControlHandle: HWND; TabIndex: Integer); 
begin 
    SendMessage(PageControlHandle, TCM_SETCURFOCUS, TabIndex, 0); 
end; 

但是,如果页面控制按钮模式(有TCS_BUTTONS风格),这是行不通的,因为这些按钮可以接收焦点不改变内容。

+0

TCS_BUTTON问题是[记录的行为](http://msdn.microsoft.com/en-us/library/windows/desktop/bb760610.aspx):“如果选项卡控件具有TCS_BUTTONS样式(按钮模式),则焦点可能会与选定的选项卡不同,例如,选择选项卡时,用户可以按箭头键将焦点设置到不同的选项卡,而无需更改选定的选项卡;在按钮模式下,TCM_SETCURFOCUS可将输入将焦点对准与指定选项卡关联的按钮,但不会更改选定的选项卡。“ –

+0

在TCS_BUTTON情况下,WinForms选项卡控件不显示TCM_SETCURSEL后的内容。 WM_CLICK可以提供帮助,但这是一种解决方法,而不是解决方案。 –

2

常一个PageControl本身会将TCN_...通知发送给它自己的父级,因此用于这些通知的参数与PageControl和父级运行在同一地址空间中。您正在从另一个进程发送通知,因此您的TNMHdr指针位于发送应用程序的地址空间中,并且不是接收应用程序地址空间中的有效指针。更糟糕的是,WM_NOTIFY不允许跨进程边界发送,为documented by MSDN

对于Windows 2000及更高版本的系统中,WM_NOTIFY消息无法进程之间发送。

所以,你需要使用VirtualAllocEx()WriteProcessMemory()分配和操纵在接收应用程序的地址空间中的TNMHdr记录。并且您需要将代码注入接收进程以发送消息TCN_...

试试这个:

// this is a Delphi translation of code written by David Ching: 
// 
// https://groups.google.com/d/msg/microsoft.public.vc.mfc/QMAHlPpEQyM/Nu9iQycmEykJ 
// 
// http://www.dcsoft.com/private/sendmessageremote.h 
// http://www.dcsoft.com/private/sendmessageremote.cpp 

const 
    MAX_BUF_SIZE = 512; 

type 
    LPFN_SENDMESSAGE = function(Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; 

    PINJDATA = ^INJDATA; 
    INJDATA = record 
    fnSendMessage: LPFN_SENDMESSAGE; // pointer to user32!SendMessage 
    hwnd: HWND; 
    msg: UINT; 
    wParam: WPARAM; 
    arrLPARAM: array[0..MAX_BUF_SIZE-1] of Byte; 
    end; 

function ThreadFunc(pData: PINJDATA): DWORD; stdcall; 
begin 
    Result := pData.fnSendMessage(pData.hwnd, pData.msg, pData.wParam, LPARAM(@pData.arrLPARAM)); 
end; 

procedure AfterThreadFunc; 
begin 
end; 

function SendMessageRemote(dwProcessId: DWORD; hwnd: HWND; msg: UINT; wParam: WPARAM; pLPARAM: Pointer; sizeLParam: size_t): LRESULT; 
var 
    hProcess: THandle; // the handle of the remote process 
    hUser32: THandle; 
    DataLocal: INJDATA; 
    pDataRemote: PINJDATA; // the address (in the remote process) where INJDATA will be copied to; 
    pCodeRemote: Pointer; // the address (in the remote process) where ThreadFunc will be copied to; 
    hThread: THandle; // the handle to the thread executing the remote copy of ThreadFunc; 
    dwThreadId: DWORD; 
    dwNumBytesXferred: SIZE_T; // number of bytes written/read to/from the remote process; 
    cbCodeSize: Integer; 
    lSendMessageResult: DWORD; 
begin 
    Result := $FFFFFFFF; 

    hUser32 := GetModuleHandle('user32'); 
    if hUser32 = 0 then RaiseLastOSError; 

    // Initialize INJDATA 
    @DataLocal.fnSendMessage := GetProcAddress(hUser32, 'SendMessageW'); 
    if not Assigned(DataLocal.fnSendMessage) then RaiseLastOSError; 

    DataLocal.hwnd := hwnd; 
    DataLocal.msg := msg; 
    DataLocal.wParam := wParam; 

    Assert(sizeLParam <= MAX_BUF_SIZE); 
    Move(pLPARAM^, DataLocal.arrLPARAM, sizeLParam); 

    // Copy INJDATA to Remote Process 
    hProcess := OpenProcess(PROCESS_CREATE_THREAD or PROCESS_QUERY_INFORMATION or PROCESS_VM_OPERATION or PROCESS_VM_WRITE or PROCESS_VM_READ, FALSE, dwProcessId); 
    if hProcess = 0 then RaiseLastOSError; 
    try 
    // 1. Allocate memory in the remote process for INJDATA 
    // 2. Write a copy of DataLocal to the allocated memory 
    pDataRemote := PINJDATA(VirtualAllocEx(hProcess, nil, sizeof(INJDATA), MEM_COMMIT, PAGE_READWRITE)); 
    if pDataRemote = nil then RaiseLastOSError; 
    try 
     if not WriteProcessMemory(hProcess, pDataRemote, @DataLocal, sizeof(INJDATA), dwNumBytesXferred) then RaiseLastOSError; 

     // Calculate the number of bytes that ThreadFunc occupies 
     cbCodeSize := Integer(LPBYTE(@AfterThreadFunc) - LPBYTE(@ThreadFunc)); 

     // 1. Allocate memory in the remote process for the injected ThreadFunc 
     // 2. Write a copy of ThreadFunc to the allocated memory 
     pCodeRemote := VirtualAllocEx(hProcess, nil, cbCodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); 
     if pCodeRemote = nil then RaiseLastOSError; 
     try 
     if not WriteProcessMemory(hProcess, pCodeRemote, @ThreadFunc, cbCodeSize, dwNumBytesXferred) then RaiseLastOSError; 

     // Start execution of remote ThreadFunc 
     hThread := CreateRemoteThread(hProcess, nil, 0, pCodeRemote, pDataRemote, 0, dwThreadId); 
     if hThread = 0 then RaiseLastOSError; 
     try 
      WaitForSingleObject(hThread, INFINITE); 

      // Copy LPARAM back (result is in it) 
      if not ReadProcessMemory(hProcess, @pDataRemote.arrLPARAM, pLPARAM, sizeLParam, dwNumBytesXferred) then RaiseLastOSError; 
     finally 
      GetExitCodeThread(hThread, lSendMessageResult); 
      CloseHandle(hThread); 
      Result := lSendMessageResult; 
     end; 
     finally 
     VirtualFreeEx(hProcess, pCodeRemote, 0, MEM_RELEASE); 
     end; 
    finally 
     VirtualFreeEx(hProcess, pDataRemote, 0, MEM_RELEASE); 
    end; 
    finally 
    CloseHandle(hProcess); 
    end; 
end; 

procedure ChangeTab(PageControlHandle: HWND; TabIndex: Integer); 
var 
    dwProcessId: DWORD; 
    hParent: HWND; 
    Info: TNMHdr; 
begin 
    GetWindowThreadProcessId(PageControlHandle, @dwProcessId); 
    hParent := GetParent(PageControlHandle); 

    Info.hwndFrom := PageControlHandle; 
    Info.idFrom := GetWindowLongPtr(PageControlHandle, GWL_ID); 
    Info.code := TCN_SELCHANGING; 

    if SendMessageRemote(dwProcessId, hParent, WM_NOTIFY, WPARAM(PageControlHandle), @Info, SizeOf(TNMHdr)) <> 0 then 
    raise Exception.Create('Page control didn''t allow tab to change.'); 

    if SendMessage(PageControlHandle, TCM_SETCURSEL, TabIndex, 0) = -1 then 
    raise Exception.Create('Failed to change tab.'); 

    Info.code := TCN_SELCHANGE; 
    SendMessageRemote(dwProcessId, hParent, WM_NOTIFY, WPARAM(PageControlHandle), @Info, SizeOf(TNMHdr)); 
end; 
+0

这仍然没有改变标签的内容。 –

+0

然后尝试将'CN_NOTIFY'消息直接发送到PageControl窗口本身,而不是将'WM_NOTIFY'消息发送到PageControl的父窗口。 'CN_NOTIFY'是VCL如何将WM_NOTIFY消息反射回最初触发它们的组件:'SendMessage(PageControlHandle,CN_NOTIFY,WPARAM(PageControlHandle),LPARAM(PInfo));' –

+0

@RemyLebeau:我知道这是一个旧线程,但我认为它对Daniel不起作用,因为必须将WM_NOTIFY发送给父代,而当前代码不会。 – c00000fd

相关问题