tkinter.LabelFrame with Checkbutton labelwidget - python-3.x

In tkinter, I'd like to have a frame similar to LabelFrame, but with a Checkbutton as the label.
After reading the LabelFrame sources, I saw the labelwidget option and tried
checkbutton = tk.Checkbutton(text=text)
checkbutton_frame = tk.LabelFrame(window, labelwidget=checkbutton)
It works visually, but the Checkbutton ends up being a child of window (window.winfo_children()). Alternatively:
checkbutton_frame = tk.LabelFrame(window)
checkbutton = tk.Checkbutton(checkbutton_frame, text="Hello, frame")
checkbutton_frame.configure(labelwidget=checkbutton)
makes the Checkbutton a child of the LabelFrame. The default, built-in Label of LabelFrame doesn't appear in winfo_children. Because I set padx and pady recursively after adding all my widgets, having the Checkbutton in winfo_children breaks the layout by moving the Checkbutton out of its anchored position. Is there a nice, clean way to substitute a Checkbutton for the Label in LabelFrame that maintains consistent (i.e. lack of any) descendant relationship? Answers with other workarounds are welcome.
Minimal reproduction
import tkinter as tk
def pack_configure_recursive(widget, **kwargs):
stack = list(widget.winfo_children())
while stack:
descendent = stack.pop()
try:
stack.extend(descendent.winfo_children())
except Exception:
pass
try:
descendent.pack_configure(**kwargs)
except Exception:
pass
class CheckbuttonFrame(tk.LabelFrame):
def __init__(self, master=None, **kwargs):
def pop_kwarg(name):
nonlocal kwargs
if name in kwargs:
result = kwargs[name]
del kwargs[name]
return result
else:
return None
text = pop_kwarg("text")
self.variable = pop_kwarg("variable")
self.command = pop_kwarg("command")
tk.LabelFrame.__init__(self, master, **kwargs)
self.configure(labelwidget=tk.Checkbutton(text=text, variable=self.variable, command=self.handler)) # Use this to make a child of the window
# self.configure(labelwidget=tk.Checkbutton(self, text=text, variable=self.variable, command=self.handler)) # Use this to make a child of the frame
def update_state(self):
if self.variable:
state = 'normal' if self.variable.get() else 'disable'
stack = list(self.winfo_children())
while stack:
descendent = stack.pop()
try:
stack.extend(descendent.winfo_children())
except Exception:
pass
try:
descendent.configure(state=state)
except tk.TclError:
pass
def handler(self):
self.update_state()
if self.command:
external_command = self.command
external_command()
window = tk.Tk()
option = tk.IntVar()
checkbutton_frame = CheckbuttonFrame(window, text="My option group", variable=option)
checkbutton_frame.pack()
tk.Label(checkbutton_frame, text="More widgets here").pack()
checkbutton_frame.update_state()
pack_configure_recursive(window, padx=2, pady=2) # This line break the layout
window.mainloop()

The default, built-in Label of LabelFrame doesn't appear in winfo_children
That is because the built-in label of the LabelFrame isn't a widget.
Is there a nice, clean way to substitute a Checkbutton for the Label in LabelFrame that maintains consistent (i.e. lack of any) descendant relationship? Answers with other workarounds are welcome.
No, there is not. The widget must be a descendant of something, there is simply no getting around that. And logically, it should probably be the descendant of the labelframe itself.
Any code you have to automatically re-pack widgets will need to skip over this widget.
Your code to call pack_configure should probably just ignore any widget not managed by pack, which you can do like this:
if descendent.winfo_manager() == "pack":
descendent.pack_configure(**kwargs)

Related

How can I set the default container in a decorator class for tkinter.Frame?

