Форум сайта python.su
Доброго дня!
Я - очередной новичок, изучающий Питон, поэтому вопросы могут быть несколько.. простыми для вас. Это на всякий случай, чтобы не удивлялись.
К сути. Я пишу интерфейс на Tk под Python 3.5.1. В моём коде есть инструкция, которая должна встраивать в виджет Text кучку виджетов Checkbutton. Делается это с помощью команды вида “self.window_create('end', window=cb)”. Так вот при попытке её выполнения вылетает эксепшен:
_tkinter.TclError: can't embed .19551680 in .19474248.19511280.19512568
can't read "::tcl_pkgPath": no such variable while executing "foreach Dir $::tcl_pkgPath { if {$Dir ni $::auto_path} { lappend ::auto_path $Dir } }"
from tkinter import * class Tagslist(Frame): def __init__(self, parent=None, **options): Frame.__init__(self, master=parent) self.textbox = Text(self, **options) scroller = Scrollbar(self) scroller.config(command=self.textbox.yview) self.textbox.config(yscrollcommand=scroller.set) self.states_dict = {1: [1, 'tag1'], 2: [0, 'tag2'], 3: [1, 'tag3']} for key in self.states_dict: state = self.states_dict[key][0] self.states_dict[key][0] = IntVar() cb = Checkbutton(text=self.states_dict[key][1], variable=self.states_dict[key][0]) try: self.textbox.window_create('end', window=cb) # Вот тут оно и падает. except TclError as err: print(err) tcl = Tcl() errorInfo = tcl.eval("set ::errorInfo") # Кстати, это работает на Windows, но на Linux вызывает ещё одно исключение. print(errorInfo) self.textbox.insert('end', '\n') self.states_dict[key][0].set(state) scroller.grid(row=0, column=1, sticky='sn') self.textbox.grid(row=0, column=0, sticky='news') self.grid_columnconfigure(0, weight=1) self.grid_rowconfigure('all', weight=1) root = Tk() f = Tagslist(root) f.grid() root.mainloop()
Отредактировано drevoborod (Март 9, 2016 21:54:33)
Офлайн
Поясните, пожалуйста, зачем вы хотите в Text виджет вставлять чекбоксы?
Офлайн
4kpt_IVМне кажется, это отдельный вопрос, никак не связанный с моим. Но ответить могу, однако на мой вопрос ответ мне всё равно хотелось бы получить - мне важно понять, что же пошло не так, почему Tcl вдруг потерял свою переменную окружения именно при встраивании виджета внутрь одного из окон моей программы.
Поясните, пожалуйста, зачем вы хотите в Text виджет вставлять чекбоксы?
Отредактировано drevoborod (Март 10, 2016 15:52:01)
Офлайн
Связанный. Вам нужно использовать Canvas. Если нужен пример реализации - пишите. Поищу и выложу.
Офлайн
Ок, спасибо, я уже сам понял, что изучить всё равно придётся и начал копать в эту сторону. Примеры реализации, если не сложно, покажите, пожалуйста.
Офлайн
Вот пример реализации по 2.7. Для перевода под 3.4 нужно помнять Tkinter на tkinter.
import Tkinter # def reconf_canvas(event): canv.configure(scrollregion=canv.bbox('all')) # root = Tkinter.Tk() root.geometry("400x400+100+100") # canv = Tkinter.Canvas(root, width=200, height=200) canv.grid(row=0, column=0) # frm = Tkinter.Frame(canv) frm.pack() # scr = Tkinter.Scrollbar(root) scr.grid(row=0, column=1, sticky="ns") scr["command"] = canv.yview canv["yscrollcommand"] = scr.set canv.create_window((0,0), window=frm, anchor="nw") frm.bind("<Configure>", reconf_canvas) # for i in xrange(20): but = Tkinter.Button(frm, text=u"Кнопка %01d" % i) but.pack() # root.mainloop()
Офлайн
Большое спасибо! С использованием вашего примера сделал такой вот класс. Кроме всего прочего, он умеет принимать параметр orientation, в зависимости от значения которого чекбоксы и скролл располагаются горизонтально либо вертикально.
Словарь states_dict в реальности, конечно же, формируется за пределами класса.
import tkinter class Tagslist(tkinter.Frame): """Список тегов со скроллом.""" def __init__(self, parent=None, orientation="vertical", **options): super().__init__(master=parent, **options) scroller = tkinter.Scrollbar(self, orient=orientation) self.canvbox = tkinter.Canvas(self, width=(300 if orientation == "horizontal" else 100), height=(30 if orientation == "horizontal" else 100)) scroller.config(command=(self.canvbox.xview if orientation == "horizontal" else self.canvbox.yview)) if orientation == "horizontal": self.canvbox.config(xscrollcommand=scroller.set) scroller.grid(row=1, column=0, sticky='ew') self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure('all', weight=1) else: self.canvbox.config(yscrollcommand=scroller.set) scroller.grid(row=0, column=1, sticky='ns') self.grid_rowconfigure('all', weight=1) self.grid_columnconfigure(0, weight=1) self.content_frame = tkinter.Frame(self.canvbox) self.content_frame.pack(fill='both', expand=1) self.canvbox.create_window((0,0), window=self.content_frame, anchor='nw') self.content_frame.bind("<Configure>", lambda event: self.reconf_canvas()) self.states_dict = {1: [1, 'default'], 2: [0, 'work'], 3: [0, 'personal']} # Словарь id тегов с состояниями для данной таски и именами. for key in self.states_dict: state = self.states_dict[key][0] self.states_dict[key][0] = tkinter.IntVar() cb = tkinter.Checkbutton(self.content_frame, text=self.states_dict[key][1], variable=self.states_dict[key][0]) cb.pack(side=('left' if orientation == "horizontal" else 'bottom'), anchor='w') self.states_dict[key][0].set(state) self.canvbox.grid(row=0, column=0, sticky='news') def reconf_canvas(self): """Изменение размера области прокрутки Canvas.""" self.canvbox.configure(scrollregion=self.canvbox.bbox('all')) if __name__ == "__main__": root = tkinter.Tk() f = Tagslist(root, orientation='horizontal') f.grid() print(f.states_dict) root.mainloop()
Отредактировано drevoborod (Март 11, 2016 14:55:36)
Офлайн
…А потом разошёлся и сделал два класса с наследованием - чтобы можно было использовать скроллируемый Canvas независимо от того, что мы хотим положить внутрь
import tkinter class ScrolledCanvas(tkinter.Frame): """Canvas со скроллом.""" def __init__(self, parent=None, orientation="vertical", **options): super().__init__(master=parent, **options) scroller = tkinter.Scrollbar(self, orient=orientation) self.canvbox = tkinter.Canvas(self, width=(300 if orientation == "horizontal" else 100), height=(30 if orientation == "horizontal" else 100)) scroller.config(command=(self.canvbox.xview if orientation == "horizontal" else self.canvbox.yview)) if orientation == "horizontal": self.canvbox.config(xscrollcommand=scroller.set) scroller.grid(row=1, column=0, sticky='ew') self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure('all', weight=1) else: self.canvbox.config(yscrollcommand=scroller.set) scroller.grid(row=0, column=1, sticky='ns') self.grid_rowconfigure('all', weight=1) self.grid_columnconfigure(0, weight=1) self.content_frame = tkinter.Frame(self.canvbox) self.content_frame.pack(fill='both', expand=1) self.canvbox.create_window((0,0), window=self.content_frame, anchor='nw') self.content_frame.bind("<Configure>", lambda event: self.reconf_canvas()) self.canvbox.grid(row=0, column=0, sticky='news') def reconf_canvas(self): """Изменение размера области прокрутки Canvas.""" self.canvbox.configure(scrollregion=self.canvbox.bbox('all')) class Tagslist(ScrolledCanvas): """Список тегов со скроллом.""" def __init__(self, parent=None, states_dict=None, orientation="vertical", **options): super().__init__(parent=parent, orientation=orientation, **options) self.states_dict = states_dict # нужно для того, чтобы из экземпляра потом можно было извлекать значения этой переменной. if self.states_dict is not None: for key in self.states_dict: state = self.states_dict[key][0] self.states_dict[key][0] = tkinter.IntVar() cb = tkinter.Checkbutton(self.content_frame, text=self.states_dict[key][1], variable=self.states_dict[key][0]) cb.pack(side=('left' if orientation == "horizontal" else 'bottom'), anchor='w') self.states_dict[key][0].set(state) if __name__ == "__main__": tags = {1: [1, 'default'], 2: [0, 'work'], 3: [0, 'personal']} # Словарь id тегов с состояниями для данной таски и именами. root = tkinter.Tk() table = Tagslist(root, orientation='horizontal', states_dict=tags) table.grid() for key in table.states_dict: print(table.states_dict[key][1], ': state: ', table.states_dict[key][0].get(), ', id: ', key, sep='') root.mainloop()
Отредактировано drevoborod (Март 11, 2016 15:35:32)
Офлайн
Слегка оптимизировал класс, отделил мух (вычисления) от котлет (прорисокви), и получилось вот что:
class ScrolledCanvas(tkinter.Frame): """Прокручиваемый Canvas.""" def __init__(self, parent=None, orientation="vertical", **options): super().__init__(master=parent, relief='groove', bd=2, **options) scroller = tkinter.Scrollbar(self, orient=orientation) self.canvbox = tkinter.Canvas(self, width=(300 if orientation == "horizontal" else 200), height=(30 if orientation == "horizontal" else 200)) scroller.config(command=(self.canvbox.xview if orientation == "horizontal" else self.canvbox.yview)) if orientation == "horizontal": self.canvbox.config(xscrollcommand=scroller.set) else: self.canvbox.config(yscrollcommand=scroller.set) scroller.pack(fill='x' if orientation == 'horizontal' else 'y', expand=1, side='bottom' if orientation == 'horizontal' else 'right', anchor='s' if orientation == 'horizontal' else 'e') self.content_frame = tkinter.Frame(self.canvbox) self.canvbox.create_window((0,0), window=self.content_frame, anchor='nw') self.content_frame.bind("<Configure>", lambda event: self.reconf_canvas()) self.canvbox.pack(fill="x" if orientation == "horizontal" else "both", expand=1) def reconf_canvas(self): """Изменение размера области прокрутки Canvas.""" self.canvbox.configure(scrollregion=self.canvbox.bbox('all'))
Офлайн
В общем хорошо.
Минусы:
1. Узнайте про PEP8
2. Вы задаете размеры канваса. Это не верно.
3. Однострочные логические выражения везде и вся это овер.
4. Ну и докстринги в на русском смотрятся как-то не очень
И… Ну это тындец. Я же уже писал
self.content_frame.bind("<Configure>", lambda event: self.reconf_canvas())
Офлайн