无法使用Managed Win32 API访问System ListView内容

时间:2010-11-26 23:08:57

标签: c# listview screen-scraping winapi

我在C#环境中使用ManagedWindows API: http://mwinapi.sourceforge.net/

过去,我使用下面的代码成功地删除了其他正在运行的程序的类似列表框的部分的内容,其中我遍历键/值对以查找列表项。但是,对于这个特定的项目列表,我可以获得准确数量的项目,但值始终为null!

使用此:

TargetMidWindow.Content.ComponentType

我发现我遇到问题的列表是'listview',而我成功的其他窗口是'detailslistview'以防万一。下面是我找到我想要的数据的代码,除了改变我使用的搜索词之外,这几乎与我的其他成功代码完全相同。此外,如果相关,我试图从中提取数据的程序是MetaTrader4,并且我已经能够成功地从程序的其他部分中删除数据。

    // Find the main window
        SystemWindow[] TopLevel = SystemWindow.AllToplevelWindows;
        SystemWindow TargetTopWindow = SystemWindow.ForegroundWindow;
        foreach (SystemWindow SearchWindow in TopLevel)
        {
            string Title = SearchWindow.Title;
            if (Title.Contains("MetaTrader"))
            {
                TargetTopWindow = SearchWindow;
                break;
            }
        }

        // Find the section where positions are contained
        SystemWindow[] MidLevel = TargetTopWindow.AllDescendantWindows;
        SystemWindow TargetMidWindow = SystemWindow.ForegroundWindow;
        foreach (SystemWindow SearchWindow in MidLevel)
        {
            string ClassName = SearchWindow.ClassName;
            if (ClassName.Contains("SysListView32"))
            {
                SystemWindow ParentWindow = SearchWindow.Parent;
                    if ((ParentWindow.Title.Contains("Terminal")))
                    {
                        TargetMidWindow = SearchWindow;

                    }
            }
        }

        // Get the positions
        Dictionary<string, string> RawValues = new Dictionary<string, string>();
        foreach (KeyValuePair<string, string> KVP in TargetMidWindow.Content.PropertyList)
        {
            string key = KVP.Key;
            string value = KVP.Value;
        }

我需要做些什么特别的事情,以便每个列表项都没有'null'值?

谢谢! 比尔

2 个答案:

答案 0 :(得分:1)

Hmya,使用友好的API包装Windows消息并不困难。 Windows Forms就是一个很好的例子。但是,一旦你开始使用另一个过程,这就有了进入一个非常坚固的墙的诀窍。

读取ListView项目所需的特定消息是LVM_GETITEM。这是那些坚实的墙壁消息之一。传递给SendMessage()的LPARAM参数需要是指向LVITEM结构的指针。控件填充该结构中的字段。问题是,您传递的指针仅在您的进程中有效,而不是拥有该窗口的进程。

修复此问题需要大量的麻烦。您必须分配在该进程内有效的内存。这需要VirtualAllocEx()和ReadProcessMemory()。加上你需要的所有胶水调用来完成这些工作。我假设你使用的这个库是而不是来处理这个问题。很容易找到,grep这些API函数名的源代码文件。

答案 1 :(得分:1)

如果要查找特定SysListView32窗口的正确句柄,则需要从正确的窗口层次结构开始。从代码片段中,您实际上找不到正确的句柄来从SysListView32窗口中检索引用。这就是你收到空值的原因。您最好运行spy ++并为您的特定代理和构建确定Metatrader终端的正确窗口结构。我发现某些窗口的构建版本以及某些代理程序之间的类别不同,但程度较小。

您正在寻找具体的报价窗口层次结构,如下所示:

Metatrader -> Market Watch -> Market Watch -> SysListView32

相比之下,目前您正在查看代码:

Metatrader -> Terminal -> (many sub-windows with SysListView32 class)

右边的每个级别都是左侧窗口的子窗口。

找到父“Metatrader”窗口,然后链接查找子窗口,直到找到SysListView32。如果使用spy ++,则可以读取SysListView32父窗口(市场监视)的类,并使用它来枚举窗口以查找正确的SysListView32窗口。仅供参考,构建419的正确市场观察类名称为:

Afx:00400000:b:00010003:00000000:00000000

找到正确的窗口后,您可以使用当前组件提取其内容。我没有尝试过,我希望从ListView模块中移植VB6中的一些代码,这实际上涉及史诗hackery。 ;)我可以看看.NET Managed Windows API,看看这是否有助于简化流程。

但与此同时,如果您必须进入低级别,以下VB6源代码应该可以帮助您了解所涉及的内容。这是相当先进的材料,祝你好运!

Public Function GetListviewItem(ByVal hWindow As Long, ByVal pColumn As Long, ByVal pRow As Long) As String
Dim result              As Long
Dim myItem              As LV_ITEMA
Dim pHandle             As Long
Dim pStrBufferMemory    As Long
Dim pMyItemMemory       As Long
Dim strBuffer()         As Byte
Dim index               As Long
Dim tmpString           As String
Dim strLength           As Long

Dim ProcessID As Long, ThreadID As Long
ThreadID = GetWindowThreadProcessId(hWindow, ProcessID)
'**********************
'init the string buffer
'**********************
ReDim strBuffer(MAX_LVMSTRING)

'***********************************************************
'open a handle to the process and allocate the string buffer
'***********************************************************
pHandle = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE, False, ProcessID)
pStrBufferMemory = VirtualAllocEx(pHandle, 0, MAX_LVMSTRING, MEM_COMMIT, PAGE_READWRITE)

'************************************************************************************
'initialize the local LV_ITEM structure
'The myItem.iSubItem member is set to the index of the column that is being retrieved
'************************************************************************************
myItem.mask = LVIF_TEXT
myItem.iSubItem = pColumn
myItem.pszText = pStrBufferMemory
myItem.cchTextMax = MAX_LVMSTRING

'**********************************************************
'write the structure into the remote process's memory space
'**********************************************************
pMyItemMemory = VirtualAllocEx(pHandle, 0, Len(myItem), MEM_COMMIT, PAGE_READWRITE)
result = WriteProcessMemory(pHandle, pMyItemMemory, myItem, Len(myItem), 0)

'*************************************************************
'send the get the item message and write back the memory space
'*************************************************************
result = SendMessage(hWindow, LVM_GETITEMTEXT, pRow, ByVal pMyItemMemory)
result = ReadProcessMemory(pHandle, pStrBufferMemory, strBuffer(0), MAX_LVMSTRING, 0)
result = ReadProcessMemory(pHandle, pMyItemMemory, myItem, Len(myItem), 0)

'**************************************************
'turn the byte array into a string and send it back
'**************************************************
For index = LBound(strBuffer) To UBound(strBuffer)
    If Chr(strBuffer(index)) = vbNullChar Then Exit For
    tmpString = tmpString & Chr(strBuffer(index))
Next index

tmpString = Trim(tmpString)

'**************************************************
'deallocate the memory and close the process handle
'**************************************************
result = VirtualFreeEx(pHandle, pStrBufferMemory, 0, MEM_RELEASE)
result = VirtualFreeEx(pHandle, pMyItemMemory, 0, MEM_RELEASE)

result = CloseHandle(pHandle)

If Len(tmpString) > 0 Then GetListviewItem = tmpString

End Function