Related
Minimal reproducable Example
from tkinter import *
def test(event):
print(event.widget)
window = Tk()
window.geometry("600x600")
window.bind("<Motion>", test)
frame = Frame(window, bg="red", width=200, height=200)
frame.pack()
frame.bind("<Motion>", test)
window.mainloop()
I want to call the function "test" from different widgets when i move over them. Instead of that, when i hover over the frame, both the window and the frame print that im hovering over the frame, which is not the behaviour i need. Can someone help me achieve the right behaviour?
I think your confusion is in the moment of interpreting when the mouse pointer entered the widget or left the widget. Instead of Motion let's use for example Leave and Enter events to better understand what happens.
I have taken the liberty of including some labels that show which widget we enter and left at each moment.
from tkinter import *
def test_enter(event):
enter.set(event.widget)
def test_left(event):
left.set(event.widget)
window = Tk()
window.geometry("300x300")
window.bind("<Enter>", test_enter)
window.bind("<Leave>", test_left)
frame = Frame(window, bg="red", width=200, height=200)
frame.pack()
frame.bind("<Enter>", test_enter)
frame.bind("<Leave>", test_left)
label1 = Label(frame, text="Enter")
label1.place(relx=0.1, rely=0.4, x= 0.2, y=0.2)
enter = StringVar()
label_enter = Label(frame, textvariable=enter)
label_enter.place(relx=0.3, rely=0.4, x= 0.6, y=0.2)
label2 = Label(frame, text="Leave")
label2.place(relx=0.1, rely=0.6, x= 0.2, y=0.2)
left = StringVar()
label_left = Label(frame, textvariable=left)
label_left.place(relx=0.3, rely=0.6, x= 0.6, y=0.2)
window.mainloop()
Remember that the Frame is inside the Window. I mean that when you enter the Window you have not left root Window, you will continue to be in Window. Or if you enter inside a Label you have not left the Frame.
You could use lambda to pass the widget when the function is run
def test(event, widget):
print(widget)
window = Tk()
window.geometry("600x600")
window.bind("<Motion>", test)
frame = Frame(window, bg="red", width=200, height=200)
frame.pack()
frame.bind("<Motion>", lambda: test(frame))
window.mainloop()
You just need to change two lines
def test(event, widget):
print(widget)
and
frame.bind("<Motion>", lambda: test(frame))
I am making a random generator for my friends and I'm stuck trying to make a scroll down option. So if you generate more the window can show, a scroll down window should be possible. But I can't seem to get any to work. I've tried many online tutorials.
And my second issue with my code is that I can't clear the generated labels from the window. I got it working that it expands the window.
from cProfile import label
from pickle import FRAME
import random
import tkinter as tk
from tkinter import BOTH, DISABLED, LEFT, RIGHT, VERTICAL, Y, Frame, Label, filedialog, Text
import os
from tkinter import ttk
from tkinter.font import NORMAL
from tkinter.messagebox import YES
root = tk.Tk()
root.title('guesser')
#Pelin arvonta ohjelma !
def delete():
for child in root.children.values():
info = child.grid_info()
if info['column'] == 0:
child.grid_forget()
def arvonta():
global label
list1 = []
lista = ["Valorant","Rainbow","Vampire: The masquerade","Playerunknown's battlegrounds","Fortnite","Left 4 Dead 2","Counter strike Global offensive","Realm roayale","Black ops 1 zombies/multiplayer","Black ops 2 zombies/multiplayer","Black ops 3 zombies/multiplayer"]
numero = random.randint(0, 10)
hahmo = (lista[numero])
list1.append(hahmo)
for app in list1:
label = tk.Label(frame, text=app, bg="red",font=('Helvetica',20))
label.pack()
def valorant():
list2 = []
lista2 = ["Brimstone","Viper","Omen","Killjoy","Cypher","Sova","Sage","phoenix","Jett","Reyna","Raze","Raze","Breach","Skye","Yoru","Astra","Kay/o","Chamber","Neon","Fade"]
numero = random.randint(0, 19)
randomValorantagent=(lista2[numero])
list2.append(randomValorantagent)
for app in list2:
label = tk.Label(frame, text=app, bg="red",font=('Helvetica',20))
label.pack()
def quitter():
quit()
canvas = tk.Canvas(root,height=700,width=700,bg="#263D42")
canvas.pack(side=LEFT,fill=BOTH,expand=1)
frame = tk.Frame(root,bg="green")
frame.place(relwidth=0.8,relheight=0.8,relx=0.1,rely=0.1)
frame.pack(fill=BOTH,expand=1)
my_scrollbar = ttk.Scrollbar(frame, orient=VERTICAL, command=canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
# Configure The Canvas
canvas.configure(yscrollcommand=my_scrollbar.set)
canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion = canvas.bbox("all")))
# Create ANOTHER Frame INSIDE the Canvas
second_frame = Frame(canvas)
# Add that New frame To a Window In The Canvas
canvas.create_window((0,0), window=second_frame, anchor="nw")
#rlls the game
openfile = tk.Button(second_frame,text="Roll a game",padx=10,pady=5,fg="white",bg="#263D42", command=arvonta)
openfile.pack()
#rolls a valorant agent
valorantA = tk.Button(second_frame,text='Roll valorant agent',padx=10,pady=5,fg="white",bg="#263D42",command=valorant)
valorantA.pack()
# stops program
stop = tk.Button(second_frame,text="Quit",padx=10,pady=5,fg="white",bg="#263D42",command=quitter)
stop.pack()
# deletes all info from screen.
deletor = tk.Button(second_frame,text="delete info",padx=10,pady=5,fg="white",bg="#263D42",command=delete)
deletor.pack()
root.mainloop()```
The following does most of what you want. I wrote it some time ago to test Scrollbars because they are wonky IMHO
from tkinter import *
from functools import partial
class ButtonsTest:
def __init__(self):
self.top = Tk()
self.top.title("Click a button to remove")
self.top.geometry("425x200+50+50")
Label(self.top, text=" Click a button to remove it ",
bg="lightyellow", font=('DejaVuSansMono', 12)
).grid(row=0, sticky="nsew")
Button(self.top, text='Exit', bg="orange", width=9,
command=self.top.quit).grid(row=1,column=0,
sticky="nsew")
self.add_scrollbar()
self.button_dic = {}
self.buttons()
self.top.mainloop()
##-------------------------------------------------------------------
def add_scrollbar(self):
self.canv = Canvas(self.top, relief=SUNKEN)
self.canv.config(width=400, height=200)
self.top_frame = Frame(self.canv, height=100)
##---------- scrollregion has to be larger than canvas size
## otherwise it just stays in the visible canvas
self.canv.config(scrollregion=(0,0, 400, 500))
self.canv.config(highlightthickness=0)
ybar = Scrollbar(self.top, width=15, troughcolor="lightblue")
ybar.config(command=self.canv.yview)
## connect the two widgets together
self.canv.config(yscrollcommand=ybar.set)
ybar.grid(row=3, column=2, sticky="ns")
self.canv.grid(row=3, column=0)
self.canv.create_window(1,0, anchor=NW,
window=self.top_frame)
##-------------------------------------------------------------------
def buttons(self):
b_row=1
b_col=0
for but_num in range(1, 51):
## create a button and send the button's number to
## self.cb_handler when the button is pressed
b = Button(self.top_frame, text = str(but_num), width=5,
command=partial(self.cb_handler, but_num))
b.grid(row=b_row, column=b_col)
## dictionary key=button number --> button instance
self.button_dic[but_num] = b
b_col += 1
if b_col > 4:
b_col = 0
b_row += 1
##----------------------------------------------------------------
def cb_handler( self, cb_number ):
print("\ncb_handler", cb_number)
self.button_dic[cb_number].grid_forget()
##===================================================================
BT=ButtonsTest()
I'm creating a GUI in python using tkinter and am having trouble when running it. I have an entry box widget, a radiobutton widget, and a button widget. When I press the button, what I want is the user to type a number into the entry box and select an option from the list of radiobuttons. When the user presses the button, I'd like the values to be retrieved and displayed in the other frame for testing. What I get instead is when the button gets pressed, I get the error 'NoneType' object has no attribute 'get'. The error is referring to the value inside of the entry box: self.tune_entry
The code I have is as follows:
SA_main.py
import os
import tkinter as tk
from tkinter import ttk
from tkinter import font
import SA_gui
def main():
x_vals = [0,1,2,3,4]
y_vals = [0,1,2,3,4]
root = SA_gui.tk.Tk()
UI = SA_gui.Window(root, x_vals, y_vals)
root.mainloop()
if __name__ == "__main__":
main()
SA_gui.py
import os
import tkinter as tk
from tkinter import ttk
from tkinter import font
# Class to define, setup, and build the GUI
class Window:
# Dimensions of the GUI
HEIGHT = 600
WIDTH = 1200
# Colors for main layout
bg_color = "#B0E0E6"
frame_color1 = "#73B1B7"
white_color = "#FFFFFF"
def __init__(self, master, x_vals, y_vals):
# Take in the lists of files for later use
self.x_vals = x_vals
self.y_vals = y_vals
#--------------------------------------------------------------
# Define and create the window
self.master = master
master.title("Signal Analysis")
master.geometry("{}x{}".format(Window.WIDTH, Window.HEIGHT))
# Create and place the background frame
self.bg_frame = tk.Frame(self.master, bg=Window.bg_color, bd=5)
self.bg_frame.place(relwidth=1, relheight=1)
# Create the main title
self.main_title = tk.Label(self.bg_frame, text="Software Defined Radio Signal Analysis",
bg=Window.bg_color, font=("Courier", 14))
self.main_title.pack(side="top")
#--------------------------------------------------------------
# Create and place the frame for tuning
self.tune_frame = tk.Frame(self.bg_frame, bg=Window.frame_color1, bd=4)
self.tune_frame.place(relx=0.05, rely=0.1, relwidth=0.2428, relheight=0.8)
# Create and place the title for the tuning frame
self.tune_title = tk.Label(self.tune_frame, text="Tune", bg=Window.frame_color1, font=
("Courier", 11))
self.tune_title.place(relwidth=1, anchor="nw")
# Create and place the contents of the tuning frame
self.tune_cont = tk.Frame(self.tune_frame, bg=Window.white_color, bd=4)
self.tune_cont.place(relx=0.05, rely=0.05, relwidth=0.9, relheight=0.925)
#Label for frequency entry
self.tune_label = tk.Label(self.tune_cont, text='Enter carrier frequency: (kHz)',
bg=Window.white_color)
self.tune_label.place(relx=0.025, rely=0)
#Entry Box for frequency entry
self.tune_entry = tk.Entry(self.tune_cont)
self.tune_entry.place(relx=0.025, rely=0.075, relwidth=0.95, relheight=0.05)
#Label for divider
self.tune_div = ttk.Separator(self.tune_cont, orient="horizontal")
self.tune_div.place(rely=0.175, relwidth=1)
#Label for display mode
self.disp_label = tk.Label(self.tune_cont, text='Select Display:', bg=Window.white_color)
self.disp_label.place(relx=0.025, rely=0.2)
#Variable for radiobuttons
self.var = tk.IntVar(self.tune_cont).set("1")
#Radio Button for Spectral Analysis
self.SA_select = tk.Radiobutton(self.tune_cont, text="Spectral
Analysis",bg=Window.white_color, padx=20, variable=self.var, value=1)
self.SA_select.place(relx=0.025, rely=0.275)
#Radio Button for Option 2
self.opt2_select = tk.Radiobutton(self.tune_cont, text="Option 2",bg=Window.white_color,
padx=20, variable=self.var, value=2)
self.opt2_select.place(relx=0.025, rely=0.35)
#Radio Button for Option 3
self.opt3_select = tk.Radiobutton(self.tune_cont, text="Option 3",bg=Window.white_color,
padx=20, variable=self.var, value=3)
self.opt3_select.place(relx=0.025, rely=0.425)
#Button for selection
self.tune_button = ttk.Button(self.tune_cont, text="Enter", command=lambda:
self.print_selected(self.var.get(), self.tune_entry.get()))
self.tune_button.place(relx= 0.775, rely=0.9, relwidth=0.2, relheight=0.075)
#-----------------------------------------------------------------
# Create and place the frame for the plot
self.plot_frame = tk.Frame(self.bg_frame, bg=Window.frame_color1, bd=4)
self.plot_frame.place(relx=0.3428, rely=0.1, relwidth=0.6071, relheight=0.8)
# Create and place the title for the plot frame
self.plot_title = tk.Label(self.plot_frame, text="Plot", bg=Window.frame_color1, font=
("Courier", 11))
self.plot_title.place(relwidth=1, anchor="nw")
# Create and place the contents of the plot frame
self.plot_cont = tk.Frame(self.plot_frame, bg=Window.white_color, bd=4)
self.plot_cont.place(relx=0.025, rely=0.05, relwidth=0.95, relheight=0.925)
def print_selected(self, disp, freq):
if disp == 1:
disp_mode = "Spectral Analysis"
elif disp == 2:
disp_mode = "Option 2"
else:
disp_mode = "Option 3"
#Label for this test
self.prnt_label = tk.Label(self.plot_cont, text="Display: " + disp_mode + ", Center Freq: " +
freq, bg=Window.white_color)
self.prnt_label.place(relx=0.025, rely=0.2)
Any help to resolve this issue is greatly appreciated!
Consider this code:
self.var = tk.IntVar(self.tune_cont).set("1")
Anytime you do x=y().z() python assigns the return value of z() to x. Thus, in your code you're assiging the result of .set("1") to self.var. The set method is returning None so self.var is None. Thus, when you later try to call self.var.get() it's the same as doing None.get().
If you want to initialize a variable at the time of creation, there is no need to call set. Also, while it works to pass a string, if you're setting an IntVar you really ought to be setting it to an integer.
self.var = tk.IntVar(value=1)
I am having button and on pressing it I want to create new Button and new Label.
Label must have random color and must change it on pressing this button to another random color.
My code even can not add buttons correctly, there is problems with placing new(sizes are strange).
How can I improve this? And how can I later create func for new buttons which will change their label's colours, cause I dont have label's names.
import random
from tkinter import *
def color(*args):
pass
def dump( *args):
global count
Butt = Button(root, text="color ", command=color)
Butt.config(width=int(root.winfo_width() / 10), height=int(root.winfo_height() / 10))
Butt.grid(row=0, column=count)
Txt = Label(root, text="Color", bg="#" + ("%06x" % random.randint(0, 16777215)))
Txt.config(width=int(root.winfo_width() / 10), height=int(root.winfo_height() / 10))
Txt.grid(row=1, column=count)
count+=1
root.mainloop()
count=2
TKroot = Tk()
TKroot.title("Hello")
root = Frame(TKroot)
root.place(relx=0, rely=0, relheight=1, relwidth=1)
root.columnconfigure(0, weight=10)
root.columnconfigure(1, weight=10)
root.rowconfigure(0, weight=10)
root.rowconfigure(1, weight=10)
Butt = Button(root, text="Butt ON")
Butt.bind('<Button-1>', dump)
Butt.config(width=int(root.winfo_width() / 10), height=int(root.winfo_height() / 10))
Butt.grid(row=0, column=0)
Exit = Button(root, text="Quit!", command=root.quit)
Exit.config(width=int(root.winfo_width() / 10), height=int(root.winfo_height() / 10))
Exit.grid(row=0, column=1)
Txt = Label(root, text="This is a label", bg="PeachPuff")
Txt.grid(row=1, column=1, columnspan=1)
TKroot.mainloop()
print("Done")
I see a few issues with your code.
1st is you are using place for your frame.
This is going to cause issues when adding new buttons as it will not allow the window to resize correctly with the new layout.
2nd is how you are writing your code. You name your frame root and use the quit method on the frame and not on your actually root window. The way you are writing things makes it harder to follow so consider following PEP8 guidelines when writing your code.
3rd you are trying to apply mainloop to your frame in the dump function. You only ever need 1 instance of mainloop and this applies to the actual root window (Tk()).
To address your question on how to change the label color later on I would use a list to store your buttons and labels. This way we can reference their index values and apply your random color code to the labels on button click.
I have re-written most of your code to follow PEP8 and done some general clean up.
Let me know if you have any questions.
import tkinter as tk
import random
def color(ndex):
button_label_list[ndex][1].config(bg="#%06x" % random.randint(0, 16777215))
def dump():
global count, button_label_list
button_label_list.append([tk.Button(frame, text="color", command=lambda x=count: color(x)),
tk.Label(frame, text="Color", bg="#" + ("%06x" % random.randint(0, 16777215)))])
button_label_list[-1][0].grid(row=0, column=count, sticky='nsew')
button_label_list[-1][1].grid(row=1, column=count, sticky='nsew')
frame.columnconfigure(count, weight=1)
count += 1
root = tk.Tk()
count = 0
button_label_list = []
root.title("Hello")
root.rowconfigure(1, weight=1)
root.columnconfigure(2, weight=1)
frame = tk.Frame(root)
frame.rowconfigure(1, weight=1)
frame.grid(row=0, column=2, sticky='nsew', rowspan=2)
tk.Button(root, text="butt ON", command=dump).grid(row=0, column=0, sticky='nsew')
tk.Button(root, text="Quit!", command=root.quit).grid(row=0, column=1, sticky='nsew')
tk.Label(root, text="This is a label", bg="PeachPuff").grid(row=1, column=1, columnspan=1, sticky='nsew')
root.mainloop()
Results:
A window that can add new buttons and be able to change colors on each label. The main 2 buttons the window starts with are static in that they cannot be pushed out of the window like in you code example and will remain on the left anchored in place.
below an object oriented version.
Every time you press on Color button, you create a new label and a new button
and put label reference in a dictionary.
The color of the label is randomly generate.
After creation if we click on a new button we change the relative label color.
The coolest part of the script is:
command=lambda which=self.count: self.change_color(which)
lambda funcion it's used to keep a reference to the button and label just
create when we call the change_color function.
import tkinter as tk
import random
class App(tk.Frame):
def __init__(self,):
super().__init__()
self.master.title("Hello World")
self.count = 0
self.labels = {}
self.init_ui()
def init_ui(self):
self.f = tk.Frame()
w = tk.Frame()
tk.Button(w, text="Color", command=self.callback).pack()
tk.Button(w, text="Close", command=self.on_close).pack()
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=0)
self.f.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
def callback(self):
text_label = "I'm the {} label".format(self.count)
text_button = "I'm the {} button".format(self.count)
color = "#" + ("%06x" % random.randint(0, 16777215))
obj = tk.Label(self.f, text=text_label, bg=color)
obj.pack()
self.labels[self.count]=obj
tk.Button(self.f,
text=text_button,
command=lambda which=self.count: self.change_color(which)).pack()
self.count +=1
def change_color(self,which):
color = "#" + ("%06x" % random.randint(0, 16777215))
self.labels[which].config(bg=color)
def on_close(self):
self.master.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()
I wrote a small farad converter to learn GUI programming. It works great, looks fine-ish. The only problem is I can't seem to figure out how to control this strange highlighting that comes up on my ttk.Combobox selections. I did use a ttk.Style(), but it only changed the colors of the ttk.Combobox background, entries, etc. I also tried changing openbox/gtk themes.
I'm talking about what's seen there on the text "microfarads (uF)".
It'd be fine, if it highlighted the entire box; but I'd rather have it gone completely.
How can I manipulate a ttk.Combobox's selection highlight?
# what the farad?
# thomas kirkpatrick (jtkiv)
from tkinter import *
from tkinter import ttk
# ze la programma.
def conversion(*args):
# this is the numerical value
inV = float(inValue.get())
# these two are the unit (farads, microfarads, etc.) values
inU = inUnitsValue.current()
outU = outUnitsValue.current()
# "mltplr" is multiplied times inValue (inV)
if inU == outU:
mltplr = 1
else:
mltplr = 10**((outU - inU)*3)
outValue.set(inV*mltplr)
# start of GUI code
root = Tk()
root.title("What the Farad?")
# frame
mainFrame = ttk.Frame(root, width="364", padding="4 4 8 8")
mainFrame.grid(column=0, row=0)
# input entry
inValue = StringVar()
inValueEntry = ttk.Entry(mainFrame, width="20", justify="right", textvariable=inValue)
inValueEntry.grid(column=1, row=1, sticky="W")
# input unit combobox
inUnitsValue = ttk.Combobox(mainFrame)
inUnitsValue['values'] = ('kilofarads (kF)', 'farads (F)', 'millifarads (mF)', 'microfarads (uF)', 'nanofarads (nF)', 'picofarads (pF)')
inUnitsValue.grid(column=2, row=1, sticky="e")
inUnitsValue.state(['readonly'])
inUnitsValue.bind('<<ComboboxSelected>>', conversion)
# result label
outValue = StringVar()
resultLabel = ttk.Label(mainFrame, textvariable=outValue)
resultLabel.grid(column=1, row=2, sticky="e")
# output unit combobox
outUnitsValue = ttk.Combobox(mainFrame)
outUnitsValue['values'] = ('kilofarads (kF)', 'farads (F)', 'millifarads (mF)', 'microfarads (uF)', 'nanofarads (nF)', 'picofarads (pF)')
outUnitsValue.grid(column=2, row=2, sticky="e")
outUnitsValue.state(['readonly'])
outUnitsValue.bind('<<ComboboxSelected>>', conversion)
# padding for widgets
for child in mainFrame.winfo_children(): child.grid_configure(padx=4, pady=4)
# focus
inValueEntry.focus()
# bind keys to convert (auto-update, no button)
root.bind('<KeyRelease>', conversion)
root.mainloop()
Could it be that with a readonly combobox the problem is not the selection but the relatively strong focus-indicator?
With this workarround you lose the ability to control your program by keyboard. To do it right you would have to change the style of the focus-highlighting.
from tkinter import *
from ttk import *
def defocus(event):
event.widget.master.focus_set()
root = Tk()
comboBox = Combobox(root, state="readonly", values=("a", "b", "c"))
comboBox.grid()
comboBox.set("a")
comboBox.bind("<FocusIn>", defocus)
mainloop()
You can use the Combobox's selection_clear() method to clear the selection whenever you want.
e.g
inUnitsValue.selection_clear()
Just refresh the selected value of the Combobox.
This will help for removing the highlight.
import tkinter.ttk
import tkinter
items = ["test1","test2","test3","test4"]
class TkCombobox(tkinter.ttk.Combobox):
def __init__(self, *arg, **kwarg):
super(TkCombobox, self).__init__(*arg, **kwarg)
self._strvar_ = tkinter.StringVar()
self._strvar_.set("")
self["textvariable"] = self._strvar_
self.bind("<<ComboboxSelected>>", self.highlight_clear)
def highlight_clear(self, event):
current = self._strvar_.get()
self.set("")
self.set(current)
master = tkinter.Tk();master.geometry("400x400")
c = TkCombobox(master, values=items, state="readonly")
c.pack()
master.mainloop()