123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- # A demo of a fairly complex dialog.
- #
- # Features:
- # * Uses a "dynamic dialog resource" to build the dialog.
- # * Uses a ListView control.
- # * Dynamically resizes content.
- # * Uses a second worker thread to fill the list.
- # * Demostrates support for windows XP themes.
-
- # If you are on Windows XP, and specify a '--noxp' argument, you will see:
- # * alpha-blend issues with icons
- # * The buttons are "old" style, rather than based on the XP theme.
- # Hence, using:
- # import winxpgui as win32gui
- # is recommended.
- # Please report any problems.
- import sys
-
- if "--noxp" in sys.argv:
- import win32gui
- else:
- import winxpgui as win32gui
-
- import array
- import os
- import queue
- import struct
-
- import commctrl
- import win32api
- import win32con
- import win32gui_struct
- import winerror
-
- IDC_SEARCHTEXT = 1024
- IDC_BUTTON_SEARCH = 1025
- IDC_BUTTON_DISPLAY = 1026
- IDC_LISTBOX = 1027
-
- WM_SEARCH_RESULT = win32con.WM_USER + 512
- WM_SEARCH_FINISHED = win32con.WM_USER + 513
-
-
- class _WIN32MASKEDSTRUCT:
- def __init__(self, **kw):
- full_fmt = ""
- for name, fmt, default, mask in self._struct_items_:
- self.__dict__[name] = None
- if fmt == "z":
- full_fmt += "pi"
- else:
- full_fmt += fmt
- for name, val in kw.items():
- if name not in self.__dict__:
- raise ValueError("LVITEM structures do not have an item '%s'" % (name,))
- self.__dict__[name] = val
-
- def __setattr__(self, attr, val):
- if not attr.startswith("_") and attr not in self.__dict__:
- raise AttributeError(attr)
- self.__dict__[attr] = val
-
- def toparam(self):
- self._buffs = []
- full_fmt = ""
- vals = []
- mask = 0
- # calc the mask
- for name, fmt, default, this_mask in self._struct_items_:
- if this_mask is not None and self.__dict__.get(name) is not None:
- mask |= this_mask
- self.mask = mask
- for name, fmt, default, this_mask in self._struct_items_:
- val = self.__dict__[name]
- if fmt == "z":
- fmt = "Pi"
- if val is None:
- vals.append(0)
- vals.append(0)
- else:
- # Note this demo still works with byte strings. An
- # alternate strategy would be to use unicode natively
- # and use the 'W' version of the messages - eg,
- # LVM_SETITEMW etc.
- val = val + "\0"
- if isinstance(val, str):
- val = val.encode("mbcs")
- str_buf = array.array("b", val)
- vals.append(str_buf.buffer_info()[0])
- vals.append(len(val))
- self._buffs.append(str_buf) # keep alive during the call.
- else:
- if val is None:
- val = default
- vals.append(val)
- full_fmt += fmt
- return struct.pack(*(full_fmt,) + tuple(vals))
-
-
- # NOTE: See the win32gui_struct module for an alternative way of dealing
- # with these structures
- class LVITEM(_WIN32MASKEDSTRUCT):
- _struct_items_ = [
- ("mask", "I", 0, None),
- ("iItem", "i", 0, None),
- ("iSubItem", "i", 0, None),
- ("state", "I", 0, commctrl.LVIF_STATE),
- ("stateMask", "I", 0, None),
- ("text", "z", None, commctrl.LVIF_TEXT),
- ("iImage", "i", 0, commctrl.LVIF_IMAGE),
- ("lParam", "i", 0, commctrl.LVIF_PARAM),
- ("iIdent", "i", 0, None),
- ]
-
-
- class LVCOLUMN(_WIN32MASKEDSTRUCT):
- _struct_items_ = [
- ("mask", "I", 0, None),
- ("fmt", "i", 0, commctrl.LVCF_FMT),
- ("cx", "i", 0, commctrl.LVCF_WIDTH),
- ("text", "z", None, commctrl.LVCF_TEXT),
- ("iSubItem", "i", 0, commctrl.LVCF_SUBITEM),
- ("iImage", "i", 0, commctrl.LVCF_IMAGE),
- ("iOrder", "i", 0, commctrl.LVCF_ORDER),
- ]
-
-
- class DemoWindowBase:
- def __init__(self):
- win32gui.InitCommonControls()
- self.hinst = win32gui.dllhandle
- self.list_data = {}
-
- def _RegisterWndClass(self):
- className = "PythonDocSearch"
- message_map = {}
- wc = win32gui.WNDCLASS()
- wc.SetDialogProc() # Make it a dialog class.
- wc.hInstance = self.hinst
- wc.lpszClassName = className
- wc.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW
- wc.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW)
- wc.hbrBackground = win32con.COLOR_WINDOW + 1
- wc.lpfnWndProc = message_map # could also specify a wndproc.
- # C code: wc.cbWndExtra = DLGWINDOWEXTRA + sizeof(HBRUSH) + (sizeof(COLORREF));
- wc.cbWndExtra = win32con.DLGWINDOWEXTRA + struct.calcsize("Pi")
- icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
-
- ## py.ico went away in python 2.5, load from executable instead
- this_app = win32api.GetModuleHandle(None)
- try:
- wc.hIcon = win32gui.LoadIcon(this_app, 1) ## python.exe and pythonw.exe
- except win32gui.error:
- wc.hIcon = win32gui.LoadIcon(this_app, 135) ## pythonwin's icon
- try:
- classAtom = win32gui.RegisterClass(wc)
- except win32gui.error as err_info:
- if err_info.winerror != winerror.ERROR_CLASS_ALREADY_EXISTS:
- raise
- return className
-
- def _GetDialogTemplate(self, dlgClassName):
- style = (
- win32con.WS_THICKFRAME
- | win32con.WS_POPUP
- | win32con.WS_VISIBLE
- | win32con.WS_CAPTION
- | win32con.WS_SYSMENU
- | win32con.DS_SETFONT
- | win32con.WS_MINIMIZEBOX
- )
- cs = win32con.WS_CHILD | win32con.WS_VISIBLE
- title = "Dynamic Dialog Demo"
-
- # Window frame and title
- dlg = [
- [
- title,
- (0, 0, 210, 250),
- style,
- None,
- (8, "MS Sans Serif"),
- None,
- dlgClassName,
- ],
- ]
-
- # ID label and text box
- dlg.append([130, "Enter something", -1, (5, 5, 200, 9), cs | win32con.SS_LEFT])
- s = cs | win32con.WS_TABSTOP | win32con.WS_BORDER
- dlg.append(["EDIT", None, IDC_SEARCHTEXT, (5, 15, 200, 12), s])
-
- # Search/Display Buttons
- # (x positions don't matter here)
- s = cs | win32con.WS_TABSTOP
- dlg.append(
- [
- 128,
- "Fill List",
- IDC_BUTTON_SEARCH,
- (5, 35, 50, 14),
- s | win32con.BS_DEFPUSHBUTTON,
- ]
- )
- s = win32con.BS_PUSHBUTTON | s
- dlg.append([128, "Display", IDC_BUTTON_DISPLAY, (100, 35, 50, 14), s])
-
- # List control.
- # Can't make this work :(
- ## s = cs | win32con.WS_TABSTOP
- ## dlg.append(['SysListView32', "Title", IDC_LISTBOX, (5, 505, 200, 200), s])
-
- return dlg
-
- def _DoCreate(self, fn):
- message_map = {
- win32con.WM_SIZE: self.OnSize,
- win32con.WM_COMMAND: self.OnCommand,
- win32con.WM_NOTIFY: self.OnNotify,
- win32con.WM_INITDIALOG: self.OnInitDialog,
- win32con.WM_CLOSE: self.OnClose,
- win32con.WM_DESTROY: self.OnDestroy,
- WM_SEARCH_RESULT: self.OnSearchResult,
- WM_SEARCH_FINISHED: self.OnSearchFinished,
- }
- dlgClassName = self._RegisterWndClass()
- template = self._GetDialogTemplate(dlgClassName)
- return fn(self.hinst, template, 0, message_map)
-
- def _SetupList(self):
- child_style = (
- win32con.WS_CHILD
- | win32con.WS_VISIBLE
- | win32con.WS_BORDER
- | win32con.WS_HSCROLL
- | win32con.WS_VSCROLL
- )
- child_style |= (
- commctrl.LVS_SINGLESEL | commctrl.LVS_SHOWSELALWAYS | commctrl.LVS_REPORT
- )
- self.hwndList = win32gui.CreateWindow(
- "SysListView32",
- None,
- child_style,
- 0,
- 0,
- 100,
- 100,
- self.hwnd,
- IDC_LISTBOX,
- self.hinst,
- None,
- )
-
- child_ex_style = win32gui.SendMessage(
- self.hwndList, commctrl.LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0
- )
- child_ex_style |= commctrl.LVS_EX_FULLROWSELECT
- win32gui.SendMessage(
- self.hwndList, commctrl.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, child_ex_style
- )
-
- # Add an image list - use the builtin shell folder icon - this
- # demonstrates the problem with alpha-blending of icons on XP if
- # winxpgui is not used in place of win32gui.
- il = win32gui.ImageList_Create(
- win32api.GetSystemMetrics(win32con.SM_CXSMICON),
- win32api.GetSystemMetrics(win32con.SM_CYSMICON),
- commctrl.ILC_COLOR32 | commctrl.ILC_MASK,
- 1, # initial size
- 0,
- ) # cGrow
-
- shell_dll = os.path.join(win32api.GetSystemDirectory(), "shell32.dll")
- large, small = win32gui.ExtractIconEx(shell_dll, 4, 1)
- win32gui.ImageList_ReplaceIcon(il, -1, small[0])
- win32gui.DestroyIcon(small[0])
- win32gui.DestroyIcon(large[0])
- win32gui.SendMessage(
- self.hwndList, commctrl.LVM_SETIMAGELIST, commctrl.LVSIL_SMALL, il
- )
-
- # Setup the list control columns.
- lvc = LVCOLUMN(
- mask=commctrl.LVCF_FMT
- | commctrl.LVCF_WIDTH
- | commctrl.LVCF_TEXT
- | commctrl.LVCF_SUBITEM
- )
- lvc.fmt = commctrl.LVCFMT_LEFT
- lvc.iSubItem = 1
- lvc.text = "Title"
- lvc.cx = 200
- win32gui.SendMessage(self.hwndList, commctrl.LVM_INSERTCOLUMN, 0, lvc.toparam())
- lvc.iSubItem = 0
- lvc.text = "Order"
- lvc.cx = 50
- win32gui.SendMessage(self.hwndList, commctrl.LVM_INSERTCOLUMN, 0, lvc.toparam())
-
- win32gui.UpdateWindow(self.hwnd)
-
- def ClearListItems(self):
- win32gui.SendMessage(self.hwndList, commctrl.LVM_DELETEALLITEMS)
- self.list_data = {}
-
- def AddListItem(self, data, *columns):
- num_items = win32gui.SendMessage(self.hwndList, commctrl.LVM_GETITEMCOUNT)
- item = LVITEM(text=columns[0], iItem=num_items)
- new_index = win32gui.SendMessage(
- self.hwndList, commctrl.LVM_INSERTITEM, 0, item.toparam()
- )
- col_no = 1
- for col in columns[1:]:
- item = LVITEM(text=col, iItem=new_index, iSubItem=col_no)
- win32gui.SendMessage(self.hwndList, commctrl.LVM_SETITEM, 0, item.toparam())
- col_no += 1
- self.list_data[new_index] = data
-
- def OnInitDialog(self, hwnd, msg, wparam, lparam):
- self.hwnd = hwnd
- # centre the dialog
- desktop = win32gui.GetDesktopWindow()
- l, t, r, b = win32gui.GetWindowRect(self.hwnd)
- dt_l, dt_t, dt_r, dt_b = win32gui.GetWindowRect(desktop)
- centre_x, centre_y = win32gui.ClientToScreen(
- desktop, ((dt_r - dt_l) // 2, (dt_b - dt_t) // 2)
- )
- win32gui.MoveWindow(
- hwnd, centre_x - (r // 2), centre_y - (b // 2), r - l, b - t, 0
- )
- self._SetupList()
- l, t, r, b = win32gui.GetClientRect(self.hwnd)
- self._DoSize(r - l, b - t, 1)
-
- def _DoSize(self, cx, cy, repaint=1):
- # right-justify the textbox.
- ctrl = win32gui.GetDlgItem(self.hwnd, IDC_SEARCHTEXT)
- l, t, r, b = win32gui.GetWindowRect(ctrl)
- l, t = win32gui.ScreenToClient(self.hwnd, (l, t))
- r, b = win32gui.ScreenToClient(self.hwnd, (r, b))
- win32gui.MoveWindow(ctrl, l, t, cx - l - 5, b - t, repaint)
- # The button.
- ctrl = win32gui.GetDlgItem(self.hwnd, IDC_BUTTON_DISPLAY)
- l, t, r, b = win32gui.GetWindowRect(ctrl)
- l, t = win32gui.ScreenToClient(self.hwnd, (l, t))
- r, b = win32gui.ScreenToClient(self.hwnd, (r, b))
- list_y = b + 10
- w = r - l
- win32gui.MoveWindow(ctrl, cx - 5 - w, t, w, b - t, repaint)
-
- # The list control
- win32gui.MoveWindow(self.hwndList, 0, list_y, cx, cy - list_y, repaint)
- # The last column of the list control.
- new_width = cx - win32gui.SendMessage(
- self.hwndList, commctrl.LVM_GETCOLUMNWIDTH, 0
- )
- win32gui.SendMessage(self.hwndList, commctrl.LVM_SETCOLUMNWIDTH, 1, new_width)
-
- def OnSize(self, hwnd, msg, wparam, lparam):
- x = win32api.LOWORD(lparam)
- y = win32api.HIWORD(lparam)
- self._DoSize(x, y)
- return 1
-
- def OnSearchResult(self, hwnd, msg, wparam, lparam):
- try:
- while 1:
- params = self.result_queue.get(0)
- self.AddListItem(*params)
- except queue.Empty:
- pass
-
- def OnSearchFinished(self, hwnd, msg, wparam, lparam):
- print("OnSearchFinished")
-
- def OnNotify(self, hwnd, msg, wparam, lparam):
- info = win32gui_struct.UnpackNMITEMACTIVATE(lparam)
- if info.code == commctrl.NM_DBLCLK:
- print("Double click on item", info.iItem + 1)
- return 1
-
- def OnCommand(self, hwnd, msg, wparam, lparam):
- id = win32api.LOWORD(wparam)
- if id == IDC_BUTTON_SEARCH:
- self.ClearListItems()
-
- def fill_slowly(q, hwnd):
- import time
-
- for i in range(20):
- q.put(("whatever", str(i + 1), "Search result " + str(i)))
- win32gui.PostMessage(hwnd, WM_SEARCH_RESULT, 0, 0)
- time.sleep(0.25)
- win32gui.PostMessage(hwnd, WM_SEARCH_FINISHED, 0, 0)
-
- import threading
-
- self.result_queue = queue.Queue()
- thread = threading.Thread(
- target=fill_slowly, args=(self.result_queue, self.hwnd)
- )
- thread.start()
- elif id == IDC_BUTTON_DISPLAY:
- print("Display button selected")
- sel = win32gui.SendMessage(
- self.hwndList, commctrl.LVM_GETNEXTITEM, -1, commctrl.LVNI_SELECTED
- )
- print("The selected item is", sel + 1)
-
- # These function differ based on how the window is used, so may be overridden
- def OnClose(self, hwnd, msg, wparam, lparam):
- raise NotImplementedError
-
- def OnDestroy(self, hwnd, msg, wparam, lparam):
- pass
-
-
- # An implementation suitable for use with the Win32 Window functions (ie, not
- # a true dialog)
- class DemoWindow(DemoWindowBase):
- def CreateWindow(self):
- # Create the window via CreateDialogBoxIndirect - it can then
- # work as a "normal" window, once a message loop is established.
- self._DoCreate(win32gui.CreateDialogIndirect)
-
- def OnClose(self, hwnd, msg, wparam, lparam):
- win32gui.DestroyWindow(hwnd)
-
- # We need to arrange to a WM_QUIT message to be sent to our
- # PumpMessages() loop.
- def OnDestroy(self, hwnd, msg, wparam, lparam):
- win32gui.PostQuitMessage(0) # Terminate the app.
-
-
- # An implementation suitable for use with the Win32 Dialog functions.
- class DemoDialog(DemoWindowBase):
- def DoModal(self):
- return self._DoCreate(win32gui.DialogBoxIndirect)
-
- def OnClose(self, hwnd, msg, wparam, lparam):
- win32gui.EndDialog(hwnd, 0)
-
-
- def DemoModal():
- w = DemoDialog()
- w.DoModal()
-
-
- def DemoCreateWindow():
- w = DemoWindow()
- w.CreateWindow()
- # PumpMessages runs until PostQuitMessage() is called by someone.
- win32gui.PumpMessages()
-
-
- if __name__ == "__main__":
- DemoModal()
- DemoCreateWindow()
|