添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

I'm using a software to manipulate another one, like a software tester environment, with the objective of getting some values and executing some tasks. For this I'm using SendMessage API and it's working very well in almost all Windows controls, except in the CheckListBox. The "slave" software was created in Delphi, so, I'm not sure if that CheckListBox is a standard Windows control, anyway, this is the documentation from MS:
https://learn.microsoft.com/en-us/windows/win32/controls/list-boxes
In this control I can get the quantity of items, the texts, which one is selected, and a lot of other info, but I can not get to know if the items are checked or not. I tried to use some tools like "Accessibility Insights for Windows" and AutoIt, but no results.
Any idea on how to get the state of the check boxes inside a Delphi CheckListBox?
Thanks in advance.

I was about to write an answer how to use GetProp to access the ControlOfsXXXXXXXXYYYYYYYY property to get a pointer to the control object in memory and then use ReadProcessMemory to find what you need when I noticed that Delphi uses the regular "item data" there at some point when accessing the info about checked items. It seems you can use LB_GETITEMDATA there CherryDT Apr 13, 2020 at 20:25 I think Delphi stores a TCheckListBoxDataWrapper there with a property State . Of course, that would be a pointer to the target process' memory as well though, so you'd need ReadProcessMemory anyway to read the actual data. CherryDT Apr 13, 2020 at 20:30 Delphi's TCheckListBox is a custom control, a wrapper for a standard Win32 ListBox using owner-drawn checkboxes, and state information stored in memory in each list item. There is no standard API to access that info directly, and the checkboxes are not exposed to UI Automation. Even though you could use LB_GETITEMDATA to get each list item's internal TCheckListBoxDataWrapper pointer, you would still need to use ReadProcessMemory() to read its State data, yes. Remy Lebeau Apr 13, 2020 at 20:38

Delphi stores the checked information in a TCheckListBoxDataWrapper object. A pointer to that object is stored in the regular "item data" of checkbox item. This object then has a boolean property State which you'll find at offset 8.

Note: If it turns out that your program's particular Delphi version has it at a different offset than 8, try something nearby - 4, 12, ... - it can't have gotten far...

