zoukankan      html  css  js  c++  java
  • 使用 wxPython 创建“目录树”(5)


    wxPython 自带的 wx.GenericDirCtrl 可以实现目录树的效果,但是同 vscode 的目录树对比,是丑了一些,而且虽然指定了目录,但是仍把磁盘翻了个遍。

    # 使用 wx.GenericDirCtrl 控件
    import wx
    import os
    
    class TestFrame(wx.Frame):
        def __init__(self, *args, **kw):
            super().__init__(*args, **kw)
            self.dir_tree = wx.GenericDirCtrl(self, -1, dir=os.getcwd())
    
    if __name__ == '__main__':
        app = wx.App()
        frm = TestFrame(None)
        frm.Show()
        app.MainLoop()
    
    GenericDirCtrl vscode
    )

    这里使用 wx.TreeCtrl 里来设计出类似 vscode 的目录树,先看下最终的效果图:

    1.1. 实现过程

    1.1.1. 设置树的样式

    默认的 TreeCtrl 控件的样式和上图的 GenericDirCtrl 是一样的,这里我们需要这种样式:

    self.tree = wx.TreeCtrl(self, -1,
            style = wx.TR_DEFAULT_STYLE # 默认样式 
                   |wx.TR_TWIST_BUTTONS # 结点使用 >/v 而不是 +/-
                   |wx.TR_NO_LINES      # 不绘制结点之间的连线
        )
    

    1.1.2. 设置图标

    结点的图标都是得自己设置的(调用 tree.SetItemImage()),否则默认是没有图标的。
    另外,文件夹与文件之间的图标不同,不同扩展名的文件图标也不同。

    这里用 Images.py 保存图标的数据,如获取名为 “file_type_python” 的图标的位图为

    bmp = Images.file_type_python.GetBitmap()
    # 下面是等价的
    # bmp = getattr(Iamges, "file_type_python").GetBitmap()
    

    Ext2IconDict.py 中包含一个字典 IconMap.Ext2IconDict,字典的 key 代表的是文件的扩展名,如 “png,py” 等,字典的 value 代表着“相应图标的名字”,如 "file_type_python",

    Ext2IconDict = {
        # ...
        "py" : "file_type_python", 
        # ...
    }
    

    这样就可以由扩展名来获取相应图标的位图了。关于如何生成 Images.py 可以参考 使用 wx.tools.img2py (4)

    bmp = getattr(Iamges, IconMap.Ext2IconDict['py']).GetBitmap()
    

    1.1.3. 获取当前工作区的所有文件,并创建结点

    参考下面代码的 self.InitTree(),一般都是使用递归的思路。这一部分主要参考了 wxPython in action Chapter 15 的相关内容。

    1.1.4. 目录最小、最大化

    使用 AuiManager 可以实现子窗口的最大化、最小化等操作。

    import wx
    import wx.lib.agw.aui as aui
    class TestFrame(wx.Frame):
      
        def __init__(self, *args, **kw):
            super().__init__(*args, **kw)
            self.SetTitle("目录树")
            self.SetSize(800, 600)
    
            self.dir_tree = DirectoryTree(self, size=(200, 600))
            self.txt = wx.TextCtrl(self, -1, value="I'm 002",style=wx.TE_MULTILINE)
    
            # aui manager 可实现窗口的最大/小化,拖动
            self._mgr = aui.AuiManager(agwFlags=aui.AUI_MGR_LIVE_RESIZE)
            self._mgr.SetManagedWindow(self)
            self._mgr.AddPane(self.dir_tree, aui.AuiPaneInfo().Caption("workspace").
                              Left().Layer(1).Position(1).CloseButton(True).MaximizeButton(True).MinimizeButton(True))
            self._mgr.AddPane(self.txt, aui.AuiPaneInfo().CenterPane())
            
            # 记得要 Update
            self._mgr.Update()
    

    目录树的最小化、最大化如下图所示:

    min_max_tree

    1.1.5. 实现右键菜单操作

    使用 Window.PopupMenu() 可以实现弹出菜单,对应的事件为 wx.EVT_CONTEXT_MENU

    class TestFrame(wx.Frame):
        def __init__(self, *args, **kw):
            # ...
            self.Bind(wx.EVT_CONTEXT_MENU, self.OnTreeRightUp) # 右键弹出式菜单
            # ...
        def OnTreeRightUp(self, event):
            # ...
    
            menu = wx.Menu()
            menuitem = menu.Append(-1, "Send")
            self.Bind(wx.EVT_MENU, self.OnSend, menuitem)
            menu.Append(-1, "(new folder)")
            menu.Append(-1, "(new file)")
            menu.Append(-1, "(copy)")
            menu.Append(-1, "(paste)")
            menu.Append(-1, "(cut)")
    
            self.PopupMenu(menu)
            menu.Destroy()
    
            # ...
    

    选中文件,右键点击 Send,可将文件名发送到右侧文字框:

    left_pop_menu

    1.1.6. 及时地更新目录树内容

    每当离开应用窗口时,都可能会改变工作目录。所以更新目录树的时机为"鼠标重新点击应用程序",对应的事件为 wx.EVT_ACTIVATE,需要注意的是这个得绑定在 wx.Frame 类里,否则不起作用。

    为了简化思路,每当重新点击窗口时,要先判断前后的工作目录是否有发生变化,有变化才刷新树(刷新指的是:删除之前的所有子结点,再根据新目录重新创建结点),不然每次点击就刷新

    1.1.7. 对结点进行排序

    对结点进行排序,需要重写 TextCtrl.OnCompareItems(self, item1, item2),然后在实例调用 SortChildren()

    
    class MyTreeCtrl(wx.TreeCtrl):
        def __init__(self, *args, **kw):
            super().__init__(*args, **kw)
        
        def OnCompareItems(self, item1, item2):
            """重写 OnCompareItems
            data = [0, 文件夹名] / 
            data = [1, 文件名]
            """
            data1 = self.GetItemData(item1)
            data2 = self.GetItemData(item2)
            if data1[0] > data2[0]:
                return 1
            elif data1[0] < data2[0]:
                return -1
            else:
                if data1[1].lower() > data2[1].lower():
                    return 1
                elif data1[1].lower() < data2[1].lower():
                    return -1
                else:
                    return 0
    
    # ...
        def InitTree(self):
            """初始化树"""
            
            # ...
    
            # 根结点展开
            self.tree.Expand(self.root_id)
            # 对子结点排序
            self.tree.SortChildren(self.root_id)
    
    

    排完序的运行结果:

    my_tree_dir_sort

    1.2. 完整代码

    1.2.1. 目录树

    点击查看代码
    
    import wx
    import glob
    import os
    import wx.lib.agw.aui as aui
    
    import Images
    from IconMap import Ext2IconDict
    
    class MyTreeCtrl(wx.TreeCtrl):
        def __init__(self, *args, **kw):
            super().__init__(*args, **kw)
        
        def OnCompareItems(self, item1, item2):
            """重写 OnCompareItems
            data = [0, 文件夹名] / 
            data = [1, 文件名]
            """
            data1 = self.GetItemData(item1)
            data2 = self.GetItemData(item2)
            if data1[0] > data2[0]:
                return 1
            elif data1[0] < data2[0]:
                return -1
            else:
                if data1[1].lower() > data2[1].lower():
                    return 1
                elif data1[1].lower() < data2[1].lower():
                    return -1
                else:
                    return 0
            
    
    class DirectoryTree(wx.Window):
    
        def __init__(self, *args, **kw):
            super().__init__(*args, **kw)
    
            self.work_path = os.getcwd() # 默认为当前的工作区目录
    
            # 创建树
            self.tree = MyTreeCtrl(self, -1,
                style = wx.TR_DEFAULT_STYLE # 默认样式 
                       |wx.TR_TWIST_BUTTONS # 结点使用 >/v 而不是 +/-
                       |wx.TR_NO_LINES      # 不绘制结点之间的连线
            )
    
            # 初始化图像列表
            self.InitImageList()
    
            # 初始化树
            self.InitTree()
    
            # 布局
            sizer = wx.BoxSizer(wx.HORIZONTAL)
            sizer.Add(self.tree, 1, wx.EXPAND|wx.LEFT|wx.BOTTOM, 2)
            self.SetSizer(sizer)
    
        def InitImageList(self):
            """初始化图像列表"""
            # 创建一个图像列表
            il = wx.ImageList(16, 16)
            # 扩展名到图标 ID 的字典
            self.ext_map_imageId = {}
            for ext in Ext2IconDict:
                # 文件扩展名 to imageID
                bmp = getattr(Images, Ext2IconDict[ext]).GetBitmap()
                self.ext_map_imageId[ext] = il.Add(bmp)
            # 单独添加几种特殊情况
            self.ext_map_imageId['default_file'] = il.Add(getattr(Images, 'default_file').GetBitmap())
            self.ext_map_imageId['default_folder'] = il.Add(getattr(Images, 'default_folder').GetBitmap())
            self.ext_map_imageId['default_folder_opened'] = il.Add(getattr(Images, 'default_folder_opened').GetBitmap())
            # 将图像分配给树
            self.tree.AssignImageList(il)
            
        def InitTree(self):
            """初始化树"""
            # 获取工作目录所有子文件和子目录
            self.all_files = self.GetAllFileFrom(self.work_path)
            # 设置根目录
            if self.tree.GetCount() < 1:
                self.root_id = self.tree.AddRoot(self.all_files[0], data=[0, self.all_files[0]])
            else:
                self.root_id = self.tree.GetRootItem()
            # 清楚所有子结点
            self.tree.DeleteChildren(self.root_id)
            # 递归添加子节点
            self.AddTreeNodes(self.root_id, self.all_files[1])
            # 根结点展开
            self.tree.Expand(self.root_id)
            # 对子结点排序
            self.tree.SortChildren(self.root_id)
    
        def GetAllFileFrom(self, path):
            """递归获取包括该目录及其子文件、子目录所有文件,
               生成一个“树状列表”,如: [root, [sub-list]]
                [root, [
                        item1,
                        [item2, [
                            item21, item22, item23
                        ],
                        item3,
                    ]
                ]
            """
            sub_list = []
            for file_name in glob.iglob(os.path.join(path, "*")):
                if os.path.isdir(file_name):
                    sub_list.append(self.GetAllFileFrom(file_name))
                else:
                    file_name_without_root = file_name.split('\\')[-1]
                    sub_list.append(file_name_without_root)
            root = path.split('\\')[-1]
            return [root, sub_list]
    
        def CompareTreeList(self, plist, qlist):
            """比较两个树状列表"""
            if len(plist) != len(qlist):
                return False
    
            res = True
            for p,q in zip(plist, qlist):
                if type(p) == str and type(q) ==str:
                    if p != q:
                        return False
                elif type(p) == list and type(q) == list:
                    res =  self.CompareTreeList(p, q)
                else:
                    return False
            return res
    
        def AddTreeNodes(self, parentItem, items):
            """递归添加树结点
    
            Args:
                parentItem ([treeItemID]): [description]
                items ([list]): [description]
            """
            for item in items:
                if type(item) == str:
                    newItem = self.tree.AppendItem(parentItem, item, data=[1, item])
                    # 设置数据图像
                    ext = item.split('.')[-1] # 扩展名
                    if ext in self.ext_map_imageId:
                        self.tree.SetItemImage(newItem, self.ext_map_imageId[ext], which=wx.TreeItemIcon_Normal)
                    else:
                        self.tree.SetItemImage(newItem, self.ext_map_imageId['default_file'], which=wx.TreeItemIcon_Normal)
                else:
                    newItem = self.tree.AppendItem(parentItem, item[0], data=[0, item[0]])
                    # 设置结点的图像(文件夹)
                    self.tree.SetItemImage(newItem, self.ext_map_imageId['default_folder'], which=wx.TreeItemIcon_Normal)
                    self.tree.SetItemImage(newItem, self.ext_map_imageId['default_folder_opened'], which=wx.TreeItemIcon_Expanded)
                    # 递归调用
                    self.AddTreeNodes(newItem, item[1]) 
    
        def IsDirChange(self):
            """判断当前工作区目录是否修改"""
            tmp_list = self.GetAllFileFrom(self.work_path)
            return not self.CompareTreeList(tmp_list, self.all_files)
    
        def GetSelectionItem(self):
            """返回被选中的结点"""
            return self.tree.GetFocusedItem()
    
        def Unselect(self):
            """取消选中的结点"""
            self.tree.Unselect()
    
        def GetItemData(self, id):
            """返回指定结点的data"""
            return self.tree.GetItemData(id)
    
    

    1.2.2. 测试类

    点击查看代码
    class TestFrame(wx.Frame):
        
        def __init__(self, *args, **kw):
            super().__init__(*args, **kw)
            self.SetTitle("目录树")
            self.SetSize(800, 600)
    
            self.dir_tree = DirectoryTree(self, size=(200, 600))
            self.txt = wx.TextCtrl(self, -1, value="I'm 002",style=wx.TE_MULTILINE)
    
            # aui manager 可实现窗口的最大/小化,拖动
            self._mgr = aui.AuiManager(agwFlags=aui.AUI_MGR_LIVE_RESIZE)
            self._mgr.SetManagedWindow(self)
            self._mgr.AddPane(self.dir_tree, aui.AuiPaneInfo().Caption("workspace").
                              Left().Layer(1).Position(1).CloseButton(True).MaximizeButton(True).MinimizeButton(True))
            self._mgr.AddPane(self.txt, aui.AuiPaneInfo().CenterPane())
            
            # 记得要 Update
            self._mgr.Update()
    
            # 绑定事件
            self.Bind(wx.EVT_ACTIVATE, self.OnActive) # 每当鼠标重新点击此窗口,检查更新目录
            self.Bind(wx.EVT_CONTEXT_MENU, self.OnTreeRightUp) # 右键弹出式菜单
    
        def OnActive(self, event):
            if event.GetActive():
                print("On Active!" )
                if self.dir_tree.IsDirChange():
                    print("Refresh dir-tree!")
                    self.dir_tree.InitTree()
    
        def OnTreeRightUp(self, event):
            id = self.dir_tree.GetSelectionItem()
            self.msg = ""
            if id.IsOk():
                self.msg = self.dir_tree.GetItemData(id)
                print(self.msg)
    
            menu = wx.Menu()
            menuitem = menu.Append(-1, "Send")
            self.Bind(wx.EVT_MENU, self.OnSend, menuitem)
            menu.Append(-1, "(new folder)")
            menu.Append(-1, "(new file)")
            menu.Append(-1, "(copy)")
            menu.Append(-1, "(paste)")
            menu.Append(-1, "(cut)")
    
            self.PopupMenu(menu)
            menu.Destroy()
    
            # 记得取消当前选择
            self.dir_tree.Unselect()
    
        def OnSend(self, event):
            self.txt.AppendText("\nClick: "+self.msg)
            
    if __name__ == '__main__':
        app = wx.App()
        frm = TestFrame(None)
        frm.Show()
        app.MainLoop()
    

    1.2.3. 相关图标

    1.3. 相关参考

  • 相关阅读:
    Stepping Number
    Replace String
    String Permutation
    Clock Angle
    Keypad Permutation
    Replace Words
    1、奉加微 PHY6202 Get started
    Python3 修改二进制文件
    Google Fast Pair
    python 校验 BLE resolvable private address
  • 原文地址:https://www.cnblogs.com/wreng/p/15785732.html
Copyright © 2011-2022 走看看