如何在Python中创建带复选框的树视图

20 投票
4 回答
30716 浏览
提问于 2025-04-16 12:27

我一直在用Tkinter和Tix写一个小程序。现在我需要一个带复选框的树形视图,这样我就可以从树形视图中选择项目。请问有没有简单的方法可以做到这一点?我看过ttk.Treeview(),感觉用它来创建树形视图很简单,但有没有办法在视图中插入复选框呢?

如果能给我一个简单的代码示例,那就太好了。

我不局限于ttk,任何方法都可以;只要有个例子或者好的文档,我就能搞定。

4 个回答

9

如果你可以使用 Tix,那就按照 @Brandon 的方法来做。如果你只能用 Ttk(就像我一样),这里有一个基于 @j_4231 想法的解决方案。我们可以用 Unicode 提供的两个字符来代替用图片表示复选框:

  • '投票框' (U+2610) :
  • '带X的投票框' (U+2612) : .

这些字符会放在项目名称后面,用来检查当前的状态:treeview.item(iid, "text")[-1] 要么是 ,要么是 。当点击文本时,我们可以更新项目名称。

TtkCheckList 继承自 ttk.Treeview,所以可以使用 Treeview 的常规参数和方法。

import tkinter as tk
from tkinter import ttk

BALLOT_BOX = "\u2610"
BALLOT_BOX_WITH_X = "\u2612"


class TtkCheckList(ttk.Treeview):
    def __init__(self, master=None, width=200, clicked=None, separator='.',
                 unchecked=BALLOT_BOX, checked=BALLOT_BOX_WITH_X, **kwargs):
        """
        :param width: the width of the check list
        :param clicked: the optional function if a checkbox is clicked. Takes a
                        `iid` parameter.
        :param separator: the item separator (default is `'.'`)
        :param unchecked: the character for an unchecked box (default is
                          "\u2610")
        :param unchecked: the character for a checked box (default is "\u2612")

        Other parameters are passed to the `TreeView`.
        """
        if "selectmode" not in kwargs:
            kwargs["selectmode"] = "none"
        if "show" not in kwargs:
            kwargs["show"] = "tree"
        ttk.Treeview.__init__(self, master, **kwargs)
        self._separator = separator
        self._unchecked = unchecked
        self._checked = checked
        self._clicked = self.toggle if clicked is None else clicked

        self.column('#0', width=width, stretch=tk.YES)
        self.bind("<Button-1>", self._item_click, True)

    def _item_click(self, event):
        assert event.widget == self
        x, y = event.x, event.y
        element = self.identify("element", x, y)
        if element == "text":
            iid = self.identify_row(y)
            self._clicked(iid)

    def add_item(self, item):
        """
        Add an item to the checklist. The item is the list of nodes separated
        by dots: `Item.SubItem.SubSubItem`. **This item is used as `iid`  at
        the underlying `Treeview` level.**
        """
        try:
            parent_iid, text = item.rsplit(self._separator, maxsplit=1)
        except ValueError:
            parent_iid, text = "", item
        self.insert(parent_iid, index='end', iid=item,
                    text=text+" "+self._unchecked, open=True)

    def toggle(self, iid):
        """
        Toggle the checkbox `iid`
        """
        text = self.item(iid, "text")
        checked = text[-1] == self._checked
        status = self._unchecked if checked else self._checked
        self.item(iid, text=text[:-1] + status)

    def checked(self, iid):
        """
        Return True if checkbox `iid` is checked
        """
        text = self.item(iid, "text")
        return text[-1] == self._checked

    def check(self, iid):
        """
        Check the checkbox `iid`
        """
        text = self.item(iid, "text")
        if text[-1] == self._unchecked:
            self.item(iid, text=text[:-1] + self._checked)

    def uncheck(self, iid):
        """
        Uncheck the checkbox `iid`
        """
        text = self.item(iid, "text")
        if text[-1] == self._checked:
            self.item(iid, text=text[:-1] + self._unchecked)

这里有一个例子:

items = [
    'Item',
    'Item.SubItem1',
    'Item.SubItem2',
    'Item.SubItem2.SubSubItem1',
    'Item.SubItem2.SubSubItem2',
    'Item.SubItem2.SubSubItem3',
    'Item.SubItem3',
    'Item.SubItem3.SubSubItem1',
    'Item.SubItem4'
]

root = tk.Tk()
root.title('Test')
root.geometry('400x300')

check_list = TtkCheckList(root, height=len(items))

for item in items:
    check_list.add_item(item)
check_list.pack()

root.mainloop()

你可以使用 clicked 参数来定义点击项目时的新行为。例如:

def obey_ancestor(iid):
    """
    If the status of an item is toggled, the status of all its descendants
    is also set to the new status.
    """
    set_status = check_list.uncheck if check_list.checked(iid) else check_list.check
    stack = [iid]
    while stack:
        iid = stack.pop()
        set_status(iid)
        stack.extend(check_list.get_children(iid))

还有:

check_list = TtkCheckList(root, height=len(items),
                      clicked=obey_ancestor)
11

我做了一个树形视图类,里面有复选框,这个类是从ttk.Treeview继承来的。不过,这里的复选框不是ttk.Checkbutton,而是用已选中、未选中和三态复选框的图片来表示的。

