project:peekwindow
차이
문서의 선택한 두 판 사이의 차이를 보여줍니다.
양쪽 이전 판이전 판다음 판 | 이전 판 | ||
project:peekwindow [2012/12/19 15:20] – 127.0.0.1 | project:peekwindow [2015/08/08 20:22] (현재) – [설명] changwoo | ||
---|---|---|---|
줄 1: | 줄 1: | ||
+ | ====== 파이썬을 이용하여 다른 윈도우 엿보기 ====== | ||
+ | 이번에는 파이썬과 pywin32 확장을 이용하여 내 프로그램의 윈도우 상황을 ' | ||
+ | ' | ||
+ | |||
+ | ===== 배경 설명 ===== | ||
+ | 우선 배경 지식부터 간단히 살펴 보겠습니다. 현재 거의 모든 컴퓨터는 멀티태스킹으로 돌아간다는 사실은 알고 계시지요? | ||
+ | |||
+ | ==== 윈도우와 프로세스, | ||
+ | 운영체제가 어떻게 이렇게 다양한 작업을 관리하는지에 대해 설명하려면 아예 운영체제 이론 시간이 되어 버리므로 생략하겠습니다. | ||
+ | 완벽히 들어맞는 명제는 아닙니다만, | ||
+ | 아까는 작업을 동시에 처리한다고 했으나, 사실 컴퓨터는 여러 가지를 정말 ' | ||
+ | |||
+ | 그러면 정말 내 컴퓨터에 많은 프로세스들이 작업중이란 사실을 확인해 보도록 하지요. 윈도우의 경우 Ctrl+Shift+Esc를 누르거나 윈도우+R키를 누르고 ' | ||
+ | |||
+ | 아무튼 이렇게 운영체제 안에서는 여러 프로세스들이 각자의 일을 정해진대로 묵묵히 수행하고 있습니다. 아예 운영체제 자체가 여러 프로세스로 구성되어 있습니다. 사실 운영체제 내부에서는 절대로 멈추어서는 안되는 몇몇 중요한 프로세스들이 있습니다. 왠만한 프로세스들이 말썽을 일으키면 그냥 에러 메시지를 보는 정도로 그치지만, | ||
+ | |||
+ | 지금 여러분들이 보고 있는 웹브라우저 또한 하나의 프로세스입니다. 그리고 또 하나 중요한 사실이 있습니다. 웹브라우저라는 프로세스 안에는 다시 여러 작은 작업들이 동시에 돌아가고 있는 중이란 것입니다. 웹페이지를 보여주고, | ||
+ | |||
+ | 스레드라는 존재를 염두에 두고 사용중인 웹브라우저를 다시 한 번 살펴보세요. 정말 여러 가지 일들이 동시에 처리되고 있는지 확인되시나요? | ||
+ | |||
+ | ==== 윈도우의 컨트롤 ==== | ||
+ | 윈도우 창에는 프로그램이 사용자에게 요구하는 사항들을 합리적으로 입력 받기 위한 여러 장치들이 있습니다. 간단히 말해 버튼, 콤보 상자, 리스트 상자, 입력 박스 등등 ... 이런 것들을 ' | ||
+ | |||
+ | 이제서야 우리가 하고 싶은 일에 대해 좀 더 구체적인 설명을 할 수 있을 것 같습니다. 아까는 "내 프로그램의 윈도우 상황을 엿본다" | ||
+ | * 내 윈도우(운영체제)에 돌아가는 프로세스 중, 원하는 프로세스에 접근한다. | ||
+ | * 프로세스 내부에 있는 여러 스레드 중에서, 컨트롤에 해당하는 스레드를 찾는다. | ||
+ | * 컨트롤(스레드)의 정보를 얻어낸다. | ||
+ | |||
+ | 사실 이런 일을 해 주는 유틸리티는 이미 널리고 널려 있습니다. 가장 유명한 것으로는 Spy++를 들 수 있겠네요. Spy++를 실행하면 내 컴퓨터에 돌아가는 윈도우의 정보를 낱낱이 볼 수 있습니다. 정말 낱낱이요. 다음에 설명할 메시지 이벤트까지 다 확인할 수 있습니다. 조금 설명이 어려워 일반적으로 접근하기 까다로웠을 뿐, 거의 모든 프로그램들은 이렇게 낱낱이 정보가 공개된 채 돌아가고 있는 것이었습니다. | ||
+ | |||
+ | 우리의 목적은 Spy++와 비슷하지만, | ||
+ | |||
+ | ==== 윈도우의 작동 방식 ==== | ||
+ | 혹시라도 Win32API를 공부해 보셨다든지, | ||
+ | |||
+ | 프로그램들은 일반적으로 사용자가 특별한 명령을 내리기 전까지는 가만히 대기하고 있지요? 몇날 며칠이건 입력이 없으면 무조건 대기합니다. 미리 약속되지 않은 이상 자기 멋대로 먼저 동작하는 일은 절대로 없습니다. 반드시 사용자가 무언가 지시해야 비로소 다음으로 진행됩니다. | ||
+ | |||
+ | 사용자가 컴퓨터에게 무언가를 한 일(키보드를 누른다, 마우스를 이동시킨다)을 일컬어 ' | ||
+ | |||
+ | 한 이벤트가 발생하면 그 이벤트에 대해 운영체제는 ' | ||
+ | |||
+ | 예를 들어 버튼을 클릭한다면, | ||
+ | |||
+ | 쓰고 나니 설명이 매우 많이 복잡해졌습니다. 저는 여기까지 설명하도록 하겠습니다.보다 자세한 설명은 ' | ||
+ | |||
+ | 요점은 운영체제의 메시지 큐에 사용자가 입력한 이벤트에 대한 적절한 메시지가 담기고, 그것을 처리하는 것이 반복되면서 프로그램이 진행된다는 것입니다. 여기서 조금만, 조금만 더 심사숙고해보도록 하지요. | ||
+ | * 원인과 결과로 생각하면, | ||
+ | * 사용자의 이벤트(원인) -> 운영체제에서 메시지 생성(결과) | ||
+ | * 운영체제의 메시지 전달(원인) -> 프로그램 동작(결과) | ||
+ | * 그렇다면, | ||
+ | * 사용자의 이벤트를 기반으로 하지 않고도 유사한 메시지를 생성할 수 있을까? | ||
+ | * 유사하게 메시지가 생성된다면, | ||
+ | |||
+ | 설명이 많이 길어지므로 결론만 적고 끝내도록 하겠습니다. 네, 됩니다. 그래서 우리가 프로그래밍을 하지요. :) | ||
+ | |||
+ | |||
+ | ===== 프로그래밍하기 ===== | ||
+ | 이제 파이썬을 기반으로 프로그래밍을 시작하도록 하겠습니다. | ||
+ | 우선 [[http:// | ||
+ | |||
+ | ==== 실행중인 윈도우를 열거하기 ==== | ||
+ | <code python enumwindows.py> | ||
+ | # -*- coding: | ||
+ | # 모든 최상위 수준의 윈도우를 나열합니다. | ||
+ | import win32gui | ||
+ | |||
+ | def EnumWindowsHandler(hwnd, | ||
+ | wintext = win32gui.GetWindowText(hwnd) | ||
+ | print "%08X: %s" % (hwnd, wintext) | ||
+ | |||
+ | if __name__ == ' | ||
+ | win32gui.EnumWindows(EnumWindowsHandler, | ||
+ | </ | ||
+ | Spy++에서 출력된 결과(Spy-> | ||
+ | |||
+ | 여기서 한 가지 반드시 짚고 넘어가야 할 중요한 사항이 있습니다. 바로 ' | ||
+ | |||
+ | |||
+ | |||
+ | ==== 타겟 윈도우의 모든 컨트롤들을 찾아내기 | ||
+ | |||
+ | 우선 타겟이 되는 윈도우를 정해야겠지요? | ||
+ | {{ : | ||
+ | |||
+ | 윈도우 상단의 문자열에서 ' | ||
+ | {{ : | ||
+ | |||
+ | <code python enumchildwindows.py> | ||
+ | # -*- coding: | ||
+ | # TeraCopy 내부의 모든 윈도우 객체를 나열합니다. | ||
+ | import win32gui | ||
+ | import pywintypes | ||
+ | import sys | ||
+ | |||
+ | # 부모 윈도우의 핸들을 검사합니다. | ||
+ | class WindowFinder: | ||
+ | def __init__(self, | ||
+ | try: | ||
+ | win32gui.EnumWindows(self.__EnumWindowsHandler, | ||
+ | except pywintypes.error as e: | ||
+ | # 발생된 예외 중 e[0]가 0이면 callback이 멈춘 정상 케이스 | ||
+ | if e[0] == 0: pass | ||
+ | |||
+ | def __EnumWindowsHandler(self, | ||
+ | wintext = win32gui.GetWindowText(hwnd) | ||
+ | if wintext.find(extra) != -1: | ||
+ | self.__hwnd = hwnd | ||
+ | return pywintypes.FALSE # FALSE는 예외를 발생시킵니다. | ||
+ | |||
+ | def GetHwnd(self): | ||
+ | return self.__hwnd | ||
+ | |||
+ | __hwnd = 0 | ||
+ | |||
+ | # 자식 윈도우의 핸들 리스트를 검사합니다. | ||
+ | class ChildWindowFinder: | ||
+ | def __init__(self, | ||
+ | try: | ||
+ | win32gui.EnumChildWindows(parentwnd, | ||
+ | except pywintypes.error as e: | ||
+ | if e[0] == 0: pass | ||
+ | |||
+ | def __EnumChildWindowsHandler(self, | ||
+ | self.__childwnds.append(hwnd) | ||
+ | |||
+ | def GetChildrenList(self): | ||
+ | return self.__childwnds | ||
+ | |||
+ | __childwnds = [] | ||
+ | |||
+ | # windowname을 가진 윈도우의 모든 자식 윈도우 리스트를 얻어낸다. | ||
+ | def GetChildWindows(windowname): | ||
+ | |||
+ | # TeraCopy의 window handle을 검사한다. | ||
+ | teracopyhwnd = WindowFinder(' | ||
+ | |||
+ | # Teracopy의 모든 child window handle을 검색한다. | ||
+ | childrenlist = ChildWindowFinder(teracopyhwnd).GetChildrenList() | ||
+ | |||
+ | return teracopyhwnd, | ||
+ | |||
+ | # main 입니다. | ||
+ | def main(argv): | ||
+ | hwnd, childwnds = GetChildWindows(' | ||
+ | print "%X %s" % (hwnd, win32gui.GetWindowText(hwnd)) | ||
+ | |||
+ | print " | ||
+ | print " | ||
+ | |||
+ | for child in childwnds: | ||
+ | ctrl_id | ||
+ | wnd_clas = win32gui.GetClassName(child) | ||
+ | wnd_text = win32gui.GetWindowText(child) | ||
+ | print "%08X %6d\t%s\t%s" | ||
+ | |||
+ | return 0 | ||
+ | |||
+ | if __name__ == ' | ||
+ | sys.exit(main(sys.argv)) | ||
+ | |||
+ | </ | ||
+ | |||
+ | 코드의 결과입니다. | ||
+ | 4A08B4 [일시중지됨] 복사: 17.2% (32 MB/s) - TeraCopy | ||
+ | HWND | ||
+ | =========================================== | ||
+ | 00250844 | ||
+ | 00220418 | ||
+ | 0014089C | ||
+ | 0015072C | ||
+ | 001601BC | ||
+ | 00150102 | ||
+ | 00220348 | ||
+ | 002C0DAE | ||
+ | 000E0730 | ||
+ | 00270806 | ||
+ | 00160944 | ||
+ | 00100918 | ||
+ | 00200874 | ||
+ | 002702EE | ||
+ | 00190296 | ||
+ | 001108DC | ||
+ | 001D0DC6 | ||
+ | 00170310 | ||
+ | 00210878 | ||
+ | 00140868 | ||
+ | 001508B8 | ||
+ | 000906EE | ||
+ | 00090708 | ||
+ | ==== ListView의 내용을 추출해내기 ==== | ||
+ | 앞서 컨트롤들을 열거한 것만으로도 버튼(Button) 컨트롤과 라벨(Static) 컨트롤의 내용을 가져올 수 있었습니다. | ||
+ | 그러나 제가 원한 ' | ||
+ | |||
+ | 바로 아래와 두 줄이 리스트 뷰 컨트롤의 존재를 보여주고 있습니다. | ||
+ | 001D0DC6 | ||
+ | 00170310 | ||
+ | SysListView32에 리스트뷰가, | ||
+ | |||
+ | 한편 내용을 추출하기 위해서는 리스트뷰에 대한 약간의 사전 지식과 배경 지식이 있어야 합니다. Platform SDK에서는 [[http:// | ||
+ | |||
+ | <code python ListViewItems.py> | ||
+ | # -*- coding: | ||
+ | import win32gui | ||
+ | import win32api | ||
+ | import commctrl | ||
+ | import struct | ||
+ | import ctypes | ||
+ | |||
+ | from win32con import PAGE_READWRITE, | ||
+ | |||
+ | GetWindowThreadProcessId = ctypes.windll.user32.GetWindowThreadProcessId | ||
+ | VirtualAllocEx | ||
+ | VirtualFreeEx | ||
+ | OpenProcess | ||
+ | WriteProcessMemory | ||
+ | ReadProcessMemory | ||
+ | |||
+ | def GetListViewItems(hwnd, | ||
+ | # Allocate virtual memory inside target process | ||
+ | pid = ctypes.create_string_buffer(4) | ||
+ | p_pid = ctypes.addressof(pid) | ||
+ | |||
+ | GetWindowThreadProcessId(hwnd, | ||
+ | hProcHnd = OpenProcess(PROCESS_ALL_ACCESS, | ||
+ | pLVI = VirtualAllocEx(hProcHnd, | ||
+ | pBuffer = VirtualAllocEx(hProcHnd, | ||
+ | |||
+ | # Prepare an LVITEM record and write it to target process memory | ||
+ | lvitem_str | ||
+ | lvitem_buffer = ctypes.create_string_buffer(lvitem_str) | ||
+ | copied = ctypes.create_string_buffer(4) | ||
+ | p_copied = ctypes.addressof(copied) | ||
+ | WriteProcessMemory(hProcHnd, | ||
+ | |||
+ | # iterate items in the SysListView32 control | ||
+ | num_items = win32gui.SendMessage(hwnd, | ||
+ | |||
+ | item_texts = [] | ||
+ | for item_index in range(num_items): | ||
+ | win32gui.SendMessage(hwnd, | ||
+ | target_buff = ctypes.create_string_buffer(4096) | ||
+ | ReadProcessMemory(hProcHnd, | ||
+ | item_texts.append(target_buff.value) | ||
+ | |||
+ | VirtualFreeEx(hProcHnd, | ||
+ | VirtualFreeEx(hProcHnd, | ||
+ | win32api.CloseHandle(hProcHnd) | ||
+ | return item_texts | ||
+ | </ | ||
+ | |||
+ | 그럼 이전의 enumwindowtext.py는 아래와 같이 변경해 봅시다. ' | ||
+ | <code python enumwindowtext2.py> | ||
+ | # -*- coding: | ||
+ | # TeraCopy 내부의 모든 윈도우 객체를 나열합니다. | ||
+ | import win32gui | ||
+ | import commctrl | ||
+ | import pywintypes | ||
+ | import struct, array | ||
+ | import sys | ||
+ | from ListViewItems import GetListViewItems | ||
+ | |||
+ | # 부모 윈도우의 핸들을 검사합니다. | ||
+ | class WindowFinder: | ||
+ | def __init__(self, | ||
+ | try: | ||
+ | win32gui.EnumWindows(self.__EnumWindowsHandler, | ||
+ | except pywintypes.error as e: | ||
+ | # 발생된 예외 중 e[0]가 0이면 callback이 멈춘 정상 케이스 | ||
+ | if e[0] == 0: pass | ||
+ | |||
+ | def __EnumWindowsHandler(self, | ||
+ | wintext = win32gui.GetWindowText(hwnd) | ||
+ | if wintext.find(extra) != -1: | ||
+ | self.__hwnd = hwnd | ||
+ | return pywintypes.FALSE # FALSE는 예외를 발생시킵니다. | ||
+ | |||
+ | def GetHwnd(self): | ||
+ | return self.__hwnd | ||
+ | |||
+ | __hwnd = 0 | ||
+ | |||
+ | # 자식 윈도우의 핸들 리스트를 검사합니다. | ||
+ | class ChildWindowFinder: | ||
+ | def __init__(self, | ||
+ | try: | ||
+ | win32gui.EnumChildWindows(parentwnd, | ||
+ | except pywintypes.error as e: | ||
+ | if e[0] == 0: pass | ||
+ | |||
+ | def __EnumChildWindowsHandler(self, | ||
+ | self.__childwnds.append(hwnd) | ||
+ | |||
+ | def GetChildrenList(self): | ||
+ | return self.__childwnds | ||
+ | |||
+ | __childwnds = [] | ||
+ | |||
+ | # windowname을 가진 윈도우의 모든 자식 윈도우 리스트를 얻어낸다. | ||
+ | def GetChildWindows(windowname): | ||
+ | |||
+ | # TeraCopy의 window handle을 검사한다. | ||
+ | teracopyhwnd = WindowFinder(' | ||
+ | |||
+ | # Teracopy의 모든 child window handle을 검색한다. | ||
+ | childrenlist = ChildWindowFinder(teracopyhwnd).GetChildrenList() | ||
+ | |||
+ | return teracopyhwnd, | ||
+ | |||
+ | # main 입니다. | ||
+ | def main(argv): | ||
+ | hwnd, childwnds = GetChildWindows(' | ||
+ | listviewCtrl | ||
+ | |||
+ | # TeraCopy의 윈도우 핸들 | ||
+ | print "%X %s" % (hwnd, win32gui.GetWindowText(hwnd)) | ||
+ | |||
+ | # TeraCopy 자식 윈도우의 핸들 | ||
+ | print " | ||
+ | print " | ||
+ | |||
+ | for child in childwnds: | ||
+ | ctrl_id | ||
+ | wnd_clas = win32gui.GetClassName(child) | ||
+ | wnd_text = win32gui.GetWindowText(child) | ||
+ | print "%08X %6d\t%s\t%s" | ||
+ | |||
+ | if wnd_clas == ' | ||
+ | listviewCtrl = child | ||
+ | |||
+ | itemlist = GetListViewItems(listviewCtrl) | ||
+ | |||
+ | for itm in itemlist: | ||
+ | print itm | ||
+ | |||
+ | return 0 | ||
+ | |||
+ | if __name__ == ' | ||
+ | sys.exit(main(sys.argv)) | ||
+ | |||
+ | </ | ||
+ | |||
+ | 실행하면 TeraCopy의 파일 목록(1열)이 출력됩니다. GetListViewItems의 두 번째 인자는 생략되어 있지만, 명시하면 각 열의 목록을 얻어낼 수 있습니다. | ||
+ | |||
+ | ==== 타겟 윈도우에 특정 메시지 전달하기 ==== | ||
+ | 위 예제 코드에서 이 함수를 눈여겨 보시기 바랍니다. | ||
+ | win32gui.SendMessage( ... ) | ||
+ | 이 함수가 메시지 큐에 특정 메시지를 전달하는 함수입니다. 메시지의 목록은 일일이 열거하기 힘듭니다. MSDN중 [[http:// | ||
+ | 이 함수를 이용해 버튼을 클릭하는 효과, 체크박스를 클릭하는 효과 등을 프로그램 명령으로 해낼 수 있습니다. 이는 여러분께 맡깁니다. | ||
+ | |||
+ | ===== 결론 ===== | ||
+ | 윈도우는 멀티태스킹, | ||
+ | |||
+ | 윈도우 운영체제는 메시지 전달 기반으로 동작합니다. 운영체제는 메시지 큐에 메시지를 저장하고 각 메시지를 프로그램에 전달합니다. 프로그램은 전달받은 메시지를 해석해 미리 메시지를 받았을 때 약속된 사항을 수행합니다. 메시지는 이벤트에 의해 발생합니다. 이벤트란 마우스를 움직이거나 키보드를 움직이는 등의 여러 ' | ||
+ | |||
+ | 한편 우리가 만일 각 프로세스의 핸들에 접근할 수 있고 어떤 메시지를 주고 받는지에 대한 정보를 안다면, 우리는 이벤트와 관계없이 프로그래밍을 통해 메시지를 생성하여 그 프로세스(스레드)를 제어할 수도 있다는 것을 보았습니다. | ||
+ | |||
+ | 가벼운 마음으로 시작하였는데, | ||
+ | |||
+ | ===== 2015년 08월 09일 내용 추가 ===== | ||
+ | 한 클리앙 회원분께서 이메일로 " | ||
+ | 예전 파이썬을 막 시작할 때 코드를 잠시 보다가 개정도 할 겸, 답변도 드릴 겸 코드를 조금 수정하기로 맘 먹었습니다. | ||
+ | |||
+ | ==== ListView Peek ==== | ||
+ | 타겟이 되는 리스트뷰에 적절히 원하는 이벤트를 날려주는 코드. | ||
+ | |||
+ | <code python listview_peek.py> | ||
+ | # -*- coding: utf-8 -*- | ||
+ | import win32gui | ||
+ | import win32api | ||
+ | import commctrl | ||
+ | import ctypes | ||
+ | from win32con import PAGE_READWRITE, | ||
+ | |||
+ | GetWindowThreadProcessId = ctypes.windll.user32.GetWindowThreadProcessId | ||
+ | VirtualAllocEx = ctypes.windll.kernel32.VirtualAllocEx | ||
+ | VirtualFreeEx = ctypes.windll.kernel32.VirtualFreeEx | ||
+ | OpenProcess = ctypes.windll.kernel32.OpenProcess | ||
+ | WriteProcessMemory = ctypes.windll.kernel32.WriteProcessMemory | ||
+ | ReadProcessMemory = ctypes.windll.kernel32.ReadProcessMemory | ||
+ | |||
+ | |||
+ | class LVItem(ctypes.Structure): | ||
+ | """ | ||
+ | LVITEM structure: https:// | ||
+ | |||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | Total 9 items | ||
+ | """ | ||
+ | _fields_ = [ | ||
+ | (" | ||
+ | (" | ||
+ | (" | ||
+ | (" | ||
+ | (" | ||
+ | (" | ||
+ | (" | ||
+ | (" | ||
+ | (" | ||
+ | ] | ||
+ | |||
+ | |||
+ | class ListViewPeek(object): | ||
+ | |||
+ | def __init__(self, | ||
+ | self.hwnd = hwnd | ||
+ | self.pid = ctypes.c_uint() | ||
+ | |||
+ | GetWindowThreadProcessId(self.hwnd, | ||
+ | self.process_handle = OpenProcess(PROCESS_ALL_ACCESS, | ||
+ | self.p_lvi = VirtualAllocEx(self.process_handle, | ||
+ | self.p_buf = VirtualAllocEx(self.process_handle, | ||
+ | |||
+ | def __del__(self): | ||
+ | VirtualFreeEx(self.process_handle, | ||
+ | VirtualFreeEx(self.process_handle, | ||
+ | win32api.CloseHandle(self.process_handle) | ||
+ | |||
+ | def get_item_count(self): | ||
+ | return win32gui.SendMessage(self.hwnd, | ||
+ | |||
+ | def get_list_items(self): | ||
+ | init_list = [0, 0, 0, 0, 0, self.p_buf, 4096, 0, 0] | ||
+ | self.init_p_lvi(init_list) | ||
+ | |||
+ | num_items = self.get_item_count() | ||
+ | items = [] | ||
+ | extraction_buffer = ctypes.create_string_buffer(4096) | ||
+ | |||
+ | for i in xrange(num_items): | ||
+ | win32gui.SendMessage(self.hwnd, | ||
+ | self.__read_from_buffer(self.p_buf, | ||
+ | items.append(extraction_buffer.value) | ||
+ | return items | ||
+ | |||
+ | def select_item(self, | ||
+ | """ | ||
+ | index: | ||
+ | selected: | ||
+ | """ | ||
+ | lv_item_list = [ | ||
+ | commctrl.LVIF_STATE, | ||
+ | 0, | ||
+ | 0, | ||
+ | commctrl.LVIS_SELECTED if selected else 0, | ||
+ | commctrl.LVIS_SELECTED, | ||
+ | 0, | ||
+ | 0, | ||
+ | 0, | ||
+ | 0, | ||
+ | ] | ||
+ | self.init_p_lvi(lv_item_list) | ||
+ | return win32gui.SendMessage(self.hwnd, | ||
+ | |||
+ | def init_p_lvi(self, | ||
+ | lv_item_buf = LVItem(*init_list) | ||
+ | return self.__write_to_buffer(self.p_lvi, | ||
+ | |||
+ | def __write_to_buffer(self, | ||
+ | copied = ctypes.c_uint() | ||
+ | p_copied = ctypes.addressof(copied) | ||
+ | WriteProcessMemory(self.process_handle, | ||
+ | return copied.value | ||
+ | |||
+ | def __read_from_buffer(self, | ||
+ | copied = ctypes.c_uint() | ||
+ | p_copied = ctypes.addressof(copied) | ||
+ | ReadProcessMemory(self.process_handle, | ||
+ | return copied.value | ||
+ | |||
+ | </ | ||
+ | |||
+ | === 설명 === | ||
+ | 이전 코드의 약간 손보아 리스트 뷰에 대해 딱 필요한 기능을 꺼내 쓸 수 있도록 고쳤습니다. 그리고 이전에 생략한 설명을 첨부하도록 할께요. 어차피 혹시 또 코드를 볼 때가 있다면 다시 빨리 기억하기 편할 테니까요. | ||
+ | |||
+ | 우선 LVItem 클래스는 [[https:// | ||
+ | |||
+ | ListViewPeek 클래스가 본 기능을 하는 녀석입니다. 생성 인자로 리스트뷰 컨트롤의 핸들을 넘겨 주도록 되어 있죠. 생성자에서는 [[https:// | ||
+ | |||
+ | [[https:// | ||
+ | |||
+ | 리스트의 각 아이템 텍스트를 가져오는 방법은 이렇습니다. [[http:// | ||
+ | |||
+ | 우선 제대로 이벤트를 던지면 p_buf에는 각 항목의 텍스트가 복사됩니다. WinAPI가 여기까지는 해 줍니다. 그 다음 | ||
+ | ''< | ||
+ | |||
+ | 리스트의 각 항목을 선택하려면 [[https:// | ||
+ | |||
+ | * mask: state를 변경하는 것이므로 '' | ||
+ | * state: [[https:// | ||
+ | * stateMask: '' | ||
+ | * 이외: 이외의 필드는 0으로 해도 무방합니다. | ||
+ | |||
+ | 그런데 TeraCopy는 ListView Control에서 LVM_SETITEMSTATE 이벤트를 제대로 수신하지 않습니다. ListView Control을 이용한 다른 앱을 이용해서 확인하기 바랍니다. 추정컨데 TeraCopy는 [[https:// | ||
+ | |||
+ | ==== Control Picker ==== | ||
+ | ListView Control를 찾아내고 조정하는 역할을 합니다. | ||
+ | |||
+ | <code python control_picker.py> | ||
+ | # -*- coding: utf-8 | ||
+ | import win32gui | ||
+ | import pywintypes | ||
+ | from listview_peek import ListViewPeek | ||
+ | |||
+ | |||
+ | class ControlPicker: | ||
+ | def __init__(self, | ||
+ | self.parent_window_handle = 0 | ||
+ | self.child_windows = [] | ||
+ | |||
+ | try: | ||
+ | win32gui.EnumWindows(self.__enum_window_handler, | ||
+ | except pywintypes.error as e: | ||
+ | if e[0] == 0: | ||
+ | pass | ||
+ | |||
+ | win32gui.EnumChildWindows(self.parent_window_handle, | ||
+ | |||
+ | def __enum_window_handler(self, | ||
+ | window_text = win32gui.GetWindowText(window_handle) | ||
+ | if window_text.find(extra) != -1: | ||
+ | self.parent_window_handle = window_handle | ||
+ | return pywintypes.FALSE | ||
+ | |||
+ | def __enum_child_window_handler(self, | ||
+ | self.child_windows.append(window_handle) | ||
+ | |||
+ | def pick_control(self, | ||
+ | for child_window in self.child_windows: | ||
+ | window_class = win32gui.GetClassName(child_window) | ||
+ | control_id = win32gui.GetDlgCtrlID(child_window) | ||
+ | |||
+ | if window_class != target_class_name: | ||
+ | continue | ||
+ | |||
+ | if target_control_id: | ||
+ | if target_control_id == control_id: | ||
+ | return child_window | ||
+ | else: | ||
+ | return child_window | ||
+ | |||
+ | |||
+ | if __name__ == ' | ||
+ | import sys | ||
+ | import time | ||
+ | |||
+ | picker = ControlPicker(' | ||
+ | list_view_control = picker.pick_control(' | ||
+ | if not list_view_control: | ||
+ | print " | ||
+ | sys.exit(1) | ||
+ | |||
+ | peek = ListViewPeek(list_view_control) | ||
+ | |||
+ | print "There are %d items in the ListView control!" | ||
+ | for x in peek.get_list_items(): | ||
+ | print x | ||
+ | |||
+ | |||
+ | if peek.get_item_count() < 2: | ||
+ | print "Make sure Subrenamer has enough unmatched video lists!" | ||
+ | sys.exit(1) | ||
+ | |||
+ | pause = 5 | ||
+ | win32gui.SetActiveWindow(picker.parent_window_handle) | ||
+ | win32gui.SetForegroundWindow(list_view_control) | ||
+ | print " | ||
+ | time.sleep(pause) | ||
+ | |||
+ | peek.select_item(-1, | ||
+ | print "No item selected. %d seconds sleep..." | ||
+ | time.sleep(pause) | ||
+ | |||
+ | peek.select_item(0, | ||
+ | print "The first item selected. %d seconds sleep..." | ||
+ | time.sleep(pause) | ||
+ | |||
+ | peek.select_item(-1, | ||
+ | peek.select_item(1, | ||
+ | print "The second item selected. %d seconds sleep..." | ||
+ | time.sleep(pause) | ||
+ | |||
+ | peek.select_item(-1, | ||
+ | print "All selected. %d seconds sleep..." | ||
+ | time.sleep(pause) | ||
+ | |||
+ | peek.select_item(-1, | ||
+ | print "None selected again." | ||
+ | print " | ||
+ | |||
+ | </ | ||
+ | |||
+ | === 설명 === | ||
+ | |||
+ | {{: | ||
+ | |||
+ | 위 그림이 SubRenamer입니다. [[https:// | ||
+ | |||
+ | 이 프로그램은 동영상과 자막을 맞춰주는 프로그램입니다. [[.: | ||
+ | |||
+ | {{: | ||
+ | |||
+ | control picker 스크립트도 subrenamer 프로그램을 상대로 제작했습니다. 스크립트를 실행하면 subrenamer 창을 찾고, 창을 가장 위로 올린 다음, 리스트 컨트롤의 항목을 선택하는 데모를 보여 줍니다. | ||