How to properly loop through / get text / select SysTreeView32 window item

孤人 提交于 2020-01-16 00:35:54

问题


I've spent a couple of hours pouring through Microsoft's Dev Center; however, I can't seem to figure out how to do the following two things:

  1. Cycle through and view the names of each program under the 'Expert Advisors' section of the 'Navigator' sub window (for example 'MACD Sample' in screenshot below)

  2. select and double click the program (e.g. 'MACD Sample').

Winspector(Left) | Application(Right)

My main problem seems to be that I don't know how to properly use HTREEITEM to access the information. I noticed there is a function ListView_GetItemText, but I've been unable to find a TreeView_GetItemText or equivalent function.

Any help would be greatly appreciated.

Below is the main function of my program:

int _tmain(int argc, _TCHAR* argv[])
{
    wcout << TEXT("Enumerating Windows...") << endl;
    HWND handle = NULL;

    //--- Success: gets application handle
    bool success1 = getHandle(L"MetaTrader", L"20", handle);

    cout << "Success1: " << success1 << endl;
    cout << "Result1: " << handle << endl;

    //--- Success: gets navigator window
    bool success2 = getChildHandle(handle, L"", L"Navigator", handle);

    cout << "Success2: " << success2 << endl;
    cout << "Result2: " << handle << endl;

    //--- Success: gets "SysTreeView32" handle
    handle = FindWindowEx(handle, 0, L"SysTreeView32", L"");

    cout << "Result3: " << handle << endl;

    //--- Success: get "SysTreeView32" root nod
    HTREEITEM root = TreeView_GetNextItem(handle, NULL, TVGN_ROOT);
    cout << "root: " << root << endl;

    return 0;
}

The result of running the code seems to be working properly

Entire code for completeness:

// MT4Terminal-test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#pragma once

#include "targetver.h"


#include <iostream>
#include <map>
#include <string>

namespace std {
#if defined _UNICODE || defined UNICODE
    typedef wstring tstring;
#else
    typedef string tstring;
#endif
}

#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#include <psapi.h>

#include <Windows.h>
#include <Commctrl.h>
#include <windows.system.h>

using namespace std;

HWND glb_handle;
tstring glb_searchWindowTitle;
tstring glb_seachClassName;

BOOL CALLBACK enumWindowsChildProc(
    __in  HWND hWnd,
    __in  LPARAM lParam
    ) {


    return TRUE;
}

BOOL CALLBACK enumWindowsProc(
    __in  HWND hWnd,
    __in  LPARAM lParam
    ) {

    int length = ::GetWindowTextLength(hWnd);
    if (0 == length) return TRUE;

    TCHAR* bufferA;
    bufferA = new TCHAR[length + 1];
    memset(bufferA, 0, (length + 1) * sizeof(TCHAR));

    TCHAR* bufferB;
    bufferB = new TCHAR[100];
    memset(bufferB, 0, 100 * sizeof(TCHAR));

    GetWindowText(hWnd, bufferA, length + 1);
    GetClassName(hWnd, bufferB, 100);
    tstring windowTitle = tstring(bufferA);
    tstring className = tstring(bufferB);
    delete bufferA;
    delete bufferB;

    if (windowTitle.find(glb_searchWindowTitle) < string::npos &&
        className.find(glb_seachClassName) < string::npos)
            glb_handle = hWnd;

    wcout.clear();

    return TRUE;
}

bool getHandle(wstring searchClassName, wstring searchWindowTitle, HWND &handle)
{
    handle = NULL;
    glb_handle = NULL;
    glb_searchWindowTitle = searchWindowTitle;
    glb_seachClassName = searchClassName;
    BOOL enumeratingWindowsSucceeded = EnumWindows(enumWindowsProc, NULL);

    if (enumeratingWindowsSucceeded)
    {
        if (glb_handle != NULL)
        {
            handle = glb_handle;
            return true;
        }
    }

    glb_handle = NULL;
    glb_searchWindowTitle = L"";
    glb_seachClassName = L"";
    return false;
}

bool getChildHandle(HWND parent_handle, wstring searchClassName, wstring searchWindowTitle, HWND &handle)
{
    handle = NULL;
    glb_handle = NULL;
    glb_searchWindowTitle = searchWindowTitle;
    glb_seachClassName = searchClassName;
    BOOL enumeratingWindowsSucceeded = EnumChildWindows(parent_handle, enumWindowsProc, NULL);

    if (enumeratingWindowsSucceeded)
    {
        if (glb_handle != NULL)
        {
            handle = glb_handle;
            return true;
        }
    }

    glb_handle = NULL;
    glb_searchWindowTitle = L"";
    glb_seachClassName = L"";
    return false;
}