I would like to create a contractible panel in a GUI, using the Python package tkinter.
My idea is to create a decorator for the tkinter.Frameclass, adding a nested frame and a "vertical button" which toggles the nested frame.
Sketch: (Edit: The gray box should say Parent of contractible panel)
I got it to toggle just fine, using the nested frame's grid_remove to hide it and then move the button to the left column (otherwise occupied by the frame).
Now I want to be able to use it like any other tkinter.Frame, but let it target the nested frame. Almost acting like a proxy for the nested frame. For example, adding a tkinter.Label (the green Child component in the sketch) to the decorator should add the label to the nested frame component (light yellow tk.Frame in the sketch) not the decorator itself (strong yellow ContractiblePanel in the sketch).
Minimal example: (omitting the toggling stuff and any "formatting"):
(Here's a published (runnable) Repl project)
import tkinter
class ContractiblePanel(tkinter.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self._panel = tkinter.Frame(self)
self._toggle = tkinter.Button(self, text='<', command=self._toggle_panel)
self.grid(row=0, column=0, sticky='nsw')
self._panel.grid(row=0, column=0, sticky='nsw')
self._toggle.grid(row=0, column=1, sticky='nsw')
def _toggle_panel(self):
# ...
if __name__ == '__main__':
root = tkinter.Tk()
root.geometry('128x128')
contractible_panel = ContractiblePanel(root)
Forwarding configuration calls is just overriding the config method I guess?
class ContractiblePanel(tkinter.Frame):
# ...
def config(self, **kwargs):
self._panel.config(**kwargs)
# ...
contractible_panel.config(background='blue')
But I would like to be able to add a child component into the nested panel frame by
label_in_panel = tkinter.Label(contractible_panel, text='yadayada')
How do I get the ContractiblePanel object to act like a proxy to its member _panel, when adding child components?
What other methods/use cases should I consider? I am quite new to tkinter and thus expect the current implementation to break some common practices when developing tkinter GUIs.
This is an interesting question. Unfortunately, tkinter really isn't designed to support what you want. I think it would be less complicated to simply expose the inner frame and add widgets to it.
That being said, I'll present one possible solution. It's not implemented as a python decorator, but rather a custom class.
The difficulty is that you want the instance of the custom class to represent the outer frame in one context (for example, when packing it in your UI) and the inner frame in another context (when adding child widgets to it)
The following solution solves this by making the instance be the inner frame, and then overriding pack,place, and grid so that they operates on the outer frame. This works fine, with an important exception: you cannot use this class directly inside a notebook or embedded in a text widget or canvas.
I've used colors and borders so it's easy to see the individual components, but you can remove the colors in production code, obviously. Also, I used a label instead of a button since I created the screenshot on OSX where the background color of a button can't be changed.
import tkinter as tk
class ContractiblePanel(tk.Frame):
def __init__(self, parent, **kwargs):
self._frame = tk.Frame(parent, **kwargs)
super().__init__(self._frame, bd=2, relief="solid", bg="#EFE4B0")
self._button = tk.Label(
self._frame, text="<", bg="#00A2E8", bd=2,
relief="solid", font=("Helvetica", 20), width=4
)
self._frame.grid_rowconfigure(0, weight=1)
self._frame.grid_columnconfigure(0, weight=1)
self._button.grid(row=0, column=1, sticky="ns", padx=4, pady=4)
super().grid(row=0, column=0, sticky="nsew", padx=4, pady=4)
self._button.bind("<1>", lambda event: self.toggle())
def collapse(self):
super().grid_remove()
self._button.configure(text=">")
def expand(self):
super().grid()
self._button.configure(text="<")
def toggle(self):
self.collapse() if self.winfo_viewable() else self.expand()
def pack(self, **kwargs):
# override to call pack in the private frame
self._frame.pack(**kwargs)
def grid(self, **kwargs):
# override to call grid in the private frame
self._frame.grid(**kwargs)
def place(self, **kwargs):
# override to call place in the private frame
self._frame.place(**kwargs)
root = tk.Tk()
root.geometry("400x300")
cp = ContractiblePanel(root, bg="yellow", bd=2, relief="raised")
cp.pack(side="left", fill="y", padx=10, pady=10)
label = tk.Label(cp, text="Child component", background="#22B14C", height=3, bd=2, relief="solid")
label.pack(side="top", expand=True, padx=20, pady=20)
root.mainloop()
First of all it is kinda gross to use this code and it's very confusing. So I'm really not sure if you really want to take this route. However, it is possible to achieve it.
The basic idea is to have a wrapper and to pretend the wrapper is the actual object you can lie with __str__ and __repr__ about what the class really is. That is not what a proxy means.
class WrapperClass:
def __init__(self, master=None, **kwargs):
self._wrapped_frame = tk.Frame(master, **kwargs)
self._panel = tk.Frame(self._wrapped_frame)
self._toggle = tk.Button(self._wrapped_frame, text='<', command=self._toggle_panel)
self._wrapped_frame.grid(row=0, column=0, sticky='nsw')
self._panel.grid(row=0, column=0, sticky='nsw')
self._toggle.grid(row=0, column=1, sticky='nsw')
return None
def _toggle_panel(self):
print('toggle')
def __str__(self):
return self._panel._w
__repr__ = __str__
You can do even more confusing things by delegate the lookup-chain to the _wrapped_frame inside the WrapperClass this enables you to call on the instance of WrapperFrame() methods like pack or every other method. It kinda works similar for inheritance with the difference that by referring to the object, you will point to different one.
I don't recommend using this code by the way.
import tkinter as tk
NONE = object()
#use an object here that there will no mistake
class WrapperClass:
def __init__(self, master=None, **kwargs):
self._wrapped_frame = tk.Frame(master, **kwargs)
self._panel = tk.Frame(self._wrapped_frame)
self._toggle = tk.Button(self._wrapped_frame, text='<', command=self._toggle_panel)
self._wrapped_frame.grid(row=0, column=0, sticky='nsw')
self._panel.grid(row=0, column=0, sticky='nsw')
self._toggle.grid(row=0, column=1, sticky='nsw')
return None
def _toggle_panel(self):
print('toggle')
def __str__(self):
return self._panel._w
__repr__ = __str__
def __getattr__(self, name):
#when wrapper class has no attr name
#delegate the lookup chain to self.frame
inreturn = getattr(self._wrapped_frame, name, NONE)
if inreturn is NONE:
super().__getattribute__(name)
return inreturn
root = tk.Tk()
wrapped_frame = WrapperClass(root, bg='red', width=200, height=200)
root.mainloop()

How can I create a multiple buttons in tkinter that change the background image when hovering over them?

I am coding a GUI for a Mathematics Formula Calculator. I want to create multiple buttons that change the background image when hovering over them, and I don't really know how to go about doing that...
I have already tried creating a class for the button itself so that I can modify the behaviour of it, it did not work...
import tkinter as tk
class HoverButton(tk.Button):
def __init__(self, master, **kw):
tk.Button.__init__(self, master=master,**kw)
self.defaultbackground = tk.PhotoImage(file = "GeometricBackground.png")
self.currentbackground = tk.PhotoImage(file = "")
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
def on_enter(self, currentbackground):
image = tk.Label(self, image = currentbackground)
image.pack()
def on_leave(self):
image = tk.Label(self, image = self.defaultbackground)
image.pack()
root = tk.Tk()
classButton = HoverButton(root, currentbackground = "MainMenu.png")
classButton.grid()
root.mainloop()
I was really hoping this would cut it, but I got this error message when it executed:
_tkinter.TclError: unknown option "-currentbackground"
Any help would be appreciated :)
There are several issues with your code:
The error you get is because you are trying to pass the currentbackground option to your HoverButton but given the way your class is defined:
def __init__(self, master, **kw):
tk.Button.__init__(self, master=master,**kw)
currentbackground ends into the kw dictionary you pass in argument to the standard tkinter Button class which has no currentbackground option, hence the unknown option error. To fix it, you can define the options specific to your class like
def __init__(self, master, defaultbackground="", currentbackground="", **kw):
tk.Button.__init__(self, master=master, **kw)
so that defaultbackground and currentbackground won't end in the kw dictionary.
When an event occurs, the function you bound to this event is executed with one argument, the "event" object that contains information about the event (like the pointer coordinates, the widget in which the event happened ...) so you need to add this argument when you define on_enter() and on_leave().
You are creating a label to put the image inside then packing this label in the button. This is overly complicated (and probably will result in the button not reacting to click events). The button class has an image option to set the background image of the button, so you can change the image with button.configure(image=<image>).
Inserting all those changes in the code gives
import tkinter as tk
class HoverButton(tk.Button):
def __init__(self, master, defaultbackground="GeometricBackground.png", currentbackground="", **kw):
tk.Button.__init__(self, master=master, **kw)
self.defaultbackground = tk.PhotoImage(file=defaultbackground)
self.currentbackground = tk.PhotoImage(file=currentbackground)
self.configure(image=self.defaultbackground)
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
def on_enter(self, event):
self.configure(image=self.currentbackground)
def on_leave(self, event):
self.configure(image=self.defaultbackground)
root = tk.Tk()
classButton = HoverButton(root, currentbackground="MainMenu.png")
classButton.grid()
root.mainloop()

Python Tkinter GUI File Menu Not Displaying though GUI is operational

I'm relatively new to Python and I'm sure this is an error with the structure of my code, but I cannot seem to get the filemenu to display in my GUI. Can someone tell me what errors I have made with the filemenu inclusion? Also, I am sorry, but the spacing after copying and pasting is a little off. The class indentation level is proper on my side. I am using Python 3.71
Any other comments on better or more Pythonic ways to accomplish what I have here are also welcome and thank you for your help in advance!
from tkinter import *
from tkinter import ttk
import tkinter.scrolledtext as tkst
import os
import tkinter as tk
from functools import partial
from PIL import Image, ImageTk
class UserGui(tk.Tk):
def __init__(self,parent):
self.parent=parent
self.widgets()
def widgets(self):
self.parent.configure(bg='white')
self.frame1_style = ttk.Style()
self.frame1_style.configure('My.TFrame', background='white')
self.frame2_style = ttk.Style()
self.frame2_style.configure('My2.TFrame',background='white')
self.parent.title("TGUI")
self.frame1 = ttk.Frame(self.parent, style='My.TFrame') #Creating Total Window Frame 1
self.frame1.grid(row=0, column=0, sticky=(N, S, E, W))
self.frame2 = ttk.Frame(self.parent, width=100, height=20, style='My2.TFrame')
self.frame2.grid(row=0, column=6, padx=20, pady=5)
#Menu Creation
self.menu1 = tk.Menu(self.parent, tearoff=0)
self.parent.config(menu=self.menu1)
self.fileMenu = tk.Menu(self.menu1, tearoff=0)
self.fileMenu.add_command(label="Open", command=self.donothing)
self.fileMenu.add_command(label="Save", command=self.donothing)
self.fileMenu.add_separator()
self.fileMenu.add_command(label="Exit", command=self.parent.quit)
self.fileMenu.add_cascade(label="File", menu=self.menu1)
self.editMenu = tk.Menu(self.menu1, tearoff=0)
self.editMenu.add_command(label="Cut", command=self.donothing)
self.editMenu.add_command(label="Copy", command=self.donothing)
self.editMenu.add_command(label="Paste", command=self.donothing)
self.editMenu.add_cascade(label="Edit", menu=self.menu1)
def donothing(self):
filewin = Toplevel(self.parent)
button = Button(filewin, text="Do nothing button")
button.pack()
def main():
root=tk.Tk()
ug=UserGui(root)
root.mainloop()
if __name__ == '__main__':
main()
Edit 1,2,3: I have corrected the add_cascade option for menu with menu=self.menu1 and I still do not have a file menu displaying.
EDIT: I'm sorry I didn't notice the Python-3 tag in time, it's all the same except when inherriting you would call super().__init__ instead of the Frame.__init__ directly. That would make it more Py3-like. Even so, this should still work.
Weirdly, pushing the menu.config down to the run function worked for me - even though it looks like it should work the way you did it.
def main():
root=tk.Tk()
ug=UserGui(root)
root.config(menu=ug.fileMenu)
root.mainloop()
if __name__ == '__main__':
main()
Oterwise there are some things you can work on to make it more OOP like and readable. THis is how I usually handle making GUIs. The idea is to split the GUI's into Frames that then do simmilar things. I.e. your app could have left and right Frame where the RightFrame would hold the textbox ad the left Frame would actually have 2 sub frames - one for the names and dropdowns and the other for the buttons. That way each individual functionality is handled by the Frames themselves and it's not all in one giant class, the elements in those Frames are placed relative to the Frame's grid itself, while all the Frames are placed in the MainFrame's grid. This lets you split a lot of code into modules as well and helps with maintainability.
The sub-frames emit "global" events (events bothering other frames) by propagating them through the MainFrame, that's why they all carry a self.parent - their parent frame, and a self.root - the MainFrame. The MainFrame is also the Frame in which I like to put something like self.data which itself is a class on its own (outside Tkinter) that handles all the data input/output and logic so that you don't clutter the GUI code logic with data calculations and logic. Ideally the Data class would handle data errors and GUI would only then have to handle any errors in logic (such as selecting two impossible-to-combine options from the dropdown menus.
from tkinter import *
from tkinter import ttk
class SubFrame(Frame):
def __init__(self, parent, text="Top Right"):
Frame.__init__(self)
self.pack()
self.parent = parent
self.root = parent.root
self.label=Label(self, text=text).pack()
class RightFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, relief=RAISED, borderwidth=1)
self.pack(side=RIGHT, fill=BOTH, expand=1)
self.root = parent
self.label = Label(self, text="Right Frame").pack()
class LeftFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, relief=RAISED, borderwidth=1)
self.pack(side=LEFT, fill=BOTH, expand=1)
self.root = parent
self.label = Label(self, text="Left Frame").pack()
#again Frames which would have a parent class RightFrame and root MainFrame
self.subFrame1 = SubFrame(self)
self.subFrame2 = SubFrame(self, text="Top Right SubFrame 2")
class MainFrame(Tk):
def __init__(self):
Tk.__init__(self)
self.geometry("1100x600")
self.title("Working Example")
self.leftFrame = LeftFrame(self)
self.rightFrame = RightFrame(self)
#self.data = MagicalDataHandlingClass()
def run():
app = MainFrame()
app.mainloop()
EDIT answer to comments that are too long to fit
The call to Frame.__init__(...) is made because the class definition looks like class LeftFrame(Frame). Usually to declare a class what you would write is just class LeftFrame. When you add the bit in the () what is happening is called inheritance. When you inherit from a class (called parent), your class (called child) inherits all of the methods and attributes of parent. But much like you have to initialize your class to get an object, i.e. lf = LeftFrame(...) the parent class has to be initialized too. In Python this initialization is done by calling the special dunder __init__(...) function. So that call to Frame.__init__(...) happens because you need to tell the parent class what are all the values it needs to work properly. In Python 3 however it is recommended that instead of instantiating the parent by name like that you use the super function like super().__init__(....). This happens for a lot of complicated reasons most of which you probably don't have to worry about for a while yet (such as what if you inherit from multiple classes at the same time, what if you inherit from a class that inherited from a different one, etc...). I wouldn't try to feel overwhelmed by understanding the complete power of super() if you're just starting because 99% of the time in Python 3 just doing super().__init__(...) will do exactly what you want even if you don't understand. If you feel like getting in over your head Raymond Hettinger has a good writeup of Super is Super and why exactly it's so much better than old way.
I will post this answer for completeness considering #JasonHarper has not copied it to an answer format and I want others to be able to benefit from the post.
The key was the object that I was calling the add_cascade on the child Menu widget object instead of the main Menu widget object called self.menu1. The key was changing:
self.fileMenu.add_cascade(label="File", menu=self.menu1)
to :
self.menu1.add_cascade(label="File", menu=self.fileMenu)
This was the proper way of adding the fileMenu Menu object to the total Menu widget object of self.menu1.

