简体   繁体   中英

Python tkinter.ttk: how to disable Treeview

Disabling a widget such as 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:

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? 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.

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:

('.!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. 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(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. They actually suggest deleting a widget's bindtags to render it 'inert' (as they call it) on p.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:

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. 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:

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()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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