int _tmain(int argc, _TCHAR* argv[])
{
    wcout << TEXT("Enumerating Windows...") << endl;
    HWND handle = NULL;

    //--- Success: gets application handle
    bool success1 = getHandle(L"MetaTrader", L"20", handle);

    cout << "Success1: " << success1 << endl;
    cout << "Result1: " << handle << endl;

    //--- Success: gets navigator window
    bool success2 = getChildHandle(handle, L"", L"Navigator", handle);

    cout << "Success2: " << success2 << endl;
    cout << "Result2: " << handle << endl;

    //--- Success: gets "SysTreeView32" handle
    handle = FindWindowEx(handle, 0, L"SysTreeView32", L"");

    cout << "Result3: " << handle << endl;

    //--- Success: get "SysTreeView32" root nod
    HTREEITEM root = TreeView_GetNextItem(handle, NULL, TVGN_ROOT);
    cout << "root: " << root << endl;

    return 0;
}

Selecting a SysTreeView32 item

(For clarification, when I say selecting a SysTreeView32 item, I'm referring to simulating a double-click operation on a tree node -- similar to how one can double click an icon on their Desktop to open a program)

After looking at the documentation, I'm convinced:

  1. There doesn't exist an explicit message that will simulate double-clicking a node on a tree using the handle to the tree-view item

  2. A possible work around would be to send the TVM_GETITEMRECT message to get the coordinates of the tree node, and then use SendInput() to send a click

Are the above two statements correct?

After implementing Barmak Shemirani's code, I tried to implement #2 above using the same methodology as in Barmak Shemirani's fix. Specifically, I attempted to allocate a Rect struct in the other Application program's memory with VirtualAllocEx(), call the TreeView_GetItemRect macro in my program with a pointer to the rectangle, and read the results with ReadProcessMemory().

However, my program crashes when I call TreeView_GetItemRect(), while passing the pointer to the Rect in the other Apps memory. Most likely, because TreeView_GetItemRect() is trying to write the Rect coordinates to an invalid memory address. This caused me to realize that I don't really understand what the macro is doing:

  1. Hence, checking out the source, I found:

    #define HELLO
    #define TV_FIRST                0x1100      // TreeView messages
    
    #define TVM_GETITEMRECT         (TV_FIRST + 4)
    #define TreeView_GetItemRect(hwnd, hitem, prc, code) \
    (*(HTREEITEM *)(prc) = (hitem), (BOOL)SNDMSG((hwnd), TVM_GETITEMRECT, (WPARAM)(code), (LPARAM)(RECT *)(prc)))
    

I mostly understand everything except for the part before the SNDMSG function:

(*(HTREEITEM *)(prc) = (hitem),

What exactly does the above statement mean? Is this casting the rectangle pointer that I pass to a HTREEITEM pointer, which is somehow causing the program to crash?

Screenshot of console freezing

New code

int _tmain(int argc, _TCHAR* argv[])
{
    wcout << TEXT("Enumerating Windows...") << endl;
    HWND handle = NULL;

    //--- Success: gets application handle
    bool success1 = getHandle(L"MetaTrader", L"20", handle);

    //--- Success: gets navigator window
    bool success2 = getChildHandle(handle, L"", L"Navigator", handle);

    //--- Success: gets "SysTreeView32" handle
    handle = FindWindowEx(handle, 0, L"SysTreeView32", L"");

    //--- Success: get "SysTreeView32" root nod
    HTREEITEM root = TreeView_GetNextItem(handle, NULL, TVGN_ROOT);

    unsigned long pid;

    GetWindowThreadProcessId(handle, &pid);

    HANDLE process = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE |
    PROCESS_QUERY_INFORMATION, FALSE, pid);

    TVITEM item, *_item;

    wchar_t buf[CHAR_BUF_LEN];
    wchar_t *_buf;
    memset(buf, 0, sizeof(buf) / sizeof(buf[0]));

    _item = (TVITEM*)VirtualAllocEx(process, NULL, sizeof(TVITEM), MEM_COMMIT, PAGE_READWRITE);
    _buf = (wchar_t*)VirtualAllocEx(process, NULL, CHAR_BUF_LEN, MEM_COMMIT, PAGE_READWRITE);

    item.cchTextMax = CHAR_BUF_LEN;
    item.pszText = _buf;
    item.mask = TVIF_TEXT;

    //--- find Experts Advisors branch in tree
    HTREEITEM node = TreeView_GetNextItem(handle, root, TVGN_CHILD);
    node = TreeView_GetNextItem(handle, node, TVGN_NEXT);
    node = TreeView_GetNextItem(handle, node, TVGN_NEXT);

    RECT rect, *_rect;

    _rect = (RECT*)VirtualAllocEx(process, NULL, sizeof(RECT), MEM_COMMIT, PAGE_READWRITE);

    rect = { 0 };

    WriteProcessMemory(process, _rect, &rect, sizeof(RECT), NULL);

    //--- step into Expert Advisors
    node = TreeView_GetNextItem(handle, node, TVGN_CHILD);

    //--- target program to open
    wchar_t ea_name[] = L"MACD Sample";

    while (node != NULL)
    {
        ZeroMemory(buf, CHAR_BUF_LEN);

        item.hItem = node;

        //Binds item and _item
        WriteProcessMemory(process, _item, &item, sizeof(TVITEM), NULL);

        TreeView_GetItem(handle, _item);

        //Read buffer back to this program's process memory
        ReadProcessMemory(process, _buf, buf, CHAR_BUF_LEN, NULL);

        //Print program name
        wcout << buf << endl;

        if (wcscmp(ea_name, buf) == 0)
        {
            cout << "Found target program: " << ea_name << endl;
            cout << "get rectangle coordinates: " << TreeView_GetItemRect(handle, node, _rect, TRUE) << endl;
        }

        node = TreeView_GetNextItem(handle, node, TVGN_NEXT);
    }

    VirtualFreeEx(process, _item, 0, MEM_RELEASE);
    VirtualFreeEx(process, _buf, 0, MEM_RELEASE);
    VirtualFreeEx(process, _rect, 0, MEM_RELEASE);

    return 0;
}

回答1:


This is the method you would normally use to read a TreeView item's text:

wchar_t buf[100];
memset(buf, 0, sizeof(buf));
TVITEM item = { 0 };
item.hItem = hitem;
item.cchTextMax = 100;
item.pszText = buf;
item.mask = TVIF_TEXT;
TreeView_GetItem(hwnd, &item);

This will not work in your program. TreeView_GetItem is a macro based on SendMessage, it copies data through LPARAM parameter. But this exchange is not allowed between different processes.

You could spend hours, possibly days, trying to hack it (See this example)

Or you may want to research and see if the target program supports UI Automation


Edit, here is example to get HTREEITEM text. This won't work unless:

  • caller and target program are both 32-bit, or both 64-bit
  • caller and target program are both unicode

If target program is ANSI then change this function to ANSI.

HTREEITEM hitem = TreeView_GetSelection(hwndTree);
if (!hitem)
    debug << "!hitem\n";

const int buflen = 512;

DWORD pid;
GetWindowThreadProcessId(hwndTree, &pid);
HANDLE process = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE 
    | PROCESS_QUERY_INFORMATION, FALSE, pid);
