Connect an Object to an item in a Treeview Widget - python

I created a ttk/Treeview Widget in Tkinter using Python 3. I would like to connect an object to its name which is listed in the tree view. To illustrate this I created following example.
import tkinter as tk
from tkinter import ttk
class myclass:
def __init__(self, name, value):
self.name=name
self.value=value
class maintree(ttk.Treeview):
def __init__(self, master):
super().__init__(master)
self.master = master
self.my_objects= [myclass("object"+str(_), _) for _ in range(1,11)]
for my_object in self.my_objects:
self.insert("", "end", text=my_object.name)
def main():
root = tk.Tk()
maintree(root).grid()
root.mainloop()
if __name__ == '__main__':
main()
In this example I would like to get the my_class instance corresponding to the selected name in the treeview to do something (ie, display the value of the currently selected my_class object).
I only know about the item IDs but I don't know how to connect something to an item itself. I have the feeling that I have some misconception about how treeview is supposed to work.
I appreciate your Help!

The insert method has no command option. However, you can tag each inserted item and bind the tag to an event. So, I used the object name as tag and then bound it to execute some method of the object on mouse left click.
import tkinter as tk
from tkinter import ttk
class MyClass:
def __init__(self, name, value):
self.name=name
self.value=value
def callback(self, event=None):
# event=None is a trick to be able to call the method both directly and
# from a binding (that will pass the event argument to the function)
print(self.name, self.value)
class MainTree(ttk.Treeview):
def __init__(self, master):
super().__init__(master)
self.master = master
self.my_objects= [MyClass("object"+str(_), _) for _ in range(1,11)]
for my_object in self.my_objects:
self.insert("", "end", text=my_object.name, tags=(my_object.name,))
self.tag_bind(my_object.name, '<Button-1>', my_object.callback)
def main():
root = tk.Tk()
MainTree(root).grid()
root.mainloop()
if __name__ == '__main__':
main()

Related

Trying to access widget from other class tkinter

When I am trying to reference in a different class in a function I get a error saying their is not attribute between the widget I am referencing and the class. Here is the code displaying the referencing:
class Window(tk.Tk):
def __init__(self):
super().__init__()
self.init_csv()
def init_csv(self):
LeftFrame.spellings_listbox.insert(tk.END,i)
class LeftFrame(tk.Frame):
def __init__(self, container):
super().__init__(container,width=400,height=600,bg="red")
self.pack(side=tk.LEFT)
self.pack_propagate(0)
self.widgets()
def widgets(self):
self.spellings_listbox = tk.Listbox(self)
self.spellings_listbox.pack(expand=True,fill=tk.BOTH,side=tk.BOTTOM)
You have to create an instance of the LeftFrame class before you can use it or the widgets inside. This isn't something unique to tkinter, it's a fundamental aspect of using classes.
Usually the solution looks something like this:
class Window(tk.Tk):
def __init__(self):
super().__init__()
self.init_csv()
self.left_frame = LeftFrame(self)
def init_csv(self):
self.left_frame.spellings_listbox.insert(tk.END,i)

How to replace lambda callback handlers with callable class object callback handlers?

My following question is related to example 8-10 of Programming Python, 4th ed by Mark Lutz. The example is about creating a bar of simple buttons that launch dialog demos and return the values of those dialog calls in stdout.
The dialog table looks like the following:
#dialogTable.py
from tkinter.filedialog import askopenfilename
from tkinter.colorchooser import askcolor
from tkinter.messagebox import askquestion, showerror
from tkinter.simpledialog import askfloat
demos = {
'Open': askopenfilename,
'Color': askcolor,
'Query': lambda: askquestion('Warning', 'You typed "rm *"\nConfirm?'),
'Error': lambda: showerror('Error!', "He's dead, Jim"),
'Input': lambda: askfloat('Entry', 'Enter credit card number')
}
The code below creates the bar of buttons and makes them functional. My question relates to this part:
from tkinter import *
from dialogTable import demos # button callback handlers
from quitter import Quitter # attach a quit object to me
class Demo(Frame):
def __init__(self, parent=None, **options):
Frame.__init__(self, parent)
self.pack()
Label(self, text="Basic demos").pack()
for key in demos:
func = (lambda key=key: self.printit(key))
Button(self, text=key, command=func).pack(side=TOP, fill=BOTH)
Quitter(self).pack(side=TOP, fill=BOTH)
def printit(self, name):
print(name, 'returns =>', demos[name]()) # fetch, call, print
if __name__ == '__main__': Demo().mainloop()
Quitter is just a class for the quit button.
My question is how do I rewrite this code to use a callable class object (__call__) instead of a lambda to defer the call to the actual handler?
Well, in this case, you could do something to the effect of:
from tkinter import *
from dialogTable import demos # button callback handlers
from quitter import Quitter # attach a quit object to me
class Wrapper:
def __init__(self, func, key):
self.func = func
self.key = key
def __call__(self):
return self.func(self.key)
class Demo(Frame):
def __init__(self, parent=None, **options):
Frame.__init__(self, parent)
self.pack()
Label(self, text="Basic demos").pack()
for key in demos:
func = Wrapper(self.printit, key)
Button(self, text=key, command=func).pack(side=TOP, fill=BOTH)
Quitter(self).pack(side=TOP, fill=BOTH)
def printit(self, name):
print(name, 'returns =>', demos[name]()) # fetch, call, print
if __name__ == '__main__': Demo().mainloop()

