2013-04-17 46 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

相關問題