TVITEMEX* ptv = (TVITEMEX*)VirtualAllocEx(process, NULL, sizeof(TVITEMEX), 
    MEM_COMMIT, PAGE_READWRITE);
wchar_t* pbuf = (wchar_t*)VirtualAllocEx(process, NULL, buflen, 
    MEM_COMMIT, PAGE_READWRITE);

TVITEMEX tv = { 0 };
tv.hItem = hitem;
tv.cchTextMax = buflen / 2;
tv.pszText = pbuf;
tv.mask = TVIF_TEXT | TVIF_HANDLE;

WriteProcessMemory(process, ptv, &tv, sizeof(TVITEMEX), NULL);

if (SendMessageW(hwndTree, TVM_GETITEM, 0, (LPARAM)(TVITEMEX*)(ptv)))
{
    wchar_t buf[buflen / 2];
    ReadProcessMemory(process, pbuf, buf, buflen, 0);
    debug << "Result:" << buf << "\n";
}
else
    debug << "!SendMessageW\n";

VirtualFreeEx(process, ptv, 0, MEM_RELEASE);
VirtualFreeEx(process, pbuf, 0, MEM_RELEASE);
CloseHandle(process); //*** I forgot this line before



回答2:


The most voted answer has solved your problem, but I'd like to add some comment on the statement:

(*(HTREEITEM *)(prc) = (hitem),

TVM_GETITEMRECT has explained that :

When sending this message, the lParam parameter contains the handle of the item that the rectangle is being retrieved for.

In macro TreeView_GetItemRect, prc will be replaced by _rect, which is allocated in other process. So the program crashed. For your situation, you can replace the code:

TreeView_GetItemRect(handle, node, _rect, TRUE) 

by:

RECT rect, *_rect;

_rect = (RECT*)VirtualAllocEx(process, NULL, sizeof(RECT), MEM_COMMIT, PAGE_READWRITE);

*(HTREEITEM*)&rect = node;

WriteProcessMemory(process, _rect, &rect, sizeof(RECT), NULL);
SendMessage(handle, TVM_GETITEMRECT, true, (LPARAM)_rect);


来源:https://stackoverflow.com/questions/34703336/how-to-properly-loop-through-get-text-select-systreeview32-window-item

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!