Class Inheriting Tkinter LabelFrame Not Displaying Contents

As the title says, I've created a class that inherits from tkinter's LabelFrame (BatteryCapacityLFrame), and I'm trying to initialize it such that it will display like a LabelFrame. However, when I run the code the Battery tab doesn't display a LabelFrame. How do I fix this (what I presume to be) inheritance issue?
Code:
import tkinter as tk
from tkinter import ttk
from tkinter import *
class PanelManager(tk.Tk):
def __init__ (self):
#initializing tkinter within initialization function
tk.Tk.__init__(self)
self.title("Combat Robotics Calculator")
self.panel_manager = ttk.Notebook(self)
self.add_battery_tab("Batteries")
self.add_tab("Pulleys")
self.add_tab("Drive System")
self.add_tab("Weapon System")
def add_tab(self, title):
tab_frame = Frame(self.panel_manager)
self.panel_manager.add(tab_frame, text = title)
self.panel_manager.pack()
def add_battery_tab(self, title):
battery_tab = BatteryTab(self)
self.panel_manager.add(battery_tab, text = title)
self.panel_manager.pack()
def run(self):
self.mainloop()
class BatteryTab(tk.Frame):
def __init__ (self, master):
tk.Frame.__init__(self, master)
#Capacity Calculator
capacity_calcf = BatteryCapacityLFrame(self)
capacity_calcf.grid(column = 1, row = 1, sticky = "news")
#I'm trying to initialize the class here, not sure what's going wrong
class BatteryCapacityLFrame(tk.LabelFrame):
def __init__ (self, master):
tk.LabelFrame.__init__(self, master)
self.config(text = "Battery Capacity Calculator")
root_window = PanelManager()
root_window.run()
Thank you all so much for your time!
You need to add something to the labelframe for it to show up.
class BatteryCapacityLFrame(tk.LabelFrame):
def __init__(self, master):
tk.LabelFrame.__init__(self, master)
self.config(text="Battery Capacity Calculator")
tk.Label(self, text='Test message.').grid()

Tkinter passing callback function result to another class

