ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 第十五章 树形控件 本章内容 * 创建树形控件并添加项目 * 使用样式来设计树形控件 * 在程序中访问树形控件 * 处理树形控件中的选择 * 控制项目的可见性 树形控件是用于显示复杂数据的控件。这里,树形控件被设计用来通过分级层来显示数据,你可以看到每 块数据都有父子方面的东西。一个标准的例子就是文件树,其中的目录中有子目录或文件,从而形成了文 件的一个嵌套的层次。另一个例子是`HTML`或`XML`文档的文档对象模型(`DOM)`树。和列表与网格控件一样,树 形控件也提供了在项目显示方面的灵活性,并允许你就地编辑树形控件中的项目。在这一章中,我们将给 你展示如何编辑树形控件中的项目及如何响应用户事件。 ## 创建树形控件并添加项目 树形控件是类`wx.TreeCtrl`的实例。图15.1显示了一个树形控件的样例。 **图15.1** ![](https://box.kancloud.cn/2016-08-21_57b9964777de0.gif) 例15.1是产生图15.1的代码。这个例子中的机制我们将在后面的部分讨论。注意其中的树形控件中的数据 是来自于一个名为`data.py`外部文件的。 **例15.1** **树形控件示例** ``` import wx import data class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="simple tree", size=(400,500)) # Create the tree self.tree = wx.TreeCtrl(self) # Add a root node root = self.tree.AddRoot("wx.Object") # Add nodes from our data set self.AddTreeNodes(root, data.tree) # Bind some interesting events self.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnItemExpanded, self.tree) self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnItemCollapsed, self.tree) self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged, self.tree) self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnActivated, self.tree) # Expand the first level self.tree.Expand(root) def AddTreeNodes(self, parentItem, items): """ Recursively traverses the data structure, adding tree nodes to match it. """ for item in items: if type(item) == str: self.tree.AppendItem(parentItem, item) else: newItem = self.tree.AppendItem(parentItem, item[0]) self.AddTreeNodes(newItem, item[1]) def GetItemText(self, item): if item: return self.tree.GetItemText(item) else: return "" def OnItemExpanded(self, evt): print "OnItemExpanded: ", self.GetItemText(evt.GetItem()) def OnItemCollapsed(self, evt): print "OnItemCollapsed:", self.GetItemText(evt.GetItem()) def OnSelChanged(self, evt): print "OnSelChanged: ", self.GetItemText(evt.GetItem()) def OnActivated(self, evt): print "OnActivated: ", self.GetItemText(evt.GetItem()) app = wx.PySimpleApp(redirect=True) frame = TestFrame() frame.Show() app.MainLoop() ``` 下面的`wx.TreeCtrl`的构造函数是一个典型的`wxPython`窗口部件构造函数: ``` wx.TreeControl(parent, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.TR_HAS_BUTTONS, validator=wx.DefaultValidator, name="treeCtrl") ``` 其中的参数意义与通常的`wx.Window`对象相同。该构造函数提供给你了一个没有元素的空的树。 **另附`data.py`文件**: ``` # Some sample data for the treectrl samples tree = [ "wx.AcceleratorTable", "wx.BrushList", "wx.BusyInfo", "wx.Clipboard", "wx.Colour", "wx.ColourData", "wx.ColourDatabase", "wx.ContextHelp", ["wx.DC", [ "wx.ClientDC", ["wx.MemoryDC", [ "wx.lib.colourchooser.canvas.BitmapBuffer", ["wx.BufferedDC", [ "wx.BufferedPaintDC", ]]]], "wx.MetaFileDC", "wx.MirrorDC", "wx.PaintDC", "wx.PostScriptDC", "wx.PrinterDC", "wx.ScreenDC", "wx.WindowDC",]], "wx.DragImage", "wx.Effects", "wx.EncodingConverter", ["wx.Event", [ "wx.ActivateEvent", "wx.CalculateLayoutEvent", "wx.CloseEvent", ["wx.CommandEvent", [ "wx.calendar.CalendarEvent", "wx.ChildFocusEvent", "wx.ContextMenuEvent", "wx.gizmos.DynamicSashSplitEvent", "wx.gizmos.DynamicSashUnifyEvent", "wx.FindDialogEvent", "wx.grid.GridEditorCreatedEvent", "wx.HelpEvent", ["wx.NotifyEvent",[ ["wx.BookCtrlEvent", [ "wx.ListbookEvent", "wx.NotebookEvent ",]], "wx.grid.GridEvent", "wx.grid.GridRangeSelectEvent", "wx.grid.GridSizeEvent", "wx.ListEvent", "wx.SpinEvent", "wx.SplitterEvent", "wx.TreeEvent", "wx.wizard.WizardEvent ",]], ["wx.PyCommandEvent", [ "wx.lib.colourselect.ColourSelectEvent", "wx.lib.buttons.GenButtonEvent", "wx.lib.gridmovers.GridColMoveEvent", "wx.lib.gridmovers.GridRowMoveEvent", "wx.lib.intctrl.IntUpdatedEvent", "wx.lib.masked.combobox.MaskedComboBoxSelectEvent", "wx.lib.masked.numctrl.NumberUpdatedEvent", "wx.lib.masked.timectrl.TimeUpdatedEvent ",]], "wx.SashEvent", "wx.ScrollEvent", "wx.stc.StyledTextEvent", "wx.TextUrlEvent", "wx.UpdateUIEvent", "wx.WindowCreateEvent", "wx.WindowDestroyEvent ",]], "wx.DisplayChangedEvent", "wx.DropFilesEvent", "wx.EraseEvent", "wx.FocusEvent", "wx.IconizeEvent", "wx.IdleEvent", "wx.InitDialogEvent", "wx.JoystickEvent", "wx.KeyEvent", "wx.MaximizeEvent", "wx.MenuEvent", "wx.MouseCaptureChangedEvent", "wx.MouseEvent", "wx.MoveEvent", "wx.NavigationKeyEvent", "wx.NcPaintEvent", "wx.PaintEvent", "wx.PaletteChangedEvent", "wx.ProcessEvent", ["wx.PyEvent", [ "wx.lib.throbber.UpdateThrobberEvent ",]], "wx.QueryLayoutInfoEvent", "wx.QueryNewPaletteEvent", "wx.ScrollWinEvent", "wx.SetCursorEvent", "wx.ShowEvent", "wx.SizeEvent", "wx.SysColourChangedEvent", "wx.TaskBarIconEvent", "wx.TimerEvent ",]], ["wx.EvtHandler", [ "wx.lib.gridmovers.GridColMover", "wx.lib.gridmovers.GridRowMover", "wx.html.HtmlHelpController", "wx.Menu", "wx.Process", ["wx.PyApp", [ ["wx.App", [ "wx.py.PyAlaCarte.App", "wx.py.PyAlaMode.App", "wx.py.PyAlaModeTest.App", "wx.py.PyCrust.App", "wx.py.PyShell.App", ["wx.py.filling.App", [ "wx.py.PyFilling.App ",]], ["wx.PySimpleApp", [ "wx.lib.masked.maskededit.test",]], "wx.PyWidgetTester ",]]]], "wx.TaskBarIcon", ["wx.Timer", [ "wx.PyTimer ",]], ["wx.Validator", [ ["wx.PyValidator",[ "wx.lib.intctrl.IntValidator",]]]], ["wx.Window", [ ["wx.lib.colourchooser.canvas.Canvas", [ "wx.lib.colourchooser.pycolourslider.PyColourSlider", "wx.lib.colourchooser.pypalette.PyPalette",]], "wx.lib.gridmovers.ColDragWindow", ["wx.Control",[ ["wx.BookCtrl", [ "wx.Listbook", ["wx.Notebook",[ "wx.py.editor.EditorNotebook", "wx.py.editor.EditorShellNotebook",]] ]], ["wx.Button", [ ["wx.BitmapButton",[ "wx.lib.colourselect.ColourSelect", "wx.ContextHelpButton", "wx.lib.foldmenu.FoldOutMenu ",]] ]], "wx.calendar.CalendarCtrl", "wx.CheckBox", ["wx.ComboBox",[ ["wx.lib.masked.combobox.BaseMaskedComboBox", [ "wx.lib.masked.combobox.ComboBox", "wx.lib.masked.combobox.PreMaskedComboBox",]] ]], ["wx.ControlWithItems", [ ["wx.Choice",[ "wx.DirFilterListCtrl ",]], "wx.ListBox", "wx.CheckListBox ",]], "wx.Gauge", "wx.GenericDirCtrl", "wx.gizmos.LEDNumberCtrl", ["wx.ListCtrl",[ "wx.ListView ",]], ["wx.PyControl",[ "wx.lib.calendar.Calendar", ["wx.lib.buttons.GenButton",[ ["wx.lib.buttons.GenBitmapButton",[ ["wx.lib.buttons.GenBitmapTextButton",[ "wx.lib.buttons.GenBitmapTextToggleButton",]], "wx.lib.buttons.GenBitmapToggleButton ",]], "wx.lib.buttons.GenToggleButton ",]], "wx.lib.statbmp.GenStaticBitmap", "wx.lib.stattext.GenStaticText", "wx.lib.popupctl.PopButton", "wx.lib.popupctl.PopupControl", "wx.lib.ticker.Ticker ",]], "wx.RadioBox", "wx.RadioButton", "wx.ScrollBar", "wx.Slider", "wx.SpinButton", "wx.SpinCtrl", ["wx.StaticBitmap",[ "wx.lib.fancytext.StaticFancyText ",]], "wx.StaticBox", "wx.StaticLine", "wx.StaticText", ["wx.stc.StyledTextCtrl",[ ["wx.py.editwindow.EditWindow",[ "wx.py.crust.Display", "wx.py.editor.EditWindow", "wx.py.filling.FillingText", "wx.py.shell.Shell",]], "wx.lib.pyshell.PyShellWindow ",]], ["wx.TextCtrl", [ ["wx.lib.masked.textctrl.BaseMaskedTextCtrl",[ "wx.lib.masked.ipaddrctrl.IpAddrCtrl", "wx.lib.masked.numctrl.NumCtrl", "wx.lib.masked.textctrl.PreMaskedTextCtrl", "wx.lib.masked.textctrl.TextCtrl", "wx.lib.masked.timectrl.TimeCtrl ",]], "wx.py.crust.Calltip", "wx.lib.sheet.CTextCellEditor", "wx.py.crust.DispatcherListing", "wx.lib.intctrl.IntCtrl", "wx.lib.rightalign.RightTextCtrl", "wx.py.crust.SessionListing",]], "wx.ToggleButton", "wx.ToolBar", ["wx.TreeCtrl",[ "wx.py.filling.FillingTree", "wx.gizmos.RemotelyScrolledTreeCtrl ",]], "wx.gizmos.TreeListCtrl ",]], "wx.gizmos.DynamicSashWindow", "wx.lib.multisash.EmptyChild", "wx.glcanvas.GLCanvas", "wx.lib.imagebrowser.ImageView", "wx.MDIClientWindow", "wx.MenuBar", "wx.lib.multisash.MultiClient", "wx.lib.multisash.MultiCloser", "wx.lib.multisash.MultiCreator", "wx.lib.multisash.MultiSash", "wx.lib.multisash.MultiSizer", "wx.lib.multisash.MultiSplit", "wx.lib.multisash.MultiViewLeaf", ["wx.Panel",[ "wx.gizmos.EditableListBox", ["wx.lib.filebrowsebutton.FileBrowseButton",[ "wx.lib.filebrowsebutton.DirBrowseButton", "wx.lib.filebrowsebutton.FileBrowseButtonWithHistory",]], "wx.lib.floatcanvas.FloatCanvas.FloatCanvas", "wx.lib.floatcanvas.NavCanvas.NavCanvas", "wx.NotebookPage", ["wx.PreviewControlBar",[ "wx.PyPreviewControlBar ",]], "wx.lib.colourchooser.pycolourbox.PyColourBox", "wx.lib.colourchooser.pycolourchooser.PyColourChooser", ["wx.PyPanel",[ "wx.lib.throbber.Throbber",]], "wx.lib.shell.PyShell", "wx.lib.shell.PyShellInput", "wx.lib.shell.PyShellOutput", ["wx.ScrolledWindow",[ "wx.lib.editor.editor.Editor", ["wx.grid.Grid",[ "wx.lib.sheet.CSheet ",]], ["wx.html.HtmlWindow",[ "wx.lib.ClickableHtmlWindow.PyClickableHtmlWindow",]], "wx.PreviewCanvas", "wx.lib.printout.PrintTableDraw", ["wx.PyScrolledWindow",[ "wx.lib.scrolledpanel.ScrolledPanel",]], "wx.lib.ogl.ShapeCanvas", "wx.gizmos.SplitterScrolledWindow ",]], ["wx.VScrolledWindow",[ ["wx.VListBox", [ "wx.HtmlListBox ",]] ]], ["wx.wizard.WizardPage", [ "wx.wizard.PyWizardPage", "wx.wizard.WizardPageSimple ",]], "wx.lib.plot.PlotCanvas", "wx.lib.wxPlotCanvas.PlotCanvas", ["wx.PopupWindow",[ "wx.lib.foldmenu.FoldOutWindow", ["wx.PopupTransientWindow",[ "wx.TipWindow ",]] ]], ["wx.PyWindow", [ "wx.lib.analogclock.AnalogClockWindow",]], "wx.lib.gridmovers.RowDragWindow", ["wx.SashWindow",[ "wx.SashLayoutWindow ",]], "wx.SplashScreenWindow", ["wx.SplitterWindow",[ "wx.py.crust.Crust", "wx.py.filling.Filling", "wx.gizmos.ThinSplitterWindow ",]], "wx.StatusBar", ["wx.TopLevelWindow",[ ["wx.Dialog",[ "wx.lib.calendar.CalenDlg", "wx.ColourDialog", "wx.DirDialog", "wx.FileDialog", "wx.FindReplaceDialog", "wx.FontDialog", "wx.lib.imagebrowser.ImageDialog", "wx.MessageDialog", "wx.MultiChoiceDialog", "wx.lib.dialogs.MultipleChoiceDialog", "wx.PageSetupDialog", "wx.lib.popupctl.PopupDialog", "wx.PrintDialog", "wx.lib.dialogs.ScrolledMessageDialog", "wx.SingleChoiceDialog", "wx.TextEntryDialog", "wx.wizard.Wizard ",]], ["wx.Frame", [ "wx.lib.analogclockopts.ACCustomizationFrame", "wx.py.filling.FillingFrame", ["wx.py.frame.Frame",[ "wx.py.crust.CrustFrame", ["wx.py.editor.EditorFrame",[ "wx.py.editor.EditorNotebookFrame",]], "wx.py.shell.ShellFrame",]], "wx.html.HtmlHelpFrame", "wx.MDIChildFrame", "wx.MDIParentFrame", "wx.MiniFrame", ["wx.PreviewFrame",[ "wx.PyPreviewFrame ",]], "wx.ProgressDialog", "wx.SplashScreen", "wx.lib.splashscreen.SplashScreen", "wx.lib.masked.maskededit.test2", "wx.lib.plot.TestFrame ",]] ]], "wx.gizmos.TreeCompanionWindow ",]] ]] ]], "wx.FileHistory", "wx.FileSystem", "wx.FindReplaceData", "wx.FontData", "wx.FontList", "wx.FSFile", ["wx.GDIObject",[ "wx.Bitmap", "wx.Brush", "wx.Cursor", "wx.Font", "wx.Icon", "wx.Palette", "wx.Pen", "wx.Region ",]], "wx.glcanvas.GLContext", ["wx.grid.GridTableBase", [ "wx.grid.GridStringTable", "wx.grid.PyGridTableBase ",]], ["wx.html.HtmlCell", [ "wx.html.HtmlColourCell", "wx.html.HtmlContainerCell", "wx.html.HtmlFontCell", "wx.html.HtmlWidgetCell", "wx.html.HtmlWordCell ",]], "wx.html.HtmlDCRenderer", "wx.html.HtmlEasyPrinting", "wx.html.HtmlFilter", "wx.html.HtmlLinkInfo", ["wx.html.HtmlParser", [ "wx.html.HtmlWinParser ",]], "wx.html.HtmlTag", ["wx.html.HtmlTagHandler", [ ["wx.html.HtmlWinTagHandler", [ "wx.lib.wxpTag.wxpTagHandler ",]] ]], "wx.Image", ["wx.ImageHandler", [ ["wx.BMPHandler", [ ["wx.ICOHandler", [ ["wx.CURHandler", [ "wx.ANIHandler ",]] ]] ]], "wx.GIFHandler", "wx.JPEGHandler", "wx.PCXHandler", "wx.PNGHandler", "wx.PNMHandler", "wx.TIFFHandler", "wx.XPMHandler ",]], "wx.ImageList", "wx.IndividualLayoutConstraint", "wx.LayoutAlgorithm", ["wx.LayoutConstraints", [ "wx.lib.anchors.LayoutAnchors", "wx.lib.layoutf.Layoutf",]], "wx.ListItem", "wx.Mask", "wx.MenuItem", "wx.MetaFile", "wx.PageSetupDialogData", "wx.PenList", "wx.PrintData", "wx.PrintDialogData", "wx.Printer", ["wx.Printout", [ "wx.html.HtmlPrintout", "wx.lib.plot.PlotPrintout", "wx.lib.printout.SetPrintout ",]], ["wx.PrintPreview", [ "wx.PyPrintPreview ",]], "wx.RegionIterator", ["wx.Sizer", [ "wx.BookCtrlSizer", ["wx.BoxSizer", [ "wx.StaticBoxSizer", ]], ["wx.GridSizer", [ ["wx.FlexGridSizer", [ "wx.GridBagSizer",]] ]], "wx.NotebookSizer", "wx.PySizer",]], ["wx.SizerItem", [ "wx.GBSizerItem",]], "wx.SystemOptions", "wx.ToolBarToolBase", "wx.ToolTip", "wx.gizmos.TreeListColumnInfo", "wx.xrc.XmlDocument", "wx.xrc.XmlResource", "wx.xrc.XmlResourceHandler ", ] ``` ### 如何添加一个root(根)元素? 当你将项目添加到树时,你首先必须要添加的项目是`root(`根)元素。添加根元素的方法如下所示: ``` AddRoot(text, image=-1, selImage=-1, data=None) ``` 你只能添加一个根元素。如果你在已经存在一个根元素后,再添加第二根元素的话,那么`wxPython`将引发一个异常。其中的参数`text`包含用于根元素的显示字符串。参数`image`是图像列表中的一个索引,代表要显示在参数`text`旁边的图像。这个图像列表将在15.5节中作更详细的讨论,但是现在只要知道它的行为类似于用于列表控件的图像列表。参数`data`是一个与项目相关的数据对象,主要的目的是分类。 `AddRoot()`方法返回一个关于根项目的`ID`。树形控件使用它自己的类`wx.TreeItemId`来管理项目。在大多数时候,你不需要关心`ID`的具体值,你只需要知道每个项目都有一个唯一的`wx.TreeItemId`就够了,并且这个值可以使用等号测试。`wx.TreeItemId`不映射到任何简单的类型——它的值没有任何的关联性,因为你只是把它用于相等测试。 ### 如何将更多的项目添加到树中? 一旦你有了根元素,你就可以开始向树中添加元素了。用的最多的方法是`AppendItem(parent, text, image=`-1, `selImage=`-1, `data=None)`。参数`parent`是已有的树项目的`wx.TreeItemId`,它作为新项目的父亲。参数`text`是显示新项目的文本字符串。参数`image`和`selImage`的意义与方法`AddRoot()`中的相同。该方法将新的项目放置到其父项目的孩子列表的末尾。这个方法返回新创建的项目的`wx.TreeItemId`。如果你想给新的项目添加子项目的话,你需要拥有这个`ID`。一个示例如下: ``` rootId = tree.AddRoot("The Root") childId = tree.AppendItem(rootId, "A Child") grandChildId = tree.AppendItem(childId, "A Grandchild") ``` 上面的这个代码片断增加了一个`root(`根)项目,然后给根项目添加了一个子项目,然后给子项目添加了它的子项目。 如果要将子项目添加到孩子列表的开头的话,使用方法`PrependItem(parent, text, image=`-1, `selImage=`-1, `data=None)`。 如果你想将一个项目插入树的任意点上,你可以使用后面的两种方法之一。第一个是`InsertItem(parent, previous, text,image=`-1, `selImage=`-1, `data=None)`。其中参数`previous`其父项目中的子列表中的项目的`wx.TreeItemId`。插入的项目将放置在该项目的后面。第二个方法是`InsertItemBefore(parent, before, text, image=`-1, `selImage=`-1, `data=None)`。该方法将新的项目放置在`before`所代表的项目之前。然而参数`before`不是一个项目的`ID`。它是项目在孩子列表中的整数索引。第二个方法返回新项目的一个`wx.TreeItemId`。 ### 如何管理项目? 要去掉树中的一个项目,可以使用`Delete(item)`方法,其中参数`item`是该项目的`wx.TreeItemId`。调用这个方法将导致一个`EVT_TREE_Delete_ITEM`类型的树的事件被触发。后面的章节我们将讨论树的事件类型。要删除一个项目的所有子项目,而留下该项目自身,可以使用方法`DeleteChildren(item)`,其中参数`item`也是一个`wx.TreeItemId`。该方法不产生一个删除事件。要清除整个树,使用方法`DeleteAllItems()`。该方法为每个项目生成一个删除事件,但是,这在某些老版的`Windows`系统上不工作。 一旦你将一个项目添加到树中,你就可以使用方法`GetItemText(item)`来得到该项目在显示在树中的文本,其中参数`item`是一个`wx.TreeItemId`。如果你想改变一个项目的显示文本,可以使用方法`SetItemText(item, text)`,其中参数`item`是一个`wx.TreeItemId`,参数`text`是一个新的显示文本。 最后,你可以使用方法`GetCount()`来得到树中项目的总数。如果你想得到特定项目下的子项目的数量,可以使用方法`GetChildrenCount(item, recursively=True)`。其中参数`item`是一个`wx.TreeItemId`,参数`recursively`如果为`False`,那么该方法只返回直接的子项目的数量,如果为`True`,则返回所有的子项目而不关嵌套有多深。 ## 树控件的显示样式 树控件的显示样式分为四类。第一类定义了树中显示在父项目的文本旁的按钮(用以展开或折叠子项目)。它们显示在表15.1中。 **表15.1** **树控件中的按钮** | | | | --- | --- | | `wx.TR_HAS_BUTTONS` | 按钮。在`Windows`上,+用于标明项目可以被展开,-表示可以折叠。 | | `wx.TR_NO_BUTTONS` | 没有按钮。 | 接下来的一类显示在表15.2中,它们决定树控件将连接线绘制在何处。 **表15.2** **树控件中的连接线** | | | | --- | --- | | `wx.TR_LINES_AT_ROOT` | 如果设置了这个样式,那么树控件将在多个`root`项目之间绘制连线。注意,如果`wx.TR_HIDE_ROOT`被设置了,那么你就有多个`root`项目。 | | `wx.TR_NO_LINES` | 如果设置了这个样式,那么树控件将不在兄弟项目间绘制连接线。这个样式将代替`wx.TR_LINES_AT_ROOT`。 | | `wx.TR_ROW_LINES ` | 树控件在行之间将绘制边距。 | 第三类样式显示在表15.3中,用于控制树控件的选择模式。 **表15.3** **树控件的选择模式** | | | | --- | --- | | `wx.TR_EXTENDED` | 可以选择多个不连续的项。不是对所有的系统有效。 | | `wx.TR_MULTIPLE` | 可以选择一块且仅一块连续的项。 | | `wx.TR_SINGLE` | 一次只能选择一个结点。这是默认模式。 | 表15.4显示了其它的一样可作用于树的显示的样式。 **表15.4** **树的其它显示样式** | | | | --- | --- | | `wx.TR_FULL_ROW_HIGHLIGHT` | 如果设置了这个样式,那么当被选择时,被选项的整个行将高亮显示。默认情况下,只是文本区高亮。在`Windows`上,该样式只在也设置`wx.NO_LINES`时有效。 | | `wx.TR_HAS_VARIABLE_ROW_HEIGHT` | 如果设置了这个样式,则行的高度将根据其中的图像和文本而不同。否则,所有的行将是同样的高度(取决于最高的行)。 | | `wx.TR_HIDE_ROOT` | 如果设置了这个样式,则通过`AddRoot()`确定的`root`元素将不被显示。此时该结节的所有子项就如同它们是`root`一样显示。这个样式用于让一个树具有多个`root`元素的外观。 | 最后,`wx.TR_DEFAULT_STYLE`让你的树显示出最接近当前操作系统本地控件的样式。在程序中,你可以使用`SetWindowStyle(styles)`来改变样式,其中参数`styles`是你想要的新样式。 树形控件有几个方法可以用以改变它的显示特性。在这些方法中,参数`item`是你想要改变的项的`wx.TreeItemId`。你可以使用方法`SetItemBackgroundColor(item, col)`来设置项目的背景色,其中参数`col`是一个`wx.Colour`或其它能够转换为颜色的东西。你可以使用`SetItemTextColour(item,col)`来改变文本的颜色。你可以使用`SetItemFont(item, font)`来设置项目的显示字体,其中参数`font`是一个`wx.Font`实例。如果你只想显示文本为粗体,你可以使用方法`SetItemBold(item, bold=True)`,其中参数`bold`是一个布尔值,它决定是否显示为粗体。上面的四个`set`*方法都有对应的`get`*方法,如下所示: `GetItemBackgroundColor(item), GetItemTextColour(item), GetItemFont(item), `和`IsBold(item)`。其中的`item`参数是一个`wx.TreeItemId`。 ## 对树形控件的元素排序 对树形控件的元素排序的基本机制是方法`SortChildren(item)`。其中参数`item`是`wx.TreeItemId`的一个实例。该方法对此项目的子项目按显示字符串的字母的顺序进行排序。 对于树的排序,每个树项目都需要有已分派的数据,不管你是否使用默认排序。在默认情况中,所指派的数据是`None`,但是对于排序任务,在树控件中你还是需要显式地设置。 在15.1节,我们提及到了让你能够创建一个树项目及将该项目与一个任意数据对象相关联的方法。我们也告诉你不要使用这个机制。数据项目是一个`wx.TreeItemData`。在`wxPython`中在一预定义的快捷的方法,使你能够用以将一个`Python`对象与一个树项目关联起来。 快捷的`set`*方法是`SetItemPyData(item, obj)`。其中参数`item`是一个`wx.TreeItemId`,参数`obj`是一个任意的`Python`对象,`wxPython`在后台管理这个关联。当你想得到这个数据项时,你可以调用`GetItemPyData(item)`,它返回相关的`Python`对象。 注意:对于`wx.TreeItemData`有一个特别的构造函数:`wx.TreeItemData(obj)`,其中参数`obj`是一个`Python`对象。因此你可以使用`GetItemData(item)`和`SetItemData(item, obj)`方法来处理这个`Python`数据。这是 `SetItemPyData()`方法的后台机制。这一信息在某些时候可能对你是有用的,但是大部分时候,你还是应该使用`SetItemPyData(item, obj)`和`GetItemPyData(item)`方法。 要使用关联的数据来排序你的树,你的树必须是一个`wx.TreeCtrl`的自定义的子类,并且你必须覆盖`OnCompareItems(item1, item2)`方法。其中参数`item1, item2`是要比较的两个项的`wx.TreeItemId`实例。如果`item1`应该排在`item2`的前面,则方法返回-1,如果`item1`应该排在`item2`的后面,则返回1,相等则返回0。该方法是在当树控件为了排序而比较计算每个项时自动被调用的。你可以在`OnCompareItems()`方法中做你想做的事情。尤其是,你可以如下调用`GetItemPyData()`方法: ``` def OnCompareItems(self, item1, item2); data1 = self.GetItemPyData(item1) data2 = self.GetItemPyData(item2) return cmp(data1, data2) ``` ## 控制与每项相关的图像 用于树形控件的图像是由一个图像列表来维护的,这非常类似于列表控件中的图像维护。有关创建图像列表的细节,参见13章。一旦你创建了图像列表,你就可以使用`SetImageList(imageList)`或`AssignImageList(imageList)`方法把它分配给树控件。前者使得图像列表可以被其它控件共享,后者的图像列表所有权属于树控件。之后,你可能使用方法`GetImageList()`来得到该图像列表。图15.2显示了一个带有一些图像的树。 **图15.2** ![](https://box.kancloud.cn/2016-08-21_57b996478c62b.gif) 例15.2是产生图15.2的代码。它使用了`ArtProvider`对象来提供图像。 **例15.2** **一个带有图标的树控件** ``` #-*- encoding:UTF-8 -*- import wx import data class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="simple tree with icons", size=(400,500)) # 创建一个图像列表 il = wx.ImageList(16,16) # 添加图像到列表 self.fldridx = il.Add( wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, (16,16))) self.fldropenidx = il.Add( wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, (16,16))) self.fileidx = il.Add( wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, (16,16))) # 创建树 self.tree = wx.TreeCtrl(self) # 给树分配图像列表 self.tree.AssignImageList(il) root = self.tree.AddRoot("wx.Object") self.tree.SetItemImage(root, self.fldridx, wx.TreeItemIcon_Normal)# 设置根的图像 self.tree.SetItemImage(root, self.fldropenidx, wx.TreeItemIcon_Expanded) self.AddTreeNodes(root, data.tree) self.tree.Expand(root) def AddTreeNodes(self, parentItem, items): for item in items: if type(item) == str: newItem = self.tree.AppendItem(parentItem, item) self.tree.SetItemImage(newItem, self.fileidx, wx.TreeItemIcon_Normal)# 设置数据图像 else: newItem = self.tree.AppendItem(parentItem, item[0]) self.tree.SetItemImage(newItem, self.fldridx, wx.TreeItemIcon_Normal)# 设置结点的图像 self.tree.SetItemImage(newItem, self.fldropenidx, wx.TreeItemIcon_Expanded) self.AddTreeNodes(newItem, item[1]) def GetItemText(self, item): if item: return self.tree.GetItemText(item) else: return "" app = wx.PySimpleApp(redirect=True) frame = TestFrame() frame.Show() app.MainLoop() ``` 如你所见,当你添加项目到列表中时,对于未选和选中状态,有两个不同的图像可用于分配给项目。和列表控件一样,你可以指定图像在图像列表中的索引。如果你想在项目被创建后得到所分配的图像,你可以使用方法`GetItemImage(item, which=wx.TreeItemIcon_Normal)`。其中参数`item`是项目的`wx.TreeItemId`。参数`which`控制你将得到了是哪个图像,默认值`wx.TreeItemIcon_Normal`,将得到该项目的未选状态的图像的索引。`which`的另一个值`wx.TreeItemIcon_Selected`的使用,将返回选中状态的图像,`wx.TreeItemIcon_Expanded`和`wxTreeItemIcon_SelectedExpanded`返回当该树项目被展开时所使用的图像。注意,后者的两个图像不能使用添加方法来被设置——如果你想设置的话,你必须使用方法`SetItemImage(item,  image,  which=wx.TreeItemIcon_Normal)`来实现。其中参数`item`是`wx.TreeItemId`的实例,参数`image`是新图像的整数索引,参数`which`同`get`*方法。 ## 使用编程的方式访问树。 在15.1节中,我们谈到没有直接得到一个给定项目的子项目的`Python`列表的方法,至于一个特定子项目的索引就更不用说了。要实现这个,我们需要使用本节所说的遍历方法来访问树的结点。 要开始遍历树,我们首先要使用`GetRootItem()`来得到根元素。该方法返回树的根元素的`wx.TreeItemId`。之后,你就可以使用诸如`GetItemText()`或`GetItemPyData()`之类的方法来获取关于根元素的更多的信息。 一旦你得到了一个项目,你就可以通过迭代器来遍历子项目的列表。你可以使用方法`GetFirstChild(item)`来得到子树中的第一个孩子,该方法返回一个二元元组(`child, cookie)`。参数`item`是第一个孩子的`wx.TreeItemId`。除了告诉你每一个孩子是什么外,该方法还初始化一个迭代对象,该对象使你能够遍历该树。`cookie`值只是一个标志,它使得树控件能够同时保持对多个迭代器的跟踪,而它们之间不会彼此干扰。 一旦你由`GetFirstChild()`得到了`cookie`,你就可以通过反复调用`GetNextChild(item, cookie)`来得到其余的孩子。这里的`item`父项的`ID`,`cookie`是由`GetFirstChild()`或前一个`GetNextChild()`调用返回的。`GetNextChild()`方法也返回一个二元元组(`child, cookie)`。如果此时没有下一个项目了,那么你就到达了孩子列表的末尾,系统将返回一个无效的孩子`ID`。你可以通过使用`wx.TreeItemId.IsOk()`或`__nonzero__`方法测试这种情况。下面的函数返回给定项目的所有孩子的文本的一个列表。 ``` def getChildren(tree, parent): result = [] item, cookie = tree.GetFirstChild(parent) while item: result.append(tree.GetItemText(item)) item, cookie = tree.getNextChild(parent, cookie) return result ``` 这个函数得到给定父项目的第一个孩子,然后将第一个孩子的文本添加到列表中,然后通过循环来得到每个子项目的文本并添加到列表中,直到得到一个无效的项,这时就返回`result`。 要得到父项目的最后一个孩子,你可以使用方法`GetLastChild(item)`,它返回列表中的最后的项目的`wx.TreeItemId`。由于这个方法不用于驱动迭代器来遍历整个孩子列表,所以它不需要`cookie`机制。如果你有这个最后的子项且你想得到它的父项,可以使用方法`GetItemParent(item)`来返回给定项的父项的`ID`。 你可以使用方法`GetNextSibling(item)`和`GetPrevSibling(item)`来在同级的项目间前后访问。这些方法均返回相关项的`wx.TreeItemId`。由这些方法同样不用于驱动迭代器,所以它们都不需要一个`cookie`。当你已经到达列表的两头时,将没有下一项或前一项,那么这些方法将返回一个无效的项(例如:`item.IsOk() == False`)。 要确定一个项是否有孩子,使用方法`ItemHasChildren(item)`,该方法返回布尔值`True`或`False`。你可以使用方法`SetItemHasChildren(item, hasChildren=True)`来将一个项设置为有子项,如果这样,即使该项没有实际的子项,它也将显示得与有子项的一样。也就是说该项旁边会有一个扩展或折叠的按钮,以便展开或折叠。这通常被用于实现一个虚的树控件。这个技术将在15.7节中演示。 ## 管理树中的选择 树形控件允许你通过程序的方式管理树中的被选项。基本的方法是`SelectItem(item,select=True) `。在单选树控件中,该方法将选择指定项`item`(`wx.TreeItemId`),同时自动撤消对先前选项的选择。如果参数`select`的取值是`False`,那么该方法将取消对参数`item`代表的项的选择。在一个可多选的树控件中,`SelectItem()`方法只改变`item`所代表的项的状态,而不改变树中其它项的选择状态。在可多选的树中,你也可以使用方法`ToggleItemSelection(item)`,它只切换参数`item`所代表项的选择状态。 对于取消选择还有三个快捷的方法。方法`Unselect()`取消单选模式树中的当前被选项的选择。在多选模式树中,使用`UnselectAll()`来取消所有的选择。如果你只想取消在一个多选树中的一个项的被选状态,可以使用方法`UnselectItem(item)`。 你也可以使用方法`IsSelected(item)`来查询一个项目的选择状态,该方法返回布尔值`True`或`False`。对于单选树,你可能使用方法`GetSelection()`来得到当前被选项目的`wx.TreeItemId`。对于多选树,可以使用方法`GetSelections()`来得到所有被选项的`wx.TreeItemId`的一个`Python`列表。 当树控件中发生选择变化的时候,有两个事件将被触发并可被捕获。第一个事件是`wx.EVT_TREE_SEL_CHANGING,`它在被选项实际改变之前发生。如果你要处理这个事件,你可以使用事件的`Veto()`方法来阻止选择的改变。在选择已经改变之后,事件`wx.EVT_TREE_SEL_CHANGED`被触发。这两个事件的类是`wx.TreeEvent`,这将在15.8节中作更完整的讨论。 ## 控制项目的可见性 在树控件中有两种机制可以让你用编程的方式控制某项目的可见性。你可以使用方法`Collapse(item)`和`Expand(item)`指定给定的树项目是展开的或折叠的。这些方法改变树控件的显示,并且如果对一个没有子项的项目调用该方法将不起作用。这儿还有一个方便的函数:`CollapseAndReset(item)`,该方法折叠指定的项,并删除指定项的所有孩子。另外,方法`Toggle(item)`用于切换项目的展开和折叠状态。你可以使用方法`IsExpanded(item)`来查询项目的当前展开状态。 展开或折叠一个树项目触发两事件。在展开或折叠前,事件`wx.EVT_TREE_ITEM_COLLAPSING `或`wx.EVT_TREE_ITEM_EXPANDING`被触发。在你的处理方法中,你可以使用事件的`Veto()`方法来阻止展开或折叠。在展开或折叠发生后,事件`EVT_TREE_ITEM_COLLAPSED`或`wx.EVT_TREE_ITEM_EXPANDED`被触发。这四个事件都是类`wx.TreeEvent`的事件类型。 **虚树** 展开和折叠项目的一个令人感兴趣的的用法是创建一个虚树,新项目仅当父项展开时添加。例15.3显示这样一个样列。 **例15.3** **展开时动态添加新的项目** ``` #-*- encoding:UTF-8 -*- import wx import data class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="virtual tree with icons", size=(400,500)) il = wx.ImageList(16,16) self.fldridx = il.Add( wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, (16,16))) self.fldropenidx = il.Add( wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, (16,16))) self.fileidx = il.Add( wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, (16,16))) self.tree = wx.TreeCtrl(self) self.tree.AssignImageList(il) root = self.tree.AddRoot("wx.Object") self.tree.SetItemImage(root, self.fldridx, wx.TreeItemIcon_Normal) self.tree.SetItemImage(root, self.fldropenidx, wx.TreeItemIcon_Expanded) # Instead of adding nodes for the whole tree, just attach some # data to the root node so that it can find and add its child # nodes when it is expanded, and mark it as having children so # it will be expandable. self.tree.SetItemPyData(root, data.tree)#创建一个根 self.tree.SetItemHasChildren(root, True) # Bind some interesting events # 绑定事件 self.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnItemExpanded, self.tree) self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnItemCollapsed, self.tree) self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged, self.tree) self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnActivated, self.tree) self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.OnItemExpanding, self.tree) self.tree.Expand(root) def AddTreeNodes(self, parentItem):#给父项目添加结节 """ Add nodes for just the children of the parentItem """ items = self.tree.GetItemPyData(parentItem) for item in items: if type(item) == str: # a leaf node newItem = self.tree.AppendItem(parentItem, item) self.tree.SetItemImage(newItem, self.fileidx, wx.TreeItemIcon_Normal) else: # this item has children newItem = self.tree.AppendItem(parentItem, item[0]) self.tree.SetItemImage(newItem, self.fldridx, wx.TreeItemIcon_Normal) self.tree.SetItemImage(newItem, self.fldropenidx, wx.TreeItemIcon_Expanded) self.tree.SetItemPyData(newItem, item[1]) self.tree.SetItemHasChildren(newItem, True) def GetItemText(self, item): if item: return self.tree.GetItemText(item) else: return "" def OnItemExpanded(self, evt): print "OnItemExpanded: ", self.GetItemText(evt.GetItem()) def OnItemExpanding(self, evt):#当展开时创建结点 # When the item is about to be expanded add the first level of child nodes print "OnItemExpanding:", self.GetItemText(evt.GetItem()) self.AddTreeNodes(evt.GetItem()) def OnItemCollapsed(self, evt): print "OnItemCollapsed:", self.GetItemText(evt.GetItem()) # And remove them when collapsed as we don't need them any longer self.tree.DeleteChildren(evt.GetItem())#折叠时删除结点 def OnSelChanged(self, evt): print "OnSelChanged: ", self.GetItemText(evt.GetItem()) def OnActivated(self, evt): print "OnActivated: ", self.GetItemText(evt.GetItem()) app = wx.PySimpleApp(redirect=True) frame = TestFrame() frame.Show() app.MainLoop() ``` 这个机制可以被扩展来从外部源读取数据以查看。这个机制除了可以被用来建造一个文件树外,我们会提及数据库中的数据的可能性,以便对文件的结构不感趣的你不用全面研究文件结构。 **控制可见性** 有大量的方法使你能够管理那些项目是可见的。一个对象的不可见,可能是因为它没处在含有滚动条的框中的可见区域或是因为它处在折叠项中。你可以使用`IsVisible(item)`方法来确定项目是否可见,该方法在项目是可见时返回`True`,不可见返回`False`。你可以通过使用方法`EnsureVisible(item)`迫使指定的项变成可见的。如果需要的话,该方法将通过展开该项的父项(以及父项的父项,以此类推)来迫使指定项变成可见的,然后滚动该树,以使指定项处于控件的可见部分。如果你只需要滚动,可以使用`ScrollTo(item)`方法来完成。 遍历树中的可见部分,首先要使用的方法是`GetFirstVisibleItem()`。该方法返回显示的可见部分中的最顶端的项目的`wx.TreeItemId`。然后通过使用`GetNextVisible(item)`方法来遍历,该方法的`item`参数来自`GetFirstVisibleItem()`和`GetNextVisible()`的返回值。如果移动的方向向上的话,使用方法`GetPreviousVisible(item)`。如果参数`item`不可见的话,返回值是一个无效的项。 还有几个别的方法可用于项目的显示。树控件有一个属性,该属性用于设置缩进。可以通过方法`GetIndent()`和方法`SetIndent(indent)`来得到和设置该属性的当前值。其中`indent`参数是缩进的像素值(整数)。 要得到关于指定点的树项目的信息,使用方法`HitTest(point)`,其中`point`是树控件中的相关位置的一个`wx.Point`。方法的返回值是一个(`item, flags)`元组,其中的`item`是相关位置的项的`wx.TreeItemId`或`None`值。如果指定位置没有项目,那么一个无效的项目将返回。`flags`部分是一个位掩码,它给出了相关的信息。表15.5包含了`flags`的一个完整列表。 还有两个方法,它们让你可以处理屏幕上项目的实际的边界。方法`GetBoundingRect(item, textOnly=False)`返回一个`wx.Rect`实例,该实例对应于屏幕上文本项的矩形边界区域。其中参数`item`是项目的`wx.TreeItemId`。如果参数`textOnly`为`True`,那么该矩形仅包括项目的显示文本所覆盖的区域。如果为`False`,那么该矩形也包括图像区域。在这两种情况中,矩形都包括从树控件的边缘到内嵌的显示项间的空白区域。如果`item`代表的项目当前是不可见的,那么两种方法都返回`None`。 **表15.5** | | | | --- | --- | | `wx.TREE_HITTEST_ABOVE` | 该位置在树的客户区的上面,不是任何项目的一部分。 | | `wx.TREE_HITTEST_BELOW` | 该位置在树的客户区的下面,不是任何项目的一部分。 | | `wx.TREE_HITTEST_NOWhere` | 该位置处在树的客户区中,不是任何项目的一部分。 | | `wx.TREE_HITTEST_ONITEMBUTTON` | 该位置位于展开/折叠图标按钮上,是项目的一部分。 | | `wx.TREE_HITTEST_ONITEMICON` | 该位置位于项目的图像部分上。 | | `wx.TREE_HITTEST_ONITEMINDENT` | 该位置位于项目的显示文本的左边缩进区域中。 | | `wx.TREE_HITTEST_ONITEMLABEL` | 该位置位于项目的显示文本中。 | | `wx.TREE_HITTEST_ONITEMRIGHT` | 该位置是项目的显示文本的右边。 | | `wx.TREE_HITTEST_ONITEMSTATEICON` | 该位置是在项目的状态图标中。 | | `wx.TREE_HITTEST_TOLEFT` | 该位置在树的客户区的左面,不是任何项目的一部分。 | | `wx.TREE_HITTEST_TORIGHT` | 该位置在树的客户区的右面,不是任何项目的一部分。 | ## 使树控件可编辑 树控件可以被设置为允许用户编辑树项目的显示文本。这通过在创建树控件时使用样式标记`wx.TR_EDIT_LABELS`来实现。使用了该样式标记后,树控件的行为就类似于可编辑的列表控件了。编辑一个树项目会给出一个文本控件来让用户编辑文本。按下`esc`则取消编辑。按下回车键或在文本控件外敲击将确认编辑。 你可以在程序中使用`EditLabel(item)`方法来启动对特定项的编辑。参数`item`是你想要编辑的项的`wx.TreeItemId`。要终止编辑,可以使用方法`EndEditLabel(cancelEdit)`。由于一次只能有一个活动编辑项,所以这里不需要指定项目的`ID`。参数`cancelEdit`是一个布尔值。如果为`True`,取消编辑,如果为`False`,则不取消。如果因为某种原因,你需要访问当前所用的文本编辑控件,你可以调用方法`GetEditControl()`,该方法返回用于当前编辑的`wx.TextCtrl`实例,如果当前没有编辑,则返回`None`。当前该方法只工作于`Windows`系统下。 当一个编辑会话开始时(通过用户选择或调用`EditLabel()`方法),`wx.EVT_TREE_BEGIN_LABEL_EDIT`类型的`wx.TreeEvent`事件被触发。如果使用`Veto()`方法否决了该事件的话,那么编辑将不会开始。当会话结束时(通过用户的敲击或调用`EndEditLabel()`方法),一个`wx.EVT_TREE_END_LABEL_EDIT`类型的事件被触发。这个事件也可以被否决,这样的话,编辑就被取消了,项目不会被改变。 ## 响应树控件的其它的用户事件 在这一节,我们将讨论`wx.TreeEvent`类的属性。表15.6列出了这些属性。 **表15.6** **`wx.TreeEvent`的属性** | | | | --- | --- | | `GetKeyCode()` | 返回所按键的整数按键码。只对`wx.EVT_TREE_KEY_DOWN`事件类型有效。如果任一修饰键(`CTRL,SHIFT,and ALT`之类的)也被按下,该属性不会告知你。 | | `GetItem()` | 返回与事件相关的项的`wx.TreeItemId`。 | | `GetKeyEvent()` | 只对`wx.EVT_TREE_KEY_DOWN`事件有效。返回`wx.KeyEvent`。该事件可以被用来告知你在该事件期间,是否有修饰键被按下。 | | `GetLabel()` | 返回项目的当前文本标签。只对`wx.EVT_TREE_BEGIN_LABEL_EDIT`和`wx.EVT_TREE_END_LABEL_EDIT`有效。 | | `GetPoint()` | 返回与该事件相关的鼠标位置的一个`wx.Point`。只对拖动事件有效。 | | `IsEditCancelled()` | 只对`wx.EVT_TREE_END_LABEL_EDIT`有效。如果用户通过取消来结束当前的编辑则返回`True`,否则返回`False`。 | | `SetToolTip(tooltip)` | 只对`wx.EVT_TREE_ITEM_GETTOOLTIP`事件有效。这使你能够得到关于项目的提示。该属性只作在`Windows`系统上。 | 表15.7列出了几个不适合在表15.6中列出的`wx.TreeEvent`的事件类型,它们有时也是用的。 **表15.7** **树控件的另外的几个事件** | | | | --- | --- | | `wx.EVT_TREE_BEGIN_DRAG` | 当用户通过按下鼠标左键来拖动树中的一个项目时,触发该事件。要让拖动时能够做些事情,该事件的处理函数必须显式地调用事件的`Allow()`方法。 | | `wx.EVT_TREE_BEGIN_RDRAG` | 当用户通过按下鼠标右键来拖动树中的一个项目时,触发该事件。要让拖动时能够做些事情,该事件的处理函数必须显式地调用事件的`Allow()`方法。 | | `wx.EVT_TREE_ITEM_ACTIVATED` | 当一个项目通过双击被激活时,触发该事件。 | | `wx.EVT_TREE_ITEM_GETTOOLTIP` | 当鼠标停留在树中的一个项目上时,触发该事件。该事件可用来为项目设置特定的提示。只需要在事件对像中简单地设置标签参数,其它的将由系统来完成。 | | `wx.EVT_TREE_KEY_DOWN` | 在树控件获得焦点的情况下,当一个键被按下时触发该事件。 | 上面这些就你需要了解的有关树控件的属性。下面,我们将通过一个有用的另一种形式的树控件来结束本章。 ## 使用树列表控件 除了`wx.TreeCtrl`,`wxPython`也提供了`wx.gizmos.TreeListCtrl`,它是树控件和报告模式的列表控件的组合。除了本章中所讨论的`wx.TreeCtrl`的特性外,`TreeListCtrl`能够显示与每行相关的数据的附加列。图15.3显示了树列表控件的样子。 **图15.3** ![](https://box.kancloud.cn/2016-08-21_57b996479f1cf.gif) 例15.4是产生上图的代码 **例15.4** **使用树列表控件** ``` import wx import wx.gizmos import data class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="TreeListCtrl", size=(400,500)) # Create an image list il = wx.ImageList(16,16) # Get some standard images from the art provider and add them # to the image list self.fldridx = il.Add( wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, (16,16))) self.fldropenidx = il.Add( wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, (16,16))) self.fileidx = il.Add( wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, (16,16))) # Create the tree # 创建树列表控件 self.tree = wx.gizmos.TreeListCtrl(self, style = wx.TR_DEFAULT_STYLE | wx.TR_FULL_ROW_HIGHLIGHT) # Give it the image list self.tree.AssignImageList(il) # create some columns #创建一些列 self.tree.AddColumn("Class Name") self.tree.AddColumn("Description") self.tree.SetMainColumn(0) # the one with the tree in it... self.tree.SetColumnWidth(0, 200) self.tree.SetColumnWidth(1, 200) # Add a root node and assign it some images root = self.tree.AddRoot("wx.Object") self.tree.SetItemText(root, "A description of wx.Object", 1)#给列添加文本 self.tree.SetItemImage(root, self.fldridx, wx.TreeItemIcon_Normal) self.tree.SetItemImage(root, self.fldropenidx, wx.TreeItemIcon_Expanded) # Add nodes from our data set self.AddTreeNodes(root, data.tree) # Bind some interesting events self.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnItemExpanded, self.tree) self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnItemCollapsed, self.tree) self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged, self.tree) self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnActivated, self.tree) # Expand the first level self.tree.Expand(root) def AddTreeNodes(self, parentItem, items): """ Recursively traverses the data structure, adding tree nodes to match it. """ for item in items: if type(item) == str: newItem = self.tree.AppendItem(parentItem, item) self.tree.SetItemText(newItem, "A description of %s" % item, 1)#给列添加文本 self.tree.SetItemImage(newItem, self.fileidx, wx.TreeItemIcon_Normal) else: newItem = self.tree.AppendItem(parentItem, item[0]) self.tree.SetItemText(newItem, "A description of %s" % item[0], 1) self.tree.SetItemImage(newItem, self.fldridx, wx.TreeItemIcon_Normal) self.tree.SetItemImage(newItem, self.fldropenidx, wx.TreeItemIcon_Expanded) self.AddTreeNodes(newItem, item[1]) def GetItemText(self, item): if item: return self.tree.GetItemText(item) else: return "" def OnItemExpanded(self, evt): print "OnItemExpanded: ", self.GetItemText(evt.GetItem()) def OnItemCollapsed(self, evt): print "OnItemCollapsed:", self.GetItemText(evt.GetItem()) def OnSelChanged(self, evt): print "OnSelChanged: ", self.GetItemText(evt.GetItem()) def OnActivated(self, evt): print "OnActivated: ", self.GetItemText(evt.GetItem()) app = wx.PySimpleApp(redirect=True) frame = TestFrame() frame.Show() app.MainLoop() ``` 树列表控件的很多方法和列表控件的相似,所以我们就再也没有列出并说明了。 ## 本章小结 1、树控件提供给你了一个如文件树或`XML`文档样的嵌套紧凑的外观。树控件是类`wx.TreeCtrl`的实例。有时,人你会想子类化`wx.TreeCtrl`,尤其是如果你需要实现自定义的排序的时候。 2、要向树中添加项目,首先要用方法`AddRoot(text, image=`-1, `selImage=`-1, `data=None)`。该函数的返回值是一个代表树的`root`项目的`wx.TreeItemId`。树控件使用`wx.TreeItemId`作为它自己的标识符类型,而非和其它控件一样使用整数的`ID`。一旦你得到了`root`项,你就可以使用方法`AppendItem(parent, text, image=`-1, `selImage=`-1, `data=None)`来添加子项目,参数`parent`是父项目的`ID`。该方法返回新项目的`wx.TreeItemId`。另外还有一些用来将新的项目添加在不同位置的方法。方法`Delete(item)`从树中移除一个项目,方法`DeleteChildren(item)`移除指定项的所有子项目。 3、树控件有一些用来改变树的显示外观的样式。一套是用来控制展开或折叠项目的按钮类型的。另一套是用来控制项目间的连接线的。第三套是用于控制树是单选还是多选的。你也可以使用样式通过隐藏树的实际的`root`,来模拟一个有着多个`root`项的树。 4、默认情况下,树的项目通常是按照显示文本的字母顺序排序的。但是要使这能够实现,你必须给每项分配数据。实现这个的最容易的方法是使用`SetItemPyData(item, obj)`,它给项目分配一个任意的`Python`对象。如果你想使用该数据去写一个自定义的排序函数,你必须继承`wx.TreeCtrl`类并覆盖方法`OnCompareItems(item1, item2)`,其中的参数`item1`和`item2`是要比较的项的`ID`。 5、树控件使用一个图像列表来管理图像,类似于列表控件管理图像的方法。你可以使用`SetImageList(imageList)`或`AssignImageList(imageList)`来分配一个图像列表给树控件。然后,当新的项目被添加到列表时,你可以将它们与图像列表中的特定的索引联系起来。 6、没有特定的函数可以让你得到一个父项的子项目列表。替而代之的是,你需要去遍历子项目列表,这通过使用方法`GetFirstChild(item)`作为开始。 7、你可以使用方法`SelectItem(item, select=True)`来管理树的项目的选择。在一个多选树中,你可以使用`ToggleItemSelection(item)`来改变给定项的状态。你可以使用`IsSelected(item)`来查询一个项目的状态。你也可以使用`Expand(item)`或`Collapse(item)`展开或折叠一个项,或使用`Toggle(item)`来切换它的状态。 8、样式`wx.TR_EDIT_LABELS`使得树控件可为用户所编辑。一个可编辑的列表中,用户可以选择一个项,并键入一个新的标签。按下`esc`来取消编辑而不对项目作任何改变。你也可以通过 `wx.EVT_TREE_END_LABEL_EDIT `事件来否决编辑。类`wx.TreeEvent`提供了允许访问当前被处理的项目的显示文本的属性。