To find out if an item is checked, you'd need to:

  • Get a pointer to the TCheckListBoxDataWrapper of that item. This can be done by sending an LB_GETITEMDATA message. If you get zero back, it also counts as not checked.
  • Since this pointer points to memory in the other process and not yours, you can't simply dereference it. Instead, you'd need to use ReadProcessMemory . Knowing that the State property is at offset 8, you can call ReadProcessMemory(hProcess, itemData + 8, &checked, 1, NULL) to read 1 byte into variable checked . (You first need to open the target process using OpenProcess .)
  • Then you'll have the checked state of the item in checked ! 1 for checked, 0 for unchecked.

    In case you later need to access some other internal state as well, another hint: There is a window property ControlOfsXXXXXXXXYYYYYYYY with X being the HINSTANCE (base address) of the window owner (usually 00400000 ) and Y being the (hex) thread ID of the window owner (you can use GetWindowThreadProcessId to obtain it). You can use GetProp to get the value of that property, which will be a pointer to the control object (in this example the TCheckListBox itself). You can then poke around with ReadProcessMemory to get other data as needed. You will need to know the offsets though (but you can use a debugger to try and figure them out). It is very helpful to compile a test program with the same Delphi version and execute functions there that access those properties, then you can debug your own test program instead and figure out the offsets more easily.

    A more advanced method that involves injecting a custom DLL (written in the same Delphi version) into the target process allows for accessing (reading and writing) this sort of data more directly. I wrote an article about that a long time ago.

    Wow, very helped and precised answer! That's exactly the way to get this working! Thanks a lot! I'll put my code right below to help others. Gustavo E. Hennemann Apr 13, 2020 at 23:21 Hey @CherryDT, this other question is similar to this one, can you check it please? stackoverflow.com/questions/60960254/… Gustavo E. Hennemann Apr 14, 2020 at 13:07 This would take me hours to answer. You'd have to go deeper on this one - either use a debugger to figure out the structures in memory (based on the pointer you get from the ControlOfs... property) or write a DLL in the same Delphi version and inject it and then use RTTI to access the relevant properties. (You'd then have to attach the same memory manager though as well, as explained in my article.) Of course, there are also commercial solutions around, see for example this . CherryDT Apr 14, 2020 at 15:56 Hey @CherryDT, how did you know about that offset 8? Where did you found it? There is something like that where I can look for a clue about the TcxGrid control? Gustavo E. Hennemann Jul 20, 2020 at 19:00 With a bit of googling, you can find source code here (search for TCheckListBoxDataWrapper in this file). The first piece of data in any class instance is a pointer to the VTable (offset 0). The next field afterwards would be FData according to the code I just linked, at offset 4. The next one, FState , is the one we are after. FData is 32 bit (4 bytes) long, and therefore the next location is offset 8. FState itself is a 1-byte-long enum which needs no special alignment, so it'll be offset 8. CherryDT Jul 20, 2020 at 19:29

    As posted by @CherryDT, that is the way to access the state of the CheckListBox. I used AutoIt to test the tip because at this moment this is the fastest way I have to test it.
    The code:

    ;------------------------------------------------------------------------------
    ;   Retuns the state of the indicated item in the CheckListBox control.
    ;   Parameter:
    ;       $iPID: process ID (PID)
    ;       $hWnd: the handle of the CheckListBox control
    ;       $iIndex: index of the item in the list (0 based)
    ;------------------------------------------------------------------------------
    Func CtrlListBox_GetState($iPID, $hWnd, $iIndex)
       Local $hProc                             ; Handle of the process.
       Local $pItem                             ; Pointer to the item.
       Local $pData = DllStructCreate("byte")   ; Data structure.
       Local $iQty                              ; Size of data read.
       Local Const $LB_STATE_SHIFT = 8          ; State position in the memory.
       $hProc = _WinAPI_OpenProcess(0x1F0FFF, False, $iPID)     ; 0x1F0FFF = PROCESS_ALL_ACCESS
       $pItem = _GUICtrlListBox_GetItemData($hWnd, $iIndex) + $LB_STATE_SHIFT
       _WinAPI_ReadProcessMemory($hProc, $pItem, DllStructGetPtr($pData), DllStructGetSize($pData), $iQty)
       Return DllStructGetData($pData, 1)
    EndFunc
    ;------------------------------------------------------------------------------
    ;   Retuns the state of the indicated item in the CheckListBox control.
    ;   Parameter:
    ;       $iPID: process ID (PID)
    ;       $hWnd: the handle of the CheckListBox control
    ;       $iIndex: index of the item in the list (0 based)
    ;       $bState: state deseired (true or false)
    ;------------------------------------------------------------------------------
    Func CtrlListBox_SetState($iPID, $hWnd, $iIndex, $bState)
       Local $hProc                             ; Handle of the process.
       Local $pItem                             ; Pointer to the item.
       Local $pData = DllStructCreate("byte")   ; Data structure.
       Local $iQty                              ; Size of data read.
       Local Const $LB_STATE_SHIFT = 8          ; State position in the memory.
       if($bState <> 0) Then
          DllStructSetData($pData, 1, True)
          DllStructSetData($pData, 1, False)
       EndIf
       $hProc = _WinAPI_OpenProcess(0x1F0FFF, False, $iPID)     ; 0x1F0FFF = PROCESS_ALL_ACCESS
       $pItem = _GUICtrlListBox_GetItemData($hWnd, $iIndex) + $LB_STATE_SHIFT
       _WinAPI_WriteProcessMemory($hProc, $pItem, DllStructGetPtr($pData), DllStructGetSize($pData), $iQty)
       Return DllStructGetData($pData, 1)
    EndFunc
    

    I was facing the similar problem while trying to get the status of checkbox inside the CheckedListBox of UI developed in VB.Net. I wanted to get the status(check/Uncheck) so that I can execute required event from my test automation framework developer in Python + Winium. This was especially required to not accidently uncheck the already checked item.

    During my research, I got to know that it is not easily possible using Winium so I added a button on the UI for resetting all the checkboxes before checking the required ones.

    Hope it might help.

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.