I wonder how to pass return value from one class to another class in tkinter.
In my program I have DataChosenForm class where I want to choose option in Combobox and pass this result to another class ReturnData to set a variable in a Label.
import tkinter as tk
from tkinter import ttk
class DataChosenForm(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
chosen = tk.LabelFrame(self, text="wybór")
chosen.grid(row=0)
self.combo = ttk.Combobox(chosen)
self.combo['values'] = ('wizz', 'ryanair', 'lot')
self.combo.grid(row=0, column=2, padx=80, pady=10)
self.combo.bind("<<ComboboxSelected>>", self.callback)
def callback(self, event=None):
if event.widget.get() == 'wizz':
print('wizz')
return 'wizz'
elif event.widget.get() == 'ryanair':
print('ryanair')
return 'ryanair'
elif event.widget.get() == 'lot':
print('lot')
return 'lot'
class ReturnData(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
var = tk.StringVar()
message_box = tk.LabelFrame(self, text="wynik")
message_box.grid(row=1)
mb = tk.Label(message_box, textvariable=var,anchor='nw')
mb.pack(padx=120, pady=30)
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("program do wyszukiwania cen lotów")
self.geometry('300x200')
self.resizable(width=False, height=False)
DataChosenForm(self).grid(row=0, column=0)
ReturnData(self).grid(row=1)
if __name__ == "__main__":
app = Application()
app.mainloop()
You could first display the combobox DataChosenForm(self).grid(row=0, column=0) without calling the ReturnData in the Application class.
Then, in the callback() method collect the choice choice = event.widget.get() and pass it to ReturnData. This would mean, however, that the LabelFrame is displayed only after a choice is made.
import tkinter as tk
from tkinter import ttk
class DataChosenForm(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
chosen = tk.LabelFrame(self, text="wybór")
chosen.grid(row=0)
self.combo = ttk.Combobox(chosen)
self.combo['values'] = ('wizz', 'ryanair', 'lot')
self.combo.grid(row=0, column=2, padx=80, pady=10)
self.combo.bind("<<ComboboxSelected>>", self.callback)
def callback(self, event=None):
choice = event.widget.get()
print(choice)
ReturnData(self, choice).grid(row=1)
class ReturnData(tk.Frame):
def __init__(self, parent, choice):
super().__init__(parent)
message_box = tk.LabelFrame(self, text="wynik")
message_box.grid(row=1)
mb = tk.Label(message_box, text=choice, anchor='nw')
mb.pack(padx=120, pady=30)
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("program do wyszukiwania cen lotów")
self.geometry('300x200')
self.resizable(width=False, height=False)
DataChosenForm(self).grid(row=0, column=0)
if __name__ == "__main__":
app = Application()
app.mainloop()
I think #Julia has the basically the right architecture for your tkinter application, but her answer could be improved by using a tkinter Variable — because all widgets associated with one will automatically update their displayed value whenever the Variable is changed (by one of them or something else).
Here's a little documentation on Variable Classes. Note that since ttk.Combobox with have all the methods of a tk.Entry widgets, here's a bit of documentation about them (which also happens to illustrate the "pattern" of using of a StringVar in conjunction with one so it also applies to a ttk.Comboboxs).
Generally, you can tell a widget to use a tkinter Variable by specifying an instance of one as the option textvariable= keyword argument when the widget is created. You can also set the option using the partial dictionary interface most widgets support, so assignments like widget['textvariable'] = variable are another way to make use of them — the code below makes use of both of these ways.
Here's Julia's code modified to use a tk.StringVar. Note the Combobox doesn't need a callback function to bind the <<ComboboxSelected>> event to it, so all that complexity has been eliminated.
import tkinter as tk
from tkinter import ttk
class DataChosenForm(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
choice = tk.LabelFrame(self, text="wybór")
choice.grid(row=0)
self.combo = ttk.Combobox(choice)
self.combo['textvariable'] = parent.var # Use shared variable.
self.combo['values'] = ('wizzair', 'ryanair', 'lot')
self.combo.grid(row=0, column=2, padx=80, pady=10)
class ReturnData(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
message_box = tk.LabelFrame(self, text="wynik")
message_box.grid(row=1)
mb = tk.Label(message_box, textvariable=parent.var, # Use shared variable.
anchor='nw', width=20)
mb.pack(padx=120, pady=30)
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("program do wyszukiwania cen lotów")
self.geometry('300x200')
self.resizable(width=False, height=False)
self.var = tk.StringVar(value='Dokonać wyboru') # Create shared variable.
DataChosenForm(self).grid(row=0, column=0)
ReturnData(self).grid(row=1)
if __name__ == "__main__":
app = Application()
app.mainloop()
You can just pass other class or it's field to __init__ of the DataChosenForm, and to callback function from there, where then you can change class/field directly. Here's what I mean, but I use TreeView:
import tkinter as tk
from tkinter import ttk
class ConnectedClass:
def __init__(self):
self.received = "will be changed"
class TreeViewWrapper(ttk.Treeview):
def __init__(self, master, connected_class, **kw):
super().__init__(master, **kw)
parents = []
for i in range(10):
parent = self.insert("", "end", text="Item %s" % i, tags=str(i))
for i in range(3):
self.insert(parent, "end", text="Item %s" % i, tags=str(i))
self.bind("<Control-r>", lambda e: self.pass_to_other(e, connected_class))
def pass_to_other(self, _, connected_class):
items = self.selection()
connected_class.received = items
class App:
def __init__(self):
self.root = tk.Tk()
self.con_class = ConnectedClass()
self.tree = TreeViewWrapper(self.root,self.con_class)
self.tree.pack()
self.root.bind("<Control-p>",lambda e:print(self.con_class.received))
self.root.mainloop()
if __name__ == "__main__":
app = App()

QAction not triggered for added QMenu

The issue that I'm facing is when I want to split the functionality of the menubar into multiple files (classes), each of them specific for handling options (File/Help/Edit and so on).
In the Main UI class I have:
class MyFrame(QMainWindow):
def __init__(self):
super().__init__()
self.menu_bar = self.menuBar()
# Create menu
self.add_menu()
def add_menu(self):
help_menu = MenuHelp(self)
def getMenuBar(self):
return self.menu_bar
In the MenuHelp (class):
class MenuHelp(QMenu):
def __init__(self, parrent_widget):
super(MenuHelp, self).__init__()
self.menu_variable = parrent_widget.getMenuBar().addMenu('Help')
about_action = self.menu_variable.addAction('About')
about_action.setStatusTip('About')
about_action.triggered.connect(self.handle_trigger)
def handle_trigger(self):
print('Im here')
The menubar is correctly shown, but handle_trigger method is never called, any ideas on what am I doing wrong?
You must pass a parent to your QMenu. You must change:
class MenuHelp(QMenu):
def __init__(self, parrent_widget):
super(MenuHelp, self).__init__()
to:
class MenuHelp(QMenu):
def __init__(self, parrent_widget):
super(MenuHelp, self).__init__(parrent_widget)

Categories

Resources