project:peekwindow
차이
문서의 선택한 두 판 사이의 차이를 보여줍니다.
양쪽 이전 판이전 판다음 판 | 이전 판 | ||
project:peekwindow [2014/10/09 21:24] – 바깥 편집 127.0.0.1 | project:peekwindow [2015/08/08 20:22] (현재) – [설명] changwoo | ||
---|---|---|---|
줄 357: | 줄 357: | ||
가벼운 마음으로 시작하였는데, | 가벼운 마음으로 시작하였는데, | ||
- | + | ===== 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 창을 찾고, 창을 가장 위로 올린 다음, 리스트 컨트롤의 항목을 선택하는 데모를 보여 줍니다. | ||
project/peekwindow.1412889846.txt.gz · 마지막으로 수정됨: 2015/08/08 18:26 (바깥 편집)