简体   繁体   English

Python tkinter.ttk:如何禁用 Treeview

[英]Python tkinter.ttk: how to disable Treeview

Disabling a widget such as ttk.Button:禁用小部件,例如 ttk.Button:

button = ttk.Button(frame, text='Quit', command=self.quit)
button.state(('disabled',))

renders it unresponsive to user actions such as clicking.使其对用户操作(例如单击)无响应。 However, disabling ttk.Treeview:但是,禁用 ttk.Treeview:

tree = ttk.Treeview(frame,column=['one','two'])
tree.state(('disabled',))

only serves to change its styling - it is still responsive to user actions such as column resizing, selecting and scrolling.仅用于更改其样式 - 它仍然响应用户操作,例如调整列大小、选择和滚动。 Is there a way to make ttk.Treeview stop responding to user actions like ttk.Button?有没有办法让 ttk.Treeview 停止响应像 ttk.Button 这样的用户操作? Neither Google nor the documentation seem to have the answer.谷歌和文档似乎都没有答案。

Thanks to the sourceforge link above kindly provided by fhdrsdg, it appears that the only way in tkinter to render a widget unresponsive to any user action is to delete the binding tags list (tuple, actually) for that widget.感谢上面由 fhdrsdg 提供的 sourceforge 链接,似乎在 tkinter 中呈现小部件对任何用户操作都没有响应的唯一方法是删除该小部件的绑定标签列表(实际上是元组)。