QTreeView not spanning parent width or height

So I am new to QtGui and looking up how to do things, and I found this neat example on QTreeView. When I got it working on my own, I noticed that it didn't fill the space as I'd anticipated:
So I have been searching for answers, and not finding much in either Python or C++ resources. I've been checking the documentation a lot, but still not quite finding what I'm searching for.
So it seems clear that something doesn't have the correct size policy, but I am having a hard time figuring out what. I have so far eliminated a couple of potential candidates:
The QWidget instance holding the QTreeView instance is correctly spanning the layout it is in (the QWidget spans the width of the QGroupBox minus a little for margins).
Since QTreeView's parent widget is the correct dimensions, I figured it's something more local to QTreeView, but when I use the setSizePolicy, none of the policies I've used seem to resolve the issue. Possibly multiple steps I'm unaware of?
The QTreeView's inherited viewport (from QAbstractScrollArea is much smaller than I expect. Calling QTreeView's setViewport() method with a new and empty QWidget only redraws the non-header contents background in gray instead of white, and I suspect that this is close but not where I need to look.
QTreeView has other children (besides viewport)that I am still investigating.
Most of what I have tried I left commented out in my code below.
This is my source code to reproduce:
import sys
from PySide.QtGui import *
class TreeTime(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.main_widget = QWidget()
self.main_layout = QVBoxLayout()
self.main_widget.setLayout(self.main_layout)
self.setCentralWidget(self.main_widget)
self.statusBar()
self.make_tree()
self.show()
def make_tree(self):
# init widgets
self.tgb = QGroupBox("[Tree Group Box Title]")
self.main_layout.addWidget(self.tgb)
tgb_layout = QVBoxLayout()
self.tgb.setLayout(tgb_layout)
tgb_widget = QWidget()
tgb_layout.addWidget(tgb_widget)
debug_btn = QPushButton("DEBUG")
tgb_layout.addWidget(debug_btn)
view = QTreeView(parent=tgb_widget)
# view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
view.setSelectionBehavior(QAbstractItemView.SelectRows)
model = QStandardItemModel()
model.setHorizontalHeaderLabels(['col1', 'col2', 'col3'])
view.setModel(model)
view.setUniformRowHeights(True)
# populate data
for i in range(10):
parent1 = QStandardItem('Family {}. Some long status text for sp'.format(i))
for j in range(3):
child1 = QStandardItem('Child {}'.format(i*3+j))
child2 = QStandardItem('row: {}, col: {}'.format(i, j+1))
child3 = QStandardItem('row: {}, col: {}'.format(i, j+2))
parent1.appendRow([child1, child2, child3])
model.appendRow(parent1)
# span container columns
view.setFirstColumnSpanned(i, view.rootIndex(), True)
# expand third container
index = model.indexFromItem(parent1)
view.expand(index)
# select last row
selmod = view.selectionModel()
index2 = model.indexFromItem(child3)
selmod.select(index2, QItemSelectionModel.Select|QItemSelectionModel.Rows)
def print_debug_info():
print('')
for child in view.children():
print("child "+repr(child)) #not sure what all these are yet
print('')
print('self.main_widget.frameSize: '+repr(self.main_widget.frameSize()))
print('view.parent().parent().frameSize(): '+repr(view.parent().parent().frameSize())) #group box
# print('self.frameSize: '+repr(self.frameSize()))
print('self.tgb.frameSize: '+repr(self.tgb.frameSize()))
print('view.parent(): '+repr(view.parent()))
print('view.parent().frameSize(): '+repr(view.parent().frameSize()))
# print('view.parent().frameSize(): '+repr(view.parent().frameSize())+" (before)")
# print('view.parent().adjustSize(): '+repr(view.parent().adjustSize()))
# print('view.parent().frameSize(): '+repr(view.parent().frameSize())+" (after)")
print('view.viewport(): '+repr(view.viewport()))
print('view.viewport().frameSize(): '+repr(view.viewport().frameSize()))
# print('view.parent().parent().parent().frameSize(): '+repr(view.parent().parent().parent().frameSize()))
# print('calling setViewport: '+repr(view.setViewport(QWidget())))
# view.adjustSize()
debug_btn.clicked.connect(print_debug_info)
def sayHello(self):
self.statusBar().showMessage("Hello World!")
import time; time.sleep(2)
self.statusBar().showMessage("")
def sayWords(self, words):
self.statusBar().showMessage(words)
if __name__ == '__main__':
app = QApplication([])
tt = TreeTime()
sys.exit(app.exec_())
I am using a Windows 8.1 machine and Python 3.4.3, PySide version 1.2.2 - any help will be much appreciated! (also, please let me know if I left out any important details)
UPDATE (5/19/2015): I tried moving my DEBUG button outside the QGroupBox, and the result was the QTreeView being collapsed into a completely nonlegible size so you couldn't even tell what the object was anymore, so it seems to be minimizing the space used, even when I uncomment the line:
view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
One friend has suggested this may simply be an issue with windows and not my code, but I don't have anything to back that up.
UPDATE 5/19/2015: I have implemented the advice provided by #titusjan, but I have the same problem/behavior.
You need to remove the redundant tp_widget and add view to tgb_layout:
def make_tree(self):
# init widgets
self.tgb = QGroupBox("[Tree Group Box Title]")
self.main_layout.addWidget(self.tgb)
tgb_layout = QVBoxLayout()
self.tgb.setLayout(tgb_layout)
view = QTreeView()
tgb_layout.addWidget(view)
...
debug_btn = QPushButton("DEBUG")
tgb_layout.addWidget(debug_btn)
Note that when you add widgets to a layout, they will be automatically re-parented to the parent of the layout (whenever it gets one), so it's not really necessary set one in the constructor.
Also note that this:
tgb_layout = QVBoxLayout(self.tgb)
is exactly equivalent to this:
tgb_layout = QVBoxLayout()
self.tgb.setLayout(tgb_layout)
because the layout will always be re-parented to the widget it's set on.
You must use the setLayout method to link the layout to the widget. So change...
self.main_layout = QVBoxLayout(self.main_widget)
into
self.main_layout = QVBoxLayout()
self.main_widget.setLayout(self.main_layout)
Similar for the tgb_view layout (which I would rename to tgb_layout for clarity).
Finally you forgot to add the tree view to this layout, so add:
tgb_view.addWidget(view)
I've put all the relevant modified code below for convenience.
def initUI(self):
self.main_widget = QWidget()
self.main_layout = QVBoxLayout()
self.main_widget.setLayout(self.main_layout)
self.setCentralWidget(self.main_widget)
self.statusBar()
self.make_tree()
self.show()
def make_tree(self):
# init widgets
self.tgb = QGroupBox("[Tree Group Box Title]")
self.main_layout.addWidget(self.tgb)
tgb_view = QVBoxLayout()
self.tgb.setLayout(tgb_view)
tgb_widget = QWidget()
tgb_view.addWidget(tgb_widget)
debug_btn = QPushButton("DEBUG")
tgb_view.addWidget(debug_btn)
view = QTreeView(parent=tgb_widget)
tgb_view.addWidget(view)
...
The size policy stuff is not necessary, the defaults are fine.

How to display text of listbox items in the canvas with Python3x tkiner?

I am trying to display the text of the selected item of a listbox into the canvas. When I bind the listbox to a helper event handler, it throws Attribute Error: CLASS object has no attribute "HELPER EVENT HANDLER".
What I want is as follows:
1) When double clicking an item in the listbox to the left, its text should be displayed on the canvas. This particular line of code is causing all the troulbe to me.
lstbox.bind("<Double-Button-1>", self.OnDouble)
Could you please help me fixing this error?
2) I believe that there must be a way to make the lines' height on the listbox larger than they appear in my application. However, I don't know how to do it. I tried providing several options but these options are not recognized by tkinter. Could you please suggest to me how to do it?
Here is the code:
import tkinter as tk
languages = ['Mandarin', 'English', 'French']
class LanguageFamilies(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
canv = tk.Canvas(self, width=675, height=530, bg="white", relief="sunken")
canv.config(scrollregion=(0,0,300,650), highlightthickness=0)
canv.pack(side="right", expand=True, fill="both")
# Create scroll bar
sbar = tk.Scrollbar(self)
canv.config(yscrollcommand=sbar.set)
sbar.config(command=canv.yview)
sbar.pack(side="right", fill="both")
# Create Scroll List
lstbox = tk.Listbox(self, width=240, height=530, relief="sunken", font="Courier")
lst_scrollbar = tk.Scrollbar(self)
lstbox.config(yscrollcommand=lst_scrollbar.set)
lst_scrollbar.config(command=lstbox.yview)
lstbox.pack(side="left", expand=True, fill="both")
lst_scrollbar.pack(side="right", fill="both")
lstbox.bind("<Double-Button-1>", self.OnDouble) # THIS IS THE LINE CAUSING THE ERROR
# Add items to the lstbox
i = 0
while i < len(languages):
lstbox.insert(i, languages[i])
i += 1
# Create a text inside canvas
canv_id = canv.create_text(50, 50, font="Times 14", anchor="nw")
msg = "This is a text inside canvas."
canv.insert(canv_id, "end", msg)
#Binding Handler
def OnDouble(self, event):
self.widget = event.widget
selection = self.widget.curselection()
content = self.widget.get(selection[0])
print("You selected", content)
if __name__ == "__main__":
root = tk.Tk()
root.geometry("930x530")
root.title("Language Families")
LanguageFamilies(root).pack(fill="both", expand=True)
root.mainloop()
And this is the error message:
Traceback (most recent call last):
File "C:/Python33/new_stack_overflow.py", line 43, in <module>
LanguageFamilies(root).pack(fill="both", expand=True)
File "C:/Python33/new_stack_overflow.py", line 23, in __init__
lstbox.bind("<Double-Button-1>", self.OnDouble)
AttributeError: 'LanguageFamilies' object has no attribute 'OnDouble'
Your help is highly appreciated!
the problem is that the def for OnDouble is defined inside __init__, making it not a class method but a method inside the scope of __init__. You need to remove one level of indentation for OnDouble.
HERE IS THE COMPLETE AND CORRECT ANSWER! BELOW IS STAGES OF SOLVING THE PROBLEM.
Bryan's suggestions of decreasing the indentation of the OnDouble event handler by one level were the first steps to the solution.
The crucial thing I learnt from this experience is that printing the text of a listbox on the canvas directly is not the right option. Rather, the best option is to put a text widget on the canvas so that manipulating text matters will be much easier. I came up to this conclusion after watching a nice video tutorial here. As such, a text widget was defined ontop of the canvas. An if condition was used to control what to print on the text-canvas and how many times it should be printed - (in this case, only onece exactly the same as an electronic dictionary - which is my application).
Here is the COMPLETE WORKING code:
import tkinter as tk
languages = ['Mandarin', 'English', 'French']
global counter
counter = 1
class LanguageFamilies(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
global canv, canv_id # IN ORDER TO MAKE THEM ACCESSIBLE TO OnDouble EVENT HANDLER
canv = tk.Canvas(self, width=675, height=530, bg="white", relief="sunken")
canv.config(scrollregion=(0,0,300,650), highlightthickness=0)
canv.pack(side="right", expand=True, fill="both")
# Create scroll bar
sbar = tk.Scrollbar(self)
canv.config(yscrollcommand=sbar.set)
sbar.config(command=canv.yview)
sbar.pack(side="right", fill="both")
# Create Scroll List
lstbox = tk.Listbox(self, width=240, height=530, relief="sunken", font="Courier")
lst_scrollbar = tk.Scrollbar(self)
lstbox.config(yscrollcommand=lst_scrollbar.set)
lst_scrollbar.config(command=lstbox.yview)
lstbox.pack(side="left", expand=True, fill="both")
lst_scrollbar.pack(side="right", fill="both")
lstbox.bind("<Double-Button-1>", self.OnDouble)
# Add items to the lstbox
i = 0
while i < len(languages):
lstbox.insert(i, languages[i])
i += 1
# Create a text inside canvas
global textmatter
textmatter = tk.Text(canv) # THE TEXT SHOULD BE PRINTED ON THE CANVAS
textmatter.insert("end", "Double-click on a language on the left to view information.")
textmatter.pack()
#Binding Handler, ONE INDENTATION LEVEL IS REMOVED AS PER BRYAN'S SUGGESTION
def OnDouble(self, event):
global counter
self.widget = event.widget
selection = self.widget.curselection()
content = self.widget.get(selection[0])
if counter == 1:
textmatter.delete(1.0, "end") # CLEAR THE TEXT WHICH IS DISPLAYED AT FIRST RUNNING
textmatter.insert("end", content)
counter += 1 #ONE ITEM HAS BEEN SELECTED AND PRINTED SUCCESSFULLY
elif counter > 1:
textmatter.delete(1.0, "end")
textmatter.insert("end", content)
counter = 1 # RESET THE COUNTER SO THAT NEXT SELECTED ITEM DISPLAYS PROPERLY
if __name__ == "__main__":
root = tk.Tk()
root.geometry("930x530")
root.title("Language Families")
LanguageFamilies(root).pack(fill="both", expand=True)
root.mainloop()
That is it. I hope this post will be of help to you!
SALAM,
Mohammed

Resources