# hierlist # # IMPORTANT - Please read before using. # This module exposes an API for a Hierarchical Tree Control. # Previously, a custom tree control was included in Pythonwin which # has an API very similar to this. # The current control used is the common "Tree Control". This module exists now # to provide an API similar to the old control, but for the new Tree control. # If you need to use the Tree Control, you may still find this API a reasonable # choice. However, you should investigate using the tree control directly # to provide maximum flexibility (but with extra work). import sys import win32ui import win32con import win32api from win32api import RGB from pywin.mfc import object, window, docview, dialog import commctrl # helper to get the text of an arbitary item def GetItemText(item): if type(item)==type(()) or type(item)==type([]): use = item[0] else: use = item if type(use)==type(''): return use else: return repr(item) class HierDialog(dialog.Dialog): def __init__(self, title, hierList, bitmapID = win32ui.IDB_HIERFOLDERS, dlgID = win32ui.IDD_TREE, dll = None, childListBoxID = win32ui.IDC_LIST1): dialog.Dialog.__init__(self, dlgID, dll ) # reuse this dialog. self.hierList=hierList self.dlgID = dlgID self.title=title # self.childListBoxID = childListBoxID def OnInitDialog(self): self.SetWindowText(self.title) self.hierList.HierInit(self) return dialog.Dialog.OnInitDialog(self) class HierList(object.Object): def __init__(self, root, bitmapID = win32ui.IDB_HIERFOLDERS, listBoxId = None, bitmapMask = None): # used to create object. self.listControl = None self.bitmapID = bitmapID self.root = root self.listBoxId = listBoxId self.itemHandleMap = {} self.filledItemHandlesMap = {} self.bitmapMask = bitmapMask def __getattr__(self, attr): try: return getattr(self.listControl, attr) except AttributeError: return object.Object.__getattr__(self, attr) def ItemFromHandle(self, handle): return self.itemHandleMap[handle] def SetStyle(self, newStyle): hwnd = self.listControl.GetSafeHwnd() style = win32api.GetWindowLong(hwnd, win32con.GWL_STYLE); win32api.SetWindowLong(hwnd, win32con.GWL_STYLE, (style | newStyle) ) def HierInit(self, parent, listControl = None ): # Used when window first exists. # this also calls "Create" on the listbox. # params - id of listbbox, ID of bitmap, size of bitmaps if self.bitmapMask is None: bitmapMask = RGB(0,0,255) else: bitmapMask = self.bitmapMask self.imageList = win32ui.CreateImageList(self.bitmapID, 16, 0, bitmapMask) if listControl is None: if self.listBoxId is None: self.listBoxId = win32ui.IDC_LIST1 self.listControl = parent.GetDlgItem(self.listBoxId) else: self.listControl = listControl lbid = listControl.GetDlgCtrlID() assert self.listBoxId is None or self.listBoxId == lbid, "An invalid listbox control ID has been specified (specified as %s, but exists as %s)" % (self.listBoxId, lbid) self.listBoxId = lbid self.listControl.SetImageList(self.imageList, commctrl.LVSIL_NORMAL) # self.list.AttachObject(self) ## ??? Need a better way to do this - either some way to detect if it's compiled with UNICODE ## defined, and/or a way to switch the constants based on UNICODE ??? if sys.version_info[0] < 3: parent.HookNotify(self.OnTreeItemExpanding, commctrl.TVN_ITEMEXPANDINGA) parent.HookNotify(self.OnTreeItemSelChanged, commctrl.TVN_SELCHANGEDA) else: parent.HookNotify(self.OnTreeItemExpanding, commctrl.TVN_ITEMEXPANDINGW) parent.HookNotify(self.OnTreeItemSelChanged, commctrl.TVN_SELCHANGEDW) parent.HookNotify(self.OnTreeItemDoubleClick, commctrl.NM_DBLCLK) self.notify_parent = parent if self.root: self.AcceptRoot(self.root) def DeleteAllItems(self): self.listControl.DeleteAllItems() self.root = None self.itemHandleMap = {} self.filledItemHandlesMap = {} def HierTerm(self): # Dont want notifies as we kill the list. parent = self.notify_parent # GetParentFrame() if sys.version_info[0] < 3: parent.HookNotify(None, commctrl.TVN_ITEMEXPANDINGA) parent.HookNotify(None, commctrl.TVN_SELCHANGEDA) else: parent.HookNotify(None, commctrl.TVN_ITEMEXPANDINGW) parent.HookNotify(None, commctrl.TVN_SELCHANGEDW) parent.HookNotify(None, commctrl.NM_DBLCLK) self.DeleteAllItems() self.listControl = None self.notify_parent = None # Break a possible cycle def OnTreeItemDoubleClick(self, info, extra): (hwndFrom, idFrom, code) = info if idFrom != self.listBoxId: return None item = self.itemHandleMap[self.listControl.GetSelectedItem()] self.TakeDefaultAction(item) return 1 def OnTreeItemExpanding(self, info, extra): (hwndFrom, idFrom, code) = info if idFrom != self.listBoxId: return None action, itemOld, itemNew, pt = extra itemHandle = itemNew[0] if itemHandle not in self.filledItemHandlesMap: item = self.itemHandleMap[itemHandle] self.AddSubList(itemHandle, self.GetSubList(item)) self.filledItemHandlesMap[itemHandle] = None return 0 def OnTreeItemSelChanged(self, info, extra): (hwndFrom, idFrom, code) = info if idFrom != self.listBoxId: return None action, itemOld, itemNew, pt = extra itemHandle = itemNew[0] item = self.itemHandleMap[itemHandle] self.PerformItemSelected(item) return 1 def AddSubList(self, parentHandle, subItems): for item in subItems: self.AddItem(parentHandle, item) def AddItem(self, parentHandle, item, hInsertAfter = commctrl.TVI_LAST): text = self.GetText(item) if self.IsExpandable(item): cItems = 1 # Trick it !! else: cItems = 0 bitmapCol = self.GetBitmapColumn(item) bitmapSel = self.GetSelectedBitmapColumn(item) if bitmapSel is None: bitmapSel = bitmapCol ## if type(text) is str: ## text = text.encode("mbcs") hitem = self.listControl.InsertItem(parentHandle, hInsertAfter, (None, None, None, text, bitmapCol, bitmapSel, cItems, 0)) self.itemHandleMap[hitem] = item return hitem def _GetChildHandles(self, handle): ret = [] try: handle = self.listControl.GetChildItem(handle) while 1: ret.append(handle) handle = self.listControl.GetNextItem(handle, commctrl.TVGN_NEXT) except win32ui.error: # out of children pass return ret def ItemFromHandle(self, handle): return self.itemHandleMap[handle] def Refresh(self, hparent = None): # Attempt to refresh the given item's sub-entries, but maintain the tree state # (ie, the selected item, expanded items, etc) if hparent is None: hparent = commctrl.TVI_ROOT if hparent not in self.filledItemHandlesMap: # This item has never been expanded, so no refresh can possibly be required. return root_item = self.itemHandleMap[hparent] old_handles = self._GetChildHandles(hparent) old_items = list(map( self.ItemFromHandle, old_handles )) new_items = self.GetSubList(root_item) # Now an inefficient technique for synching the items. inew = 0 hAfter = commctrl.TVI_FIRST for iold in range(len(old_items)): inewlook = inew matched = 0 while inewlook < len(new_items): if old_items[iold] == new_items[inewlook]: matched = 1 break inewlook = inewlook + 1 if matched: # Insert the new items. # print "Inserting after", old_items[iold], old_handles[iold] for i in range(inew, inewlook): # print "Inserting index %d (%s)" % (i, new_items[i]) hAfter = self.AddItem(hparent, new_items[i], hAfter) inew = inewlook + 1 # And recursively refresh iold hold = old_handles[iold] if hold in self.filledItemHandlesMap: self.Refresh(hold) else: # Remove the deleted items. # print "Deleting %d (%s)" % (iold, old_items[iold]) hdelete = old_handles[iold] # First recurse and remove the children from the map. for hchild in self._GetChildHandles(hdelete): del self.itemHandleMap[hchild] if hchild in self.filledItemHandlesMap: del self.filledItemHandlesMap[hchild] self.listControl.DeleteItem(hdelete) hAfter = old_handles[iold] # Fill any remaining new items: for newItem in new_items[inew:]: # print "Inserting new item", newItem self.AddItem(hparent, newItem) def AcceptRoot(self, root): self.listControl.DeleteAllItems() self.itemHandleMap = {commctrl.TVI_ROOT : root} self.filledItemHandlesMap = {commctrl.TVI_ROOT : root} subItems = self.GetSubList(root) self.AddSubList(0, subItems) def GetBitmapColumn(self, item): if self.IsExpandable(item): return 0 else: return 4 def GetSelectedBitmapColumn(self, item): return None # Use standard. def GetSelectedBitmapColumn(self, item): return 0 def CheckChangedChildren(self): return self.listControl.CheckChangedChildren() def GetText(self,item): return GetItemText(item) def PerformItemSelected(self, item): try: win32ui.SetStatusText('Selected ' + self.GetText(item)) except win32ui.error: # No status bar! pass def TakeDefaultAction(self, item): win32ui.MessageBox('Got item ' + self.GetText(item)) ########################################################################## # # Classes for use with seperate HierListItems. # # class HierListWithItems(HierList): def __init__(self, root, bitmapID = win32ui.IDB_HIERFOLDERS, listBoxID = None, bitmapMask = None): # used to create object. HierList.__init__(self, root, bitmapID, listBoxID, bitmapMask ) def DelegateCall( self, fn): return fn() def GetBitmapColumn(self, item): rc = self.DelegateCall(item.GetBitmapColumn) if rc is None: rc = HierList.GetBitmapColumn(self, item) return rc def GetSelectedBitmapColumn(self, item): return self.DelegateCall(item.GetSelectedBitmapColumn) def IsExpandable(self, item): return self.DelegateCall( item.IsExpandable) def GetText(self, item): return self.DelegateCall( item.GetText ) def GetSubList(self, item): return self.DelegateCall(item.GetSubList) def PerformItemSelected(self, item): func = getattr(item, "PerformItemSelected", None) if func is None: return HierList.PerformItemSelected( self, item ) else: return self.DelegateCall(func) def TakeDefaultAction(self, item): func = getattr(item, "TakeDefaultAction", None) if func is None: return HierList.TakeDefaultAction( self, item ) else: return self.DelegateCall(func) # A hier list item - for use with a HierListWithItems class HierListItem: def __init__(self): pass def GetText(self): pass def GetSubList(self): pass def IsExpandable(self): pass def GetBitmapColumn(self): return None # indicate he should do it. def GetSelectedBitmapColumn(self): return None # same as other # for py3k/rich-comp sorting compatibility. def __lt__(self, other): # we want unrelated items to be sortable... return id(self) < id(other) # for py3k/rich-comp equality compatibility. def __eq__(self, other): return False