import tkinter as tk
import tkinter.ttk as ttk

class CheckboxTreeview(ttk.Treeview):
    """
        Treeview widget with checkboxes left of each item.
        The checkboxes are done via the image attribute of the item, so to keep
        the checkbox, you cannot add an image to the item.
    """

    def __init__(self, master=None, **kw):
        ttk.Treeview.__init__(self, master, **kw)
        # checkboxes are implemented with pictures
        self.im_checked = tk.PhotoImage(file='checked.png')
        self.im_unchecked = tk.PhotoImage(file='unchecked.png')
        self.im_tristate = tk.PhotoImage(file='tristate.png')
        self.tag_configure("unchecked", image=self.im_unchecked)
        self.tag_configure("tristate", image=self.im_tristate)
        self.tag_configure("checked", image=self.im_checked)
        # check / uncheck boxes on click
        self.bind("<Button-1>", self.box_click, True)

    def insert(self, parent, index, iid=None, **kw):
        """ same method as for standard treeview but add the tag 'unchecked'
            automatically if no tag among ('checked', 'unchecked', 'tristate')
            is given """
        if not "tags" in kw:
            kw["tags"] = ("unchecked",)
        elif not ("unchecked" in kw["tags"] or "checked" in kw["tags"]
                  or "tristate" in kw["tags"]):
            kw["tags"] = ("unchecked",)
        ttk.Treeview.insert(self, parent, index, iid, **kw)

    def check_descendant(self, item):
        """ check the boxes of item's descendants """
        children = self.get_children(item)
        for iid in children:
            self.item(iid, tags=("checked",))
            self.check_descendant(iid)

    def check_ancestor(self, item):
        """ check the box of item and change the state of the boxes of item's
            ancestors accordingly """
        self.item(item, tags=("checked",))
        parent = self.parent(item)
        if parent:
            children = self.get_children(parent)
            b = ["checked" in self.item(c, "tags") for c in children]
            if False in b:
                # at least one box is not checked and item's box is checked
                self.tristate_parent(parent)
            else:
                # all boxes of the children are checked
                self.check_ancestor(parent)

    def tristate_parent(self, item):
        """ put the box of item in tristate and change the state of the boxes of
            item's ancestors accordingly """
        self.item(item, tags=("tristate",))
        parent = self.parent(item)
        if parent:
            self.tristate_parent(parent)

    def uncheck_descendant(self, item):
        """ uncheck the boxes of item's descendant """
        children = self.get_children(item)
        for iid in children:
            self.item(iid, tags=("unchecked",))
            self.uncheck_descendant(iid)

    def uncheck_ancestor(self, item):
        """ uncheck the box of item and change the state of the boxes of item's
            ancestors accordingly """
        self.item(item, tags=("unchecked",))
        parent = self.parent(item)
        if parent:
            children = self.get_children(parent)
            b = ["unchecked" in self.item(c, "tags") for c in children]
            if False in b:
                # at least one box is checked and item's box is unchecked
                self.tristate_parent(parent)
            else:
                # no box is checked
                self.uncheck_ancestor(parent)

    def box_click(self, event):
        """ check or uncheck box when clicked """
        x, y, widget = event.x, event.y, event.widget
        elem = widget.identify("element", x, y)
        if "image" in elem:
            # a box was clicked
            item = self.identify_row(y)
            tags = self.item(item, "tags")
            if ("unchecked" in tags) or ("tristate" in tags):
                self.check_ancestor(item)
                self.check_descendant(item)
            else:
                self.uncheck_descendant(item)
                self.uncheck_ancestor(item)



if __name__ == '__main__':
    root = tk.Tk()
    t = CheckboxTreeview(root, show="tree")
    t.pack()
    t.insert("", 0, "1", text="1")
    t.insert("1", "end", "11", text="1")
    t.insert("1", "end", "12", text="2")
    t.insert("12", "end", "121", text="1")
    t.insert("12", "end", "122", text="2")
    t.insert("122", "end", "1221", text="1")
    t.insert("1", "end", "13", text="3")
    t.insert("13", "end", "131", text="1")
    root.mainloop()

ttkwidgets模块中,有一个改进版的CheckboxTreeview可以使用。

22

这是一张图片,可能是用来展示某个技术概念或问题的。具体内容需要根据图片的内容来理解。

import Tix

class View(object):
    def __init__(self, root):
        self.root = root
        self.makeCheckList()

    def makeCheckList(self):
        self.cl = Tix.CheckList(self.root, browsecmd=self.selectItem)
        self.cl.pack()
        self.cl.hlist.add("CL1", text="checklist1")
        self.cl.hlist.add("CL1.Item1", text="subitem1")
        self.cl.hlist.add("CL2", text="checklist2")
        self.cl.hlist.add("CL2.Item1", text="subitem1")
        self.cl.setstatus("CL2", "on")
        self.cl.setstatus("CL2.Item1", "on")
        self.cl.setstatus("CL1", "off")
        self.cl.setstatus("CL1.Item1", "off")
        self.cl.autosetmode()

    def selectItem(self, item):
        print item, self.cl.getstatus(item)

def main():
    root = Tix.Tk()
    view = View(root)
    root.update()
    root.mainloop()

if __name__ == '__main__':
    main()

撰写回答