The binding tags determine the order in which events for that widget are processed (see https://www.tcl.tk/man/tcl8.4/TkCmd/bindtags.htm for more detaisl) - by default, except for top-level windows a widget has four tags: the name of the widget itself, the widget's class name, the toplevel window (signified by '.' - apparently a relic from tcl days) and the special keyword 'all' (the significance of which is still a mystery to me), for example:绑定标签确定处理该小部件的事件的顺序(有关更多详细信息,请参见https://www.tcl.tk/man/tcl8.4/TkCmd/bindtags.htm ) - 默认情况下,顶级除外windows 一个小部件有四个标签:小部件本身的名称、小部件的类名、顶层窗口(由 '.' 表示——显然是 tcl 时代的遗物)和特殊关键字“all”(其意义仍然存在)对我来说是个谜),例如:

('.!frame.!mytreeview', 'Treeview', '.', 'all')

When an event occurs, the tags are searched from left to right for a sequence which matches the event - if one is found, the bound callback is invoked and, unless it returns a 'break' to abort the process, the search continues along the chain - from instance bindings, to class binding, to top-level window bindings, to 'all' (whatever that is).当事件发生时,标签从左到右搜索匹配事件的序列 - 如果找到,则调用绑定回调,除非它返回“中断”以中止过程,否则搜索将继续沿链 - 从实例绑定到类绑定,再到顶级窗口绑定,再到“所有”(无论是什么)。

This is the default, out-of-the-box tkinter behaviour.这是默认的、开箱即用的 tkinter 行为。 However, there is nothing to stop us from changing the binding tags for a widget, thereby changing how events for that widget are processed.但是,没有什么可以阻止我们更改小部件的绑定标签,从而更改该小部件的事件处理方式。 For example, deleting all tags will prevent events from being handled at all, rendering the widget completely unresponsive.例如,删除所有标签将完全阻止处理事件,从而使小部件完全无响应。

Binding tags can be manipulated with the bindtags widget method:绑定标签可以使用 bindtags 小部件方法进行操作:

bindtags(self, tagList=None)
    Set or get the list of bindtags for this widget.

    With no argument return the list of all bindtags associated with
    this widget. With a list of strings as argument the bindtags are
    set to this list. The bindtags determine in which order events are
    processed (see bind).

For a thorough / no-stones-left-unturned treatment of the topic see Stephen Lidie and Nancy Walsh (2002) "Mastering Perl/Tk: Graphical User Interfaces in Perl" O'Reilly, p.372 et seq.有关该主题的彻底/不遗余力的处理,请参阅 Stephen Lidie 和 Nancy Walsh(2002 年)“掌握 Perl/Tk:Perl 中的图形用户界面”O'Reilly,第 372 页等。 They actually suggest deleting a widget's bindtags to render it 'inert' (as they call it) on p.373.他们实际上建议在第 373 页删除小部件的绑定标签以使其“惰性”(正如他们所说的那样)。

The following implementation encapsulates this technique in a mix-in class called DisableMixin which extends ttk widgets' state() method and provides 4 simple utility methods to check and set the widget's enabled state:以下实现将此技术封装在名为 DisableMixin 的混合类中,该类扩展了 ttk 小部件的 state() 方法并提供了 4 个简单的实用程序方法来检查和设置小部件的启用状态:

import tkinter as tk
import tkinter.ttk as ttk


class DisableMixin(object):

    def state(self,statespec=None):
        if statespec:
            e = super().state(statespec)
            if 'disabled' in e:
                self.bindtags(self.tags)
            elif '!disabled' in e:
                self.tags = self.bindtags()
                self.bindtags([None])
            return e
        else:
            return super().state()

    def disable(self):
        self.state(('disabled',))

    def enable(self):
        self.state(('!disabled',))

    def is_disabled(self):
        return 'disabled' in self.state()

    def is_enabled(self):
        return not self.is_disabled()

The state() method checks whether the widget is being enabled or disabled, and manipulates the binding tags accordingly - saving the tags in an instance variable before deleting them if the widget is being disabled, and loading them back when re-enabled. state() 方法检查小部件是被启用还是被禁用,并相应地操作绑定标签 - 如果小部件被禁用,则在删除它们之前将标签保存在实例变量中,并在重新启用时加载它们。 This mix-in will only work with ttk widgets (tk widgets do not have a state() method), and must be listed as the leftmost parent class (ie first superclass in the MRO), for example:此混合仅适用于 ttk 小部件(tk 小部件没有 state() 方法),并且必须列为最左侧的父类(即 MRO 中的第一个超类),例如:

class myTreeview(DisableMixin, ttk.Treeview): pass

And finally here's a small demo program:最后是一个小演示程序:

import tkinter as tk
import tkinter.ttk as ttk


class DisableMixin(object):

    def state(self,statespec=None):
        if statespec:
            e = super().state(statespec)
            if 'disabled' in e:
                self.bindtags(self.tags)
            elif '!disabled' in e:
                self.tags = self.bindtags()
                self.bindtags(['xxx'])
            return e
        else:
            return super().state()

    def disable(self):
        self.state(('disabled',))

    def enable(self):
        self.state(('!disabled',))

    def is_disabled(self):
        return 'disabled' in self.state()

    def is_enabled(self):
        return not self.is_disabled()


class myTreeview(DisableMixin, ttk.Treeview): pass


class myApp(tk.Tk):

    def __init__(self, *args,**kwargs):
        super().__init__(*args,**kwargs)
        self.tree = None
        self.setup_widgets()
        self.load_data()

    def setup_widgets(self):
        self.columnconfigure(0,weight=1)
        self.rowconfigure(0,weight=0)
        self.rowconfigure(1,weight=1)

        self.btn = ttk.Button(self,text='CLICK TO DISABLE', command=self.btnclicked)
        self.btn.grid(row=0,column=0,sticky='we')

        frame = ttk.Frame(self)
        frame.grid(row=1,column=0,sticky='nsew')

        self.tree = myTreeview(frame, columns=emp_header, show="headings")
        vsb = ttk.Scrollbar(frame,orient="vertical",command=self.tree.yview)
        self.tree.configure(yscrollcommand=vsb.set)

        self.tree.grid(row=0, column=0, sticky='nsew')
        vsb.grid(row=0, column=1, sticky='ns')

        frame.grid_columnconfigure(0, weight=1)
        frame.grid_columnconfigure(1, weight=0)
        frame.grid_rowconfigure(0, weight=1)


    def btnclicked(self):

        if self.tree.is_enabled():
            self.tree.disable()
            self.btn['text'] = 'CLICK TO ENABLE'
        else:
            self.tree.enable()
            self.btn['text'] = 'CLICK TO DISABLE'


    def load_data(self):
        for col in emp_header:
            self.tree.heading(col, text=col)

        for emp in emp_list:
            self.tree.insert('', tk.END, values=emp)


# --- test data
emp_header = ['Employee', 'Based','Salary']
emp_list = [
('Justin Case', 'London', 80000) ,
('Jerry Khan', 'Aberdeen', 67000) ,
('Jordie Banks', 'Cardiff', 42000) ,
('Angel Falls', 'Manchester', 65000) ,
('Judas Priest', 'Canterbury', 96000) ,
('Pearl Harper', 'Scarborough', 43000) ,
('Julian Date', 'York', 54000) ,
('Perry Winkle', 'Belfast', 78000) ,
('Kate Canaveral', 'Liverpool', 49000) ,
('Bill Lading', 'Bath', 69000) ,
]

app = myApp()
app.mainloop()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM