선택
MFC SysTreeView32 컨트롤 내 node 텍스트 값 추출하기 본문
카테고리가 애매하긴한데.. C#은 거들 정도고 실제로는 Win32API를 사용했기 때문에 카테고리는 C..
사용하는 MFC 프로그램 하나가 .dgn 파일을 읽어 컨트롤을 그리고 있는데, dgn 파일이 커스텀되어 있기도 하고, 이에 대해 분석이 어려워
MFC 프로그램의 컨트롤 영역을 찾아서 원하는 항목의 데이터를 가져오는 것 목표로 시작했었다.
cTreeCtrl이였고, header가 여러개고 그에 맞는 node가 적절한 depth로 구성된 리스트 뷰였는데..
결국 MFC 내에서 텍스트를 입력하는 게 아니고, MFC 내에서 직접 컨트롤을 draw하는 방식으로 제작되어 써먹지는 못했지만 아래와 같은 방법으로 접근하였다.
찾아보다가 UI Automation이라는 편한 기술이 있는데, 나처럼 Win32API를 호출해 쓰지 말고 UIAutomation처럼 지원하는 기능이 있으니 이거 활용하면 쉽게 접근할 수 있을 듯 하다.
C++ 기준 UIAutomation을 사용하여 Element내 텍스트를 추출하는 방법이다.
#include <UIAutomation.h>
#include <iostream>
int main()
{
CoInitialize(NULL);
// UI Automation 객체 생성
IUIAutomation *pAutomation;
CoCreateInstance(__uuidof(CUIAutomation), NULL, CLSCTX_INPROC_SERVER, __uuidof(IUIAutomation), (void**)&pAutomation);
// 현재 포커스된 요소 가져오기
IUIAutomationElement *pFocusedElement;
pAutomation->GetFocusedElement(&pFocusedElement);
// 해당 요소에서 이름(텍스트) 가져오기
BSTR name;
pFocusedElement->get_CurrentName(&name);
// 결과 출력
wprintf(L"Focused element: %s\n", name);
// 리소스 해제
SysFreeString(name);
pFocusedElement->Release();
pAutomation->Release();
CoUninitialize();
return 0;
}
C#은 이렇게.
using System.Windows.Automation;
AutomationElement rootElement = AutomationElement.RootElement; // Get the root element
AutomationElement targetWindow = FindElementByName(rootElement, "YourTargetWindowName");
AutomationElement treeView = FindElementByControlType(targetWindow, ControlType.Tree);
IterateTreeView(treeView);
AutomationElement FindElementByName(AutomationElement parent, string name)
{
Condition condition = new PropertyCondition(AutomationElement.NameProperty, name);
return parent.FindFirst(TreeScope.Descendants, condition);
}
AutomationElement FindElementByControlType(AutomationElement parent, ControlType controlType)
{
Condition condition = new PropertyCondition(AutomationElement.ControlTypeProperty, controlType);
return parent.FindFirst(TreeScope.Descendants, condition);
}
void IterateTreeView(AutomationElement treeView)
{
TreeWalker walker = TreeWalker.ControlViewWalker;
AutomationElement element = walker.GetFirstChild(treeView);
while (element != null)
{
// Get the item name
string name = element.Current.Name;
Console.WriteLine(name);
// Move to the next item
element = walker.GetNextSibling(element);
}
}
UIAutomation을 사용하면 아래 접근 방식을 모두 스킵할 수 있다.
하지만 저걸 몰랐기 때문에.. 나는 Win32API로 아래 접근을 시도했는데, 결과는 위랑 같았다는게 문제 -_-
다른 프로그램의 SysTreeView32 컨트롤 내 노드의 값을 가져와야하기 때문에 이렇게 접근했다.
1. VirtualAllocEx, ReadProcessMemory, WriteProcessMemory 3개의 API를 추가로 사용했다.
2. 물론 위 3개를 사용하기 위해서, 다른 프로세스의 핸들 값을 알아야 하기 때문에 OpenProces 사용은 덤.
권한과 플래그를 셋팅한 다음에 (1) 항목으로 접근하였다.
아래 코드처럼 작성하게 되면, 대상의 TreeNodeText를 가져올 수 있게 됐다.
[C#]
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, uint dwSize, out int lpNumberOfBytesRead);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out int lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint dwFreeType);
private const uint MEM_COMMIT = 0x1000;
private const uint MEM_RELEASE = 0x8000;
private const uint PAGE_READWRITE = 0x04;
public static string GetTreeNodeTextA(IntPtr hProcess, IntPtr hTreeView, IntPtr hItem)
{
IntPtr pText = VirtualAllocEx(hProcess, IntPtr.Zero, 256, MEM_COMMIT, PAGE_READWRITE);
if (pText == IntPtr.Zero)
return null;
TVITEM tvItem = new TVITEM();
tvItem.hItem = hItem;
tvItem.mask = TVIF_TEXT;
tvItem.pszText = pText;
tvItem.cchTextMax = 256;
byte[] buffer = new byte[Marshal.SizeOf(typeof(TVITEM))];
IntPtr ptrTvItem = Marshal.AllocHGlobal(buffer.Length);
Marshal.StructureToPtr(tvItem, ptrTvItem, false);
Marshal.Copy(ptrTvItem, buffer, 0, buffer.Length);
Marshal.FreeHGlobal(ptrTvItem);
WriteProcessMemory(hProcess, pText, buffer, (uint)buffer.Length, out _);
SendMessage(hTreeView, TVM_GETITEMA, IntPtr.Zero, ref tvItem);
byte[] resultBuffer = new byte[256];
ReadProcessMemory(hProcess, pText, resultBuffer, 256, out _);
string itemText = System.Text.Encoding.Default.GetString(resultBuffer).TrimEnd('\0');
VirtualFreeEx(hProcess, pText, 0, MEM_RELEASE);
return itemText;
}
다만 이렇게 하려면 hProcess의 값 말고도 hTreeView, 및 아이템의 값을 가져와야 한다.
저거만 딸랑 만든다고 해도 접근할 방법이 없으니 말이지..
그래서 아래처럼 가져와서 쉐킷하면 써먹을 수 있다.
const uint TVM_GETNEXTITEM = TV_FIRST + 10;
const uint TVGN_ROOT = 0x0;
const uint TVGN_NEXT = 0x1;
const uint TVGN_CHILD = 0x4;
public void ReadTreeView(IntPtr hTreeView, int processId)
{
IntPtr hProcess = Win32Api.OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, false, processId);
if (hProcess == IntPtr.Zero) return;
TraverseTreeItems(hProcess, hTreeView);
Win32Api.CloseHandle(hProcess);
}
private void TraverseTreeItems(IntPtr hProcess, IntPtr hTreeView)
{
IntPtr hCurrentItem = Win32Api.SendMessage(hTreeView, TVM_GETNEXTITEM, (IntPtr)TVGN_ROOT, IntPtr.Zero);
TraverseChildItems(hProcess, hTreeView, hCurrentItem);
}
private void TraverseChildItems(IntPtr hProcess, IntPtr hTreeView, IntPtr hParentItem)
{
while (hParentItem != IntPtr.Zero)
{
// 아이템의 텍스트를 가져옴
string itemText = GetTreeNodeText(hProcess, hTreeView, hParentItem);
Console.WriteLine(itemText);
// 현재 아이템의 첫 번째 자식을 가져옴
IntPtr hChildItem = Win32Api.SendMessage(hTreeView, TVM_GETNEXTITEM, (IntPtr)TVGN_CHILD, hParentItem);
if (hChildItem != IntPtr.Zero)
{
TraverseChildItems(hProcess, hTreeView, hChildItem);
}
// 다음 형제 아이템으로 이동
hParentItem = Win32Api.SendMessage(hTreeView, TVM_GETNEXTITEM, (IntPtr)TVGN_NEXT, hParentItem);
}
}
결론은 UIAutomation 쓰자.
'프로그래밍 > C#, .NET' 카테고리의 다른 글
[C#] 5. WebView2 양방향 통신 (AddHostObjectToScript, ExecuteScriptAsync) (0) | 2022.08.02 |
---|---|
[C#] 4. WebView2 속성 설정으로 WebView 런타임 Fixed Version 사용 (0) | 2022.08.02 |
[C#] 3. WebView2를 이용해 사이트 호출 (0) | 2022.08.02 |
[C#] 2. WebView2 패키지 설치 (Nuget) (0) | 2022.08.02 |
[C#] 1. WebView2 구성 및 설치 (0) | 2022.08.02 |