I am having the following problem. I am making a tkinter GUI, and I need to access an object that is inside a canvas, inside a Canvas.create_window widget, packed with some other objects. For example:
import Tkinter as tk
class Demo:
def __init__(self, master):
self.canvas = tk.Canvas()
self.canvas.pack(fill="both", expand=True)
f = tk.Frame(self.canvas)
f.pack()
self.container = self.canvas.create_window(50,50, window = f)
l = tk.Label(f, text='abc')
e = tk.Entry(f, width = 5)
l.pack()
e.pack()
if __name__ == '__main__':
root = tk.Tk()
app = Demo(root)
root.mainloop()
I am trying to edit the text of the l label (which is currently 'abc'), when some other event is triggered. I suppose I need to use canvas.itemconfig, but I can't find a way to pass to this function the correct reference to the label. Any Ideas?
Thank you
You don't need to use itemconfigure -- that is only for configuring canvas items. Your label is not a canvas item, it's just a normal tkinter widget that you access like any other widget. Save a reference, and then use the reference to call a method.
For example:
class Demo:
def __init__(...):
...
self.l = tk.Label(f, text='abc')
...
def some_event_handler(event):
self.l.configure(text="xyz")
Related
So I was given some example code of how to essentially control multiple Listboxes within one function, it seems to work during the example code but after implementing i am struggling to see what I've missed out.
Example code:
import tkinter as tk
class MultiListbox(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
for i in range(5):
lb = tk.Listbox(self, height=10, exportselection=False)
lb.pack(side="left", fill="y")
for j in range(10):
lb.insert("end", f"Listbox {i+1} value {j+1}")
lb.bind("<Double-1>", self.removeSeq)
def removeSeq(self, event):
lb = event.widget
curselection = lb.curselection()
index = curselection[0] if curselection else None
for listbox in self.winfo_children():
listbox.delete(index)
root = tk.Tk()
mlb = MultiListbox(root)
mlb.pack(side="top", fill="both", expand=True)
root.mainloop()
Where I am trying to implement logic:
Imports
import tkinter as tk,tkinter.ttk as ttk, pyautogui, numpy, easygui, cv2, os, time, _thread, re, math, subprocess
from tkinter import BOTH, END, LEFT
pyautogui.FAILSAFE = True
Class
class Acgm003App:
def __init__(self, master=None):
for i in range(5):
self.lb = tk.Listbox(self.modeSelect)
self.lb.configure(background='#2f2a2d', exportselection='false', font='{Arial} 12 {}', foreground='#feffff', height='23')
self.lb.configure(relief='flat', width='12')
self.lb.pack(side='left')
for j in range(10):
self.lb.insert("end", f"Listbox {i+1},{j+1}")
self.lb.bind("<Double-1>", self.getIndexLB)
Function
def getIndexLB(self, event):
print('hello')
self.lb = event.widget
curselection = self.lb.curselection()
index = curselection[0] if curselection else None
for listbox in self.lb.winfo_children():
print(index)
listbox.delete(index)
pass
I am just not getting anything back at all, I put print('hello') there just to make sure it was binded correctly, it prints just fine, but no result.
The code is intended to delete listbox items in other listboxes by taking the corresponding index of the curselection, sort of a work around to a tk.treeview.
Let me know if you can help!
I didn't test it but I think you use it with wrong object.
Original code use
lb = tk.Listbox(self, ...)
to add listbox to self and later it searchs children in self
for listbox in self.winfo_children():
You add listbox to self.modeSelect
tk.Listbox(self.modeSelect, ...)
so you should search children in self.modeSelect
for listbox in self.modeSelect.winfo_children():
But this method can make problem if you add other widgets in self.modeSelect because it will try to use .delete(index) also on other widgets. And then you should check if you get tk.Listbox
for child in self.modeSelect.winfo_children():
if isinstance(child, tk.Listbox):
child.delete(index)
EDIT:
import tkinter as tk
class Acgm003App:
def __init__(self, master=None):
self.modeSelect = tk.Frame(master)
self.modeSelect.pack()
# other child in `self.modeSelect`
self.label = tk.Label(self.modeSelect, text="Hello World")
self.label.pack(side='top')
for i in range(5):
self.lb = tk.Listbox(self.modeSelect)
self.lb.pack(side='left')
for j in range(10):
self.lb.insert("end", f"Listbox {i+1},{j+1}")
self.lb.bind("<Double-1>", self.getIndexLB)
def getIndexLB(self, event):
self.lb = event.widget
curselection = self.lb.curselection()
index = curselection[0] if curselection else None
for child in self.modeSelect.winfo_children():
# check if child is `tk.Listbox` or other widget
if isinstance(child, tk.Listbox):
child.delete(index)
root = tk.Tk()
app = Acgm003App(root)
root.mainloop()
This code of a minimal example is functional:
from tkinter import *
textbox =str()
def openpopup():
popupwindow = Toplevel(root)
global textbox
textbox = Text(popupwindow, height=20, width=40,font="Courier")
textbox.pack()
textbox.delete(1.0, END)
textbox.insert(1.0,"start")
Button(popupwindow, text="do it", command=changepopup).pack()
def changepopup():
global textbox
textbox.delete(1.0, END)
textbox.insert(1.0,"changed text")
root = Tk()
Button(root, text="open", command=openpopup).pack()
mainloop()
my goal is to open a popup dynamically on userinput and then have various gui elements interact.
I managed to do this using global. I've read using global variables should be avoided.
What is the recommended way of going about this? Can I avoid using globals? I am aware that this is an issue of scoping, this is how I came up with this "solution". I am not so familiar with OOP but I have a hunch this might be a solution here.
Question: Can I avoid using globals?
Yes, consider this OOP solution without any global.
Reference:
- 9.5. Inheritance
- class-and-instance-variables
- Dialog Windows
import tkinter as tk
from tkinter import tkSimpleDialog
class Popup(tkSimpleDialog.Dialog):
# def buttonbox(self):
# override if you don't want the standard buttons
def body(self, master):
self.text_content = ''
self.text = tk.Text(self)
self.text.pack()
return self.text # initial focus
def apply(self):
self.text_content = self.text.get(1.0, tk.END)
class App(tk.Tk):
def __init__(self):
super().__init__()
btn = tk.Button(self, text='Popup', command=self.on_popup)
btn.pack()
def on_popup(self):
# The widget `Popup(Dialog)`, waits to be destroyed.
popup = Popup(self, title='MyPopup')
print(popup.text_content)
if __name__ == '__main__':
App().mainloop()
The object-oriented way would be to create a class representing "popup" objects. The class' initializer method, __init__(), can create the popup's widgets as well as act as a storage area for the contents of the Text widget. This avoids needing a global variable because methods of class all has an first argument usually call self the is instance of the class.
Any data needed can be stored as attributes of self and can easily be "shared" all the methods of the class.
The other primary way to avoid global variables is by explicitly passing them as arguments to other callables — like main() does in the sample code below.
Here's an example based on the code in your question:
from tkinter import *
class Popup:
def __init__(self, parent):
popup_window = Toplevel(parent)
self.textbox = Text(popup_window, height=20, width=40, font="Courier")
self.textbox.pack()
self.textbox.insert(1.0, "start")
btn_frame = Frame(popup_window)
Button(btn_frame, text="Do it", command=self.do_it).pack(side=LEFT)
Button(btn_frame, text="Close", command=popup_window.destroy).pack(side=LEFT)
btn_frame.pack()
def do_it(self):
self.clear()
self.textbox.insert(1.0, "changed text")
def clear(self):
self.textbox.delete(1.0, END)
def main():
root = Tk()
Button(root, text="Open", command=lambda: Popup(root)).pack()
root.mainloop()
if __name__ == '__main__':
main()
The global you created, textbox is unnecessary. You can simply remove it from your program. and still get the same behavior
# textbox = str()
I hope my answer was helpful.
I have ran into some trouble.
I'm quite new to OOP and working with tkinter and GUI's in general.
I have managed to find some code on the Internet and meshed it all together to create something and I'm nearly where I want to be.
So what I want is some help figuring this out.
How can I assign results of askdirectory to a variable I can use elsewhere?
# coding=utf-8
import tkinter as tk
from tkinter import font as tkfont
from tkinter import filedialog
class MainApp(tk.Tk):
....
class SelectFunction(tk.Frame):
....
class FunctionChangeName(tk.Frame):
....
a = Gui(self)
# this gets me the askdirectory but how to add it to a variable?
Above is the call to run askdirectory code, and it works, just need to find out how to save it to a variable so I can use it, I have tried to print it in several ways, but all I get is something along the lines .!frame.!functionchangename.!gui.
class SelectDir:
def __init__(self, container, title, initial):
self.master = container
self.initial = initial
self.selected = initial
self.options = {'parent': container,'title': title,'initialdir':initial,}
def show(self):
result = filedialog.askdirectory()
if result:
self.selected = result
def get(self):
return self.selected
class Gui(tk.Frame):
def __init__(self, container):
tk.Frame.__init__(self, container)
frame = tk.Frame(container)
frame.pack()
self.seldir = SelectDir(self, "Select directory", "D:\\MyPgm\\Python\\Tiles_8")
button = tk.Button(frame, text="Select directory", command=self.select_dir)
button.grid(column=0, row=0)
self.act_dir = tk.StringVar()
self.act_dir.set("D:\\MyPgm\\Python\\Tiles_8")
entry = tk.Entry(frame, textvariable=self.act_dir, width=30)
entry.grid(column=0, row=1)
def select_dir(self):
self.seldir.show()
self.act_dir.set(self.seldir.get())
# or
# result = seldir.show()
# self.act_dir.set(result)
if __name__ == "__main__":
app = MainApp()
app.mainloop()
I had an idea:
example, if you have f inside a function, you can make it global to have access as variable
def print_path():
# select working directory
global f #make f global to access the path
f = filedialog.askdirectory(parent=root, initialdir="/", title='Select Dir')
I'm making a very simple program for class that involves multiplying the number of a GUI slider by another number of another GUI slider. But, for some reason when I run the program now, I get an AttributeError saying that 'gui' object has no attribute 'slider1'. Any ideas? Here's the code:
import tkinter
import random
class gui:
def __init__(self):
self.main_window = tkinter.Tk()
#widgets
self.__canvas = tkinter.Canvas(self.main_window,bg='white',width=300,height=10)
self.label = tkinter.Label(self.main_window,text=('Product:',0))
self.slider1 = tkinter.Scale(self.main_window,from_=0, to=12)
self.slider2 = tkinter.Scale(self.main_window,from_=0, to=12)
#packs
self.__canvas.pack()
self.label.pack(side='top')
self.slider1.pack(side='left')
self.slider2.pack(side='right')
self.button = tkinter.Button(self.main_window,text='Click to multiply',command=self.multiply())
self.button.pack(side='bottom')
tkinter.mainloop()
def multiply(self):
x = int(self.slider1.get())
y = int(self.slider2.get())
num = x*y
self.label.config(text=('Product:',num))
gui()
There is a few syntax error in the program, I commented those. As well as you should put orientations on the scales. Here is the code.
import tkinter as tk
class gui:
def __init__(self):
self.root = tk.Tk()
# the widgets
self.button = tk.Button(self.root, text="Multiply!", command=self.multiply)
# you need no '()' for the function when inputing it in tkinter.
self.label = tk.Label(self.root, text="Product: 0") # the '0 must be a string
self.sliderX = tk.Scale(self.root, from_=0, to=12, orient=tk.HORIZONTAL)
self.sliderY = tk.Scale(self.root, from_=0, to=12, orient=tk.VERTICAL)
# add an orient to the scales.
# now pack the widgets.
self.button.pack()
self.label.pack()
self.sliderX.pack()
self.sliderY.pack()
def multiply(self):
x = int(self.sliderX.get())
y = int(self.sliderY.get())
num = str(x * y) # need to turn the int to a string.
self.label.config(text="Product: "+num)
app = gui()
app.root.mainloop()
The reason it isn't working for you is because there is no instance of the program. This is what I do at the very end. Python's garbage collecting collects the instance made with gui() and so Tkinter can't reference an instance of the class.
Is it possible to bind all widgets to one command, with a single line? It would be nice if I could type in one line as opposed to doing each widget individually.
You would use the bind_all method on the root window. This will then apply to all widgets (unless you remove the bindtag "all" from some widgets). Note that these bindings fire last, so you can still override the application-wide binding on specific widgets if you wish.
Here's a contrived example:
import Tkinter as tk
class App:
def __init__(self):
root = tk.Tk()
root.bind_all("<1>", self.woot)
label1 = tk.Label(text="Label 1", name="label1")
label2 = tk.Label(text="Label 2", name="label2")
entry1 = tk.Entry(name="entry1")
entry2 = tk.Entry(name="entry2")
label1.pack()
label2.pack()
entry1.pack()
entry2.pack()
root.mainloop()
def woot(self, event):
print "woot!", event.widget
app=App()
You might also be interested in my answer to the question How to bind self events in Tkinter Text widget after it will binded by Text widget? where I talk a little more about bindtags.
If you have a list that contains all your widgets, you could iterate over them and assign the events.
You mean something like this code which handles all mouse events handled with single function?
from Tkinter import *
class ButtonHandler:
def __init__(self):
self.root = Tk()
self.root.geometry('600x500+200+200')
self.mousedown = False
self.label = Label(self.root, text=str(self.mousedown))
self.can = Canvas(self.root, width='500', height='400', bg='white')
self.can.bind("<Motion>",lambda x:self.handler(x,'motion'))
self.can.bind("<Button-1>",lambda x:self.handler(x,'press'))
self.can.bind("<ButtonRelease-1>",lambda x:self.handler(x,'release'))
self.label.pack()
self.can.pack()
self.root.mainloop()
def handler(self,event,button_event):
print('Handler %s' % button_event)
if button_event == 'press':
self.mousedown = True
elif button_event == 'release':
self.mousedown = False
elif button_event == 'motion':
if self.mousedown:
r = 5
self.can.create_oval(event.x-r, event.y-r, event.x+r, event.y+r, fill="orange")
self.label.config(text=str(self.mousedown))
button_event = ButtonHandler()
You could also just define a function that calls on all your widgets, and call that function. Or better yet create a class that call on your widgets in init and import the class...