Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

win32gui_dialog.py 15KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. # A demo of a fairly complex dialog.
  2. #
  3. # Features:
  4. # * Uses a "dynamic dialog resource" to build the dialog.
  5. # * Uses a ListView control.
  6. # * Dynamically resizes content.
  7. # * Uses a second worker thread to fill the list.
  8. # * Demostrates support for windows XP themes.
  9. # If you are on Windows XP, and specify a '--noxp' argument, you will see:
  10. # * alpha-blend issues with icons
  11. # * The buttons are "old" style, rather than based on the XP theme.
  12. # Hence, using:
  13. # import winxpgui as win32gui
  14. # is recommended.
  15. # Please report any problems.
  16. import sys
  17. if "--noxp" in sys.argv:
  18. import win32gui
  19. else:
  20. import winxpgui as win32gui
  21. import array
  22. import os
  23. import queue
  24. import struct
  25. import commctrl
  26. import win32api
  27. import win32con
  28. import win32gui_struct
  29. import winerror
  30. IDC_SEARCHTEXT = 1024
  31. IDC_BUTTON_SEARCH = 1025
  32. IDC_BUTTON_DISPLAY = 1026
  33. IDC_LISTBOX = 1027
  34. WM_SEARCH_RESULT = win32con.WM_USER + 512
  35. WM_SEARCH_FINISHED = win32con.WM_USER + 513
  36. class _WIN32MASKEDSTRUCT:
  37. def __init__(self, **kw):
  38. full_fmt = ""
  39. for name, fmt, default, mask in self._struct_items_:
  40. self.__dict__[name] = None
  41. if fmt == "z":
  42. full_fmt += "pi"
  43. else:
  44. full_fmt += fmt
  45. for name, val in kw.items():
  46. if name not in self.__dict__:
  47. raise ValueError("LVITEM structures do not have an item '%s'" % (name,))
  48. self.__dict__[name] = val
  49. def __setattr__(self, attr, val):
  50. if not attr.startswith("_") and attr not in self.__dict__:
  51. raise AttributeError(attr)
  52. self.__dict__[attr] = val
  53. def toparam(self):
  54. self._buffs = []
  55. full_fmt = ""
  56. vals = []
  57. mask = 0
  58. # calc the mask
  59. for name, fmt, default, this_mask in self._struct_items_:
  60. if this_mask is not None and self.__dict__.get(name) is not None:
  61. mask |= this_mask
  62. self.mask = mask
  63. for name, fmt, default, this_mask in self._struct_items_:
  64. val = self.__dict__[name]
  65. if fmt == "z":
  66. fmt = "Pi"
  67. if val is None:
  68. vals.append(0)
  69. vals.append(0)
  70. else:
  71. # Note this demo still works with byte strings. An
  72. # alternate strategy would be to use unicode natively
  73. # and use the 'W' version of the messages - eg,
  74. # LVM_SETITEMW etc.
  75. val = val + "\0"
  76. if isinstance(val, str):
  77. val = val.encode("mbcs")
  78. str_buf = array.array("b", val)
  79. vals.append(str_buf.buffer_info()[0])
  80. vals.append(len(val))
  81. self._buffs.append(str_buf) # keep alive during the call.
  82. else:
  83. if val is None:
  84. val = default
  85. vals.append(val)
  86. full_fmt += fmt
  87. return struct.pack(*(full_fmt,) + tuple(vals))
  88. # NOTE: See the win32gui_struct module for an alternative way of dealing
  89. # with these structures
  90. class LVITEM(_WIN32MASKEDSTRUCT):
  91. _struct_items_ = [
  92. ("mask", "I", 0, None),
  93. ("iItem", "i", 0, None),
  94. ("iSubItem", "i", 0, None),
  95. ("state", "I", 0, commctrl.LVIF_STATE),
  96. ("stateMask", "I", 0, None),
  97. ("text", "z", None, commctrl.LVIF_TEXT),
  98. ("iImage", "i", 0, commctrl.LVIF_IMAGE),
  99. ("lParam", "i", 0, commctrl.LVIF_PARAM),
  100. ("iIdent", "i", 0, None),
  101. ]
  102. class LVCOLUMN(_WIN32MASKEDSTRUCT):
  103. _struct_items_ = [
  104. ("mask", "I", 0, None),
  105. ("fmt", "i", 0, commctrl.LVCF_FMT),
  106. ("cx", "i", 0, commctrl.LVCF_WIDTH),
  107. ("text", "z", None, commctrl.LVCF_TEXT),
  108. ("iSubItem", "i", 0, commctrl.LVCF_SUBITEM),
  109. ("iImage", "i", 0, commctrl.LVCF_IMAGE),
  110. ("iOrder", "i", 0, commctrl.LVCF_ORDER),
  111. ]
  112. class DemoWindowBase:
  113. def __init__(self):
  114. win32gui.InitCommonControls()
  115. self.hinst = win32gui.dllhandle
  116. self.list_data = {}
  117. def _RegisterWndClass(self):
  118. className = "PythonDocSearch"
  119. message_map = {}
  120. wc = win32gui.WNDCLASS()
  121. wc.SetDialogProc() # Make it a dialog class.
  122. wc.hInstance = self.hinst
  123. wc.lpszClassName = className
  124. wc.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW
  125. wc.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW)
  126. wc.hbrBackground = win32con.COLOR_WINDOW + 1
  127. wc.lpfnWndProc = message_map # could also specify a wndproc.
  128. # C code: wc.cbWndExtra = DLGWINDOWEXTRA + sizeof(HBRUSH) + (sizeof(COLORREF));
  129. wc.cbWndExtra = win32con.DLGWINDOWEXTRA + struct.calcsize("Pi")
  130. icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
  131. ## py.ico went away in python 2.5, load from executable instead
  132. this_app = win32api.GetModuleHandle(None)
  133. try:
  134. wc.hIcon = win32gui.LoadIcon(this_app, 1) ## python.exe and pythonw.exe
  135. except win32gui.error:
  136. wc.hIcon = win32gui.LoadIcon(this_app, 135) ## pythonwin's icon
  137. try:
  138. classAtom = win32gui.RegisterClass(wc)
  139. except win32gui.error as err_info:
  140. if err_info.winerror != winerror.ERROR_CLASS_ALREADY_EXISTS:
  141. raise
  142. return className
  143. def _GetDialogTemplate(self, dlgClassName):
  144. style = (
  145. win32con.WS_THICKFRAME
  146. | win32con.WS_POPUP
  147. | win32con.WS_VISIBLE
  148. | win32con.WS_CAPTION
  149. | win32con.WS_SYSMENU
  150. | win32con.DS_SETFONT
  151. | win32con.WS_MINIMIZEBOX
  152. )
  153. cs = win32con.WS_CHILD | win32con.WS_VISIBLE
  154. title = "Dynamic Dialog Demo"
  155. # Window frame and title
  156. dlg = [
  157. [
  158. title,
  159. (0, 0, 210, 250),
  160. style,
  161. None,
  162. (8, "MS Sans Serif"),
  163. None,
  164. dlgClassName,
  165. ],
  166. ]
  167. # ID label and text box
  168. dlg.append([130, "Enter something", -1, (5, 5, 200, 9), cs | win32con.SS_LEFT])
  169. s = cs | win32con.WS_TABSTOP | win32con.WS_BORDER
  170. dlg.append(["EDIT", None, IDC_SEARCHTEXT, (5, 15, 200, 12), s])
  171. # Search/Display Buttons
  172. # (x positions don't matter here)
  173. s = cs | win32con.WS_TABSTOP
  174. dlg.append(
  175. [
  176. 128,
  177. "Fill List",
  178. IDC_BUTTON_SEARCH,
  179. (5, 35, 50, 14),
  180. s | win32con.BS_DEFPUSHBUTTON,
  181. ]
  182. )
  183. s = win32con.BS_PUSHBUTTON | s
  184. dlg.append([128, "Display", IDC_BUTTON_DISPLAY, (100, 35, 50, 14), s])
  185. # List control.
  186. # Can't make this work :(
  187. ## s = cs | win32con.WS_TABSTOP
  188. ## dlg.append(['SysListView32', "Title", IDC_LISTBOX, (5, 505, 200, 200), s])
  189. return dlg
  190. def _DoCreate(self, fn):
  191. message_map = {
  192. win32con.WM_SIZE: self.OnSize,
  193. win32con.WM_COMMAND: self.OnCommand,
  194. win32con.WM_NOTIFY: self.OnNotify,
  195. win32con.WM_INITDIALOG: self.OnInitDialog,
  196. win32con.WM_CLOSE: self.OnClose,
  197. win32con.WM_DESTROY: self.OnDestroy,
  198. WM_SEARCH_RESULT: self.OnSearchResult,
  199. WM_SEARCH_FINISHED: self.OnSearchFinished,
  200. }
  201. dlgClassName = self._RegisterWndClass()
  202. template = self._GetDialogTemplate(dlgClassName)
  203. return fn(self.hinst, template, 0, message_map)
  204. def _SetupList(self):
  205. child_style = (
  206. win32con.WS_CHILD
  207. | win32con.WS_VISIBLE
  208. | win32con.WS_BORDER
  209. | win32con.WS_HSCROLL
  210. | win32con.WS_VSCROLL
  211. )
  212. child_style |= (
  213. commctrl.LVS_SINGLESEL | commctrl.LVS_SHOWSELALWAYS | commctrl.LVS_REPORT
  214. )
  215. self.hwndList = win32gui.CreateWindow(
  216. "SysListView32",
  217. None,
  218. child_style,
  219. 0,
  220. 0,
  221. 100,
  222. 100,
  223. self.hwnd,
  224. IDC_LISTBOX,
  225. self.hinst,
  226. None,
  227. )
  228. child_ex_style = win32gui.SendMessage(
  229. self.hwndList, commctrl.LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0
  230. )
  231. child_ex_style |= commctrl.LVS_EX_FULLROWSELECT
  232. win32gui.SendMessage(
  233. self.hwndList, commctrl.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, child_ex_style
  234. )
  235. # Add an image list - use the builtin shell folder icon - this
  236. # demonstrates the problem with alpha-blending of icons on XP if
  237. # winxpgui is not used in place of win32gui.
  238. il = win32gui.ImageList_Create(
  239. win32api.GetSystemMetrics(win32con.SM_CXSMICON),
  240. win32api.GetSystemMetrics(win32con.SM_CYSMICON),
  241. commctrl.ILC_COLOR32 | commctrl.ILC_MASK,
  242. 1, # initial size
  243. 0,
  244. ) # cGrow
  245. shell_dll = os.path.join(win32api.GetSystemDirectory(), "shell32.dll")
  246. large, small = win32gui.ExtractIconEx(shell_dll, 4, 1)
  247. win32gui.ImageList_ReplaceIcon(il, -1, small[0])
  248. win32gui.DestroyIcon(small[0])
  249. win32gui.DestroyIcon(large[0])
  250. win32gui.SendMessage(
  251. self.hwndList, commctrl.LVM_SETIMAGELIST, commctrl.LVSIL_SMALL, il
  252. )
  253. # Setup the list control columns.
  254. lvc = LVCOLUMN(
  255. mask=commctrl.LVCF_FMT
  256. | commctrl.LVCF_WIDTH
  257. | commctrl.LVCF_TEXT
  258. | commctrl.LVCF_SUBITEM
  259. )
  260. lvc.fmt = commctrl.LVCFMT_LEFT
  261. lvc.iSubItem = 1
  262. lvc.text = "Title"
  263. lvc.cx = 200
  264. win32gui.SendMessage(self.hwndList, commctrl.LVM_INSERTCOLUMN, 0, lvc.toparam())
  265. lvc.iSubItem = 0
  266. lvc.text = "Order"
  267. lvc.cx = 50
  268. win32gui.SendMessage(self.hwndList, commctrl.LVM_INSERTCOLUMN, 0, lvc.toparam())
  269. win32gui.UpdateWindow(self.hwnd)
  270. def ClearListItems(self):
  271. win32gui.SendMessage(self.hwndList, commctrl.LVM_DELETEALLITEMS)
  272. self.list_data = {}
  273. def AddListItem(self, data, *columns):
  274. num_items = win32gui.SendMessage(self.hwndList, commctrl.LVM_GETITEMCOUNT)
  275. item = LVITEM(text=columns[0], iItem=num_items)
  276. new_index = win32gui.SendMessage(
  277. self.hwndList, commctrl.LVM_INSERTITEM, 0, item.toparam()
  278. )
  279. col_no = 1
  280. for col in columns[1:]:
  281. item = LVITEM(text=col, iItem=new_index, iSubItem=col_no)
  282. win32gui.SendMessage(self.hwndList, commctrl.LVM_SETITEM, 0, item.toparam())
  283. col_no += 1
  284. self.list_data[new_index] = data
  285. def OnInitDialog(self, hwnd, msg, wparam, lparam):
  286. self.hwnd = hwnd
  287. # centre the dialog
  288. desktop = win32gui.GetDesktopWindow()
  289. l, t, r, b = win32gui.GetWindowRect(self.hwnd)
  290. dt_l, dt_t, dt_r, dt_b = win32gui.GetWindowRect(desktop)
  291. centre_x, centre_y = win32gui.ClientToScreen(
  292. desktop, ((dt_r - dt_l) // 2, (dt_b - dt_t) // 2)
  293. )
  294. win32gui.MoveWindow(
  295. hwnd, centre_x - (r // 2), centre_y - (b // 2), r - l, b - t, 0
  296. )
  297. self._SetupList()
  298. l, t, r, b = win32gui.GetClientRect(self.hwnd)
  299. self._DoSize(r - l, b - t, 1)
  300. def _DoSize(self, cx, cy, repaint=1):
  301. # right-justify the textbox.
  302. ctrl = win32gui.GetDlgItem(self.hwnd, IDC_SEARCHTEXT)
  303. l, t, r, b = win32gui.GetWindowRect(ctrl)
  304. l, t = win32gui.ScreenToClient(self.hwnd, (l, t))
  305. r, b = win32gui.ScreenToClient(self.hwnd, (r, b))
  306. win32gui.MoveWindow(ctrl, l, t, cx - l - 5, b - t, repaint)
  307. # The button.
  308. ctrl = win32gui.GetDlgItem(self.hwnd, IDC_BUTTON_DISPLAY)
  309. l, t, r, b = win32gui.GetWindowRect(ctrl)
  310. l, t = win32gui.ScreenToClient(self.hwnd, (l, t))
  311. r, b = win32gui.ScreenToClient(self.hwnd, (r, b))
  312. list_y = b + 10
  313. w = r - l
  314. win32gui.MoveWindow(ctrl, cx - 5 - w, t, w, b - t, repaint)
  315. # The list control
  316. win32gui.MoveWindow(self.hwndList, 0, list_y, cx, cy - list_y, repaint)
  317. # The last column of the list control.
  318. new_width = cx - win32gui.SendMessage(
  319. self.hwndList, commctrl.LVM_GETCOLUMNWIDTH, 0
  320. )
  321. win32gui.SendMessage(self.hwndList, commctrl.LVM_SETCOLUMNWIDTH, 1, new_width)
  322. def OnSize(self, hwnd, msg, wparam, lparam):
  323. x = win32api.LOWORD(lparam)
  324. y = win32api.HIWORD(lparam)
  325. self._DoSize(x, y)
  326. return 1
  327. def OnSearchResult(self, hwnd, msg, wparam, lparam):
  328. try:
  329. while 1:
  330. params = self.result_queue.get(0)
  331. self.AddListItem(*params)
  332. except queue.Empty:
  333. pass
  334. def OnSearchFinished(self, hwnd, msg, wparam, lparam):
  335. print("OnSearchFinished")
  336. def OnNotify(self, hwnd, msg, wparam, lparam):
  337. info = win32gui_struct.UnpackNMITEMACTIVATE(lparam)
  338. if info.code == commctrl.NM_DBLCLK:
  339. print("Double click on item", info.iItem + 1)
  340. return 1
  341. def OnCommand(self, hwnd, msg, wparam, lparam):
  342. id = win32api.LOWORD(wparam)
  343. if id == IDC_BUTTON_SEARCH:
  344. self.ClearListItems()
  345. def fill_slowly(q, hwnd):
  346. import time
  347. for i in range(20):
  348. q.put(("whatever", str(i + 1), "Search result " + str(i)))
  349. win32gui.PostMessage(hwnd, WM_SEARCH_RESULT, 0, 0)
  350. time.sleep(0.25)
  351. win32gui.PostMessage(hwnd, WM_SEARCH_FINISHED, 0, 0)
  352. import threading
  353. self.result_queue = queue.Queue()
  354. thread = threading.Thread(
  355. target=fill_slowly, args=(self.result_queue, self.hwnd)
  356. )
  357. thread.start()
  358. elif id == IDC_BUTTON_DISPLAY:
  359. print("Display button selected")
  360. sel = win32gui.SendMessage(
  361. self.hwndList, commctrl.LVM_GETNEXTITEM, -1, commctrl.LVNI_SELECTED
  362. )
  363. print("The selected item is", sel + 1)
  364. # These function differ based on how the window is used, so may be overridden
  365. def OnClose(self, hwnd, msg, wparam, lparam):
  366. raise NotImplementedError
  367. def OnDestroy(self, hwnd, msg, wparam, lparam):
  368. pass
  369. # An implementation suitable for use with the Win32 Window functions (ie, not
  370. # a true dialog)
  371. class DemoWindow(DemoWindowBase):
  372. def CreateWindow(self):
  373. # Create the window via CreateDialogBoxIndirect - it can then
  374. # work as a "normal" window, once a message loop is established.
  375. self._DoCreate(win32gui.CreateDialogIndirect)
  376. def OnClose(self, hwnd, msg, wparam, lparam):
  377. win32gui.DestroyWindow(hwnd)
  378. # We need to arrange to a WM_QUIT message to be sent to our
  379. # PumpMessages() loop.
  380. def OnDestroy(self, hwnd, msg, wparam, lparam):
  381. win32gui.PostQuitMessage(0) # Terminate the app.
  382. # An implementation suitable for use with the Win32 Dialog functions.
  383. class DemoDialog(DemoWindowBase):
  384. def DoModal(self):
  385. return self._DoCreate(win32gui.DialogBoxIndirect)
  386. def OnClose(self, hwnd, msg, wparam, lparam):
  387. win32gui.EndDialog(hwnd, 0)
  388. def DemoModal():
  389. w = DemoDialog()
  390. w.DoModal()
  391. def DemoCreateWindow():
  392. w = DemoWindow()
  393. w.CreateWindow()
  394. # PumpMessages runs until PostQuitMessage() is called by someone.
  395. win32gui.PumpMessages()
  396. if __name__ == "__main__":
  397. DemoModal()
  398. DemoCreateWindow()