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()
Related
I recently made 2 functions, add_scrollbar and update_scroll_region, which adds a scrollbar to a given frame and updates the scroll region when widgets in that frame change.
The frame in which I am adding a scrollbar is a notebook tab. The functions work as intended, but when I switch off a tab with the scrollbar (and only the ones with the scrollbar) and come back to it, all the widgets are gone. If I move my mouse off of the tab label, all the widgets re-appear. I am not exactly sure what it is about these 2 functions that could be causing this kind of behavior. I have provided a simplified example below. In this example, TAB1 has the scrollbar, and TAB2 does not. You will notice that switching from TAB2 to TAB1 hides the button in TAB1 until the mouse is moved.
from tkinter import *
from tkinter import ttk
root = Tk()
root.geometry("1200x1200")
def add_scrollbar(outer_frame):
canvas = Canvas(outer_frame)
canvas.pack(side=LEFT, fill=BOTH, expand=1)
information_frame = Frame(canvas)
canvas.create_window((0, 0), window=information_frame)
scrollbar = ttk.Scrollbar(outer_frame, orient=VERTICAL, command=canvas.yview)
scrollbar.pack(side=RIGHT, fill=Y)
canvas.configure(yscrollcommand=scrollbar.set)
return information_frame, canvas
def update_scroll_region(canvas):
global root
root.update()
bbox = canvas.bbox("all")
x, y, width, height = bbox
if height < canvas.winfo_height():
bbox = x, y, width, canvas.winfo_height()
canvas.configure(scrollregion=bbox)
return
def create_example():
global root
notebook = ttk.Notebook(root, height=1200, width=1500)
notebook.pack(pady=10)
my_outer_frame_1 = Frame(root)
my_outer_frame_1.pack(fill=BOTH, expand=1)
notebook.add(my_outer_frame_1, text="TAB1")
inner_frame_1, my_canvas_1 = add_scrollbar(my_outer_frame_1)
my_outer_frame_2 = Frame(root)
my_outer_frame_2.pack(fill=BOTH, expand=1)
notebook.add(my_outer_frame_2, text="TAB2")
Label(my_outer_frame_2, text="This always shows").pack()
# ^^^ Sets up a notebook with 2 tabs
changing_frame = Frame(inner_frame_1, borderwidth=4) # this is the frame that will be changing its contents
changing_frame.pack(side=LEFT, anchor="n")
display_frame(changing_frame, my_outer_frame_1, my_canvas_1)
# this method re-displays the changing frame depending on the specified size ('big' or 'small')
root.mainloop()
return
def display_frame(frame, outer_frame, canvas, size='small'):
for widget in frame.winfo_children():
widget.destroy()
if size == 'small':
Button(frame, text="This button is gone until the mouse is moved",
command=lambda this_frame=frame: display_frame(this_frame, outer_frame, canvas, size='big')).grid(row=0,
column=0)
elif size == 'big':
Button(frame, height=5, width=5, text="Hide",
command=lambda this_frame=frame: display_frame(this_frame, outer_frame, canvas, size='small')).grid(
row=0, column=0)
for n in range(1, 100):
Label(frame, text="Other Stuff!").grid(row=n, column=0)
update_scroll_region(canvas)
return
if __name__ == '__main__':
create_example()
I am creating a game similar to bitlife in Tkinter. Although I have run into a problem. If you recall from bitlife, or any texting platform, you create text, then all the previously created text goes up. This existing text will then go into a scrollable frame (which I have already achieved). I am not asking for the straight up code, just any methods or ideas on how to make the previously created text go up. Thanks!
Not complete but I'm out of time.
Hacked together to show you how to add items to a canvas that very loosely looks like a text message application. Not pretty at all but I only had 10 mins
import tkinter as tk
class Message(tk.Frame):
def __init__(self,parent,name,text):
super().__init__(parent,width=250)
self.lblName = tk.Label(self, text=name, font=('Arial','10'))
self.lblName.grid(row=0,column=0)
self.lblText = tk.Message(self, text=text, font=('Arial','14'))
self.lblText.grid(row=1,column=1)
class MessageFrame(tk.Frame):
def __init__(self,parent):
super().__init__(parent)
# Add a canvas in that frame
self.canvas = tk.Canvas(self, bg="yellow")
self.canvas.grid(row=0, column=0, sticky="news")
# Link a scrollbar to the canvas
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
self.vsb.grid(row=0, column=1, sticky='ns')
self.canvas.configure(yscrollcommand=self.vsb.set)
# Create a frame to contain the buttons
self.frame_buttons = tk.Frame(self.canvas, bg="blue")
self.canvas.create_window((0, 0), window=self.frame_buttons, anchor='nw')
self.messages = []
self.msgItems = []
def add_message(self, sender, text):
self.messages.append({'name':sender,'text':text})
self.refresh_msg_list()
def refresh_msg_list(self):
if self.msgItems:
for item in self.msgItems:
item.destroy()
self.msgItems = []
for idx,msg in enumerate(self.messages):
newItem = Message(self.frame_buttons, msg['name'], msg['text'])
#newItem = tk.Button(self.frame_buttons, text=task['task'])
newItem.grid(row=idx,column=0,sticky='news',pady=10)
self.msgItems.append(newItem)
# Update buttons frames idle tasks to let tkinter calculate buttons sizes
self.frame_buttons.update_idletasks()
# Resize the canvas frame to fit 5 messages
first5rows_height = max([task.winfo_height() for task in self.msgItems]) * 5
item_width = max([task.winfo_width() for task in self.msgItems])
##frame_canvas.config(width=first5columns_width + vsb.winfo_width(),
## height=first5rows_height)
self.config(height=first5rows_height,width=item_width+self.vsb.winfo_width())
# Set the canvas scrolling region
self.canvas.config(scrollregion=self.canvas.bbox("all"))
def add_new_item():
pass
def add_first_item():
msg.add_message('SenderName','How are you?')
msg.add_message('SenderName','How are you?')
msg.add_message('SenderName','How are you?')
msg.add_message('SenderName','How are you?')
msg.add_message('SenderName','How are you?')
root = tk.Tk()
root.grid_rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
frame_main = tk.Frame(root, bg="gray")
frame_main.grid(sticky='news')
# Create a frame for the canvas with non-zero row&column weights
msg = MessageFrame(frame_main)
msg.grid(row=2, column=0, pady=(5, 0), sticky='nw')
msg.grid_rowconfigure(0, weight=1)
msg.grid_columnconfigure(0, weight=1)
# Set grid_propagate to False to allow 5-by-5 buttons resizing later
msg.grid_propagate(False)
root.after_idle(add_first_item)
root.after(2000,add_new_item)
root.mainloop()
I am trying to get the value of from a combobox in tkinter using python 3.6, i been looking to many tutorials but i don't see the problem yet.
every time i press the button don't show anything.
but also there is not errors.
so to clarify ... I am trying to get the value of the tk.combobox when i press ttk.Button.
thank you in advance for any ideas or comments.
this is what i have so far.
import tkinter as tk
from tkinter import ttk
def combo_box_updater():
total_location = ['linden', 'mineola', 'brooklyn']
return total_location
def start_analisys(event=None):
site = jobsite_name.get()
print(site)
# this is part of a definition that automatically will update the names in later versions
job_site = combo_box_updater()
# basic gui setup
unified = tk.Toplevel()
unified.title('Unified 1 Week Timesheet')
unified.configure(background="#00012f")
unified.geometry("650x200")
unified.resizable(width=False, height=False)
entry_width = 30
# basic frame
frame1 = tk.Frame(unified)
frame1.grid(row=0, column=0, sticky='w')
# combo box in the fourth row
jobsite_name = tk.StringVar()
combo_box = ttk.Combobox(frame1, font="none 12 bold", width=20, textvariable=jobsite_name, text="choose location")
combo_box.grid(row=0, column=1, sticky="wesn")
combo_box['values'] = [x for x in job_site]
# Left button side
ttk.Button(frame1, text='Run', command=start_analisys, ).grid(row=0, column=2, sticky='nsew', rowspan=3)
unified.mainloop()
Made three minor edits to your code: added a label to display the result, added a line to combo box setup, and changed the creation of the main window.
import tkinter as tk
from tkinter import ttk
def combo_box_updater():
total_location = ['linden', 'mineola', 'brooklyn']
return total_location
def start_analisys(event=None):
site = jobsite_name.get()
aLabel["text"] = site
print(site)
# this is part of a definition that automatically will update the names in later versions
job_site = combo_box_updater()
# basic gui setup
unified = tk.Tk()
unified.title('Unified 1 Week Timesheet')
unified.configure(background="#00012f")
unified.geometry("650x200")
unified.resizable(width=False, height=False)
entry_width = 30
# basic frame
frame1 = tk.Frame(unified)
frame1.grid(row=0, column=0, sticky='w')
# combo box in the fourth row
jobsite_name = tk.StringVar()
combo_box = ttk.Combobox(frame1, font="none 12 bold", width=20, textvariable=jobsite_name)
combo_box.grid(row=0, column=1, sticky="wesn")
combo_box['values'] = [x for x in job_site]
combo_box.current(0)
# Left button side
ttk.Button(frame1, text='Run', command=start_analisys, ).grid(row=0, column=2, sticky='nsew', rowspan=3)
# add a label
aLabel = ttk.Label(frame1, text='My Label')
# place the label
aLabel.grid(column=3, row=0)
unified.mainloop()
if __name__ == '__main__':
pass
When you add the values as an afterthought like that you need to add the corresponding commands as well. It's much better to add the values through the init method so the commands are automatically added:
jobsite_name = tk.StringVar(value="choose location")
combo_box = ttk.Combobox(frame1, textvariable=jobsite_name, values=job_site, font="none 12 bold", width=20)
I'm trying to create a custom looking dropdown in Tkinter and I decided to go with Toplevel. Everything works as expected except for the focus isn't taking place. When the window gets created, it stays on top of my other windows, but the <Enter>/<Leave> bindings aren't working until I click on the window. Here's a rebuilt example of the exact problem.
import tkinter as tk
from tkinter import ttk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.mainframe = ttk.Frame(self)
self.init_ui()
self.mainloop()
def init_ui(self):
self.mainframe.grid(row=0, column=0, sticky='nsew')
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.mainframe.rowconfigure(0, weight=1)
self.mainframe.columnconfigure(0, weight=1)
l = ttk.Label(self.mainframe, text='Test ▾')
l.config(cursor='hand', font=('Helvetica', 12, 'underline'))
l.bind('<Button-1>', self.dropdown)
l.grid(row=0, column=0, padx=50, pady=(5, 300), sticky='new')
def dropdown(self, *args):
top = tk.Toplevel(self)
top.overrideredirect(1)
top.transient(self)
def create_label(n):
nonlocal top
l = tk.Label(top, text='Test{}'.format(n))
l.config(cursor='hand', relief='ridge')
l.bind('<Enter>', enter_leave)
l.bind('<Leave>', enter_leave)
l.bind('<Button-1>', lambda _: top.destroy())
l.grid(row=n, column=0)
def enter_leave(e):
# 7 = enter
# 8 = leave
if e.type == '7':
e.widget.config(bg='grey')
else:
e.widget.config(bg='white')
# populate some labels
for x in range(9):
create_label(x)
self.update_idletasks()
top_width = top.winfo_width()
top_height = top.winfo_height()
root_x = self.winfo_x()
root_y = self.winfo_y()
top.geometry('{}x{}+{}+{}'.format(
top_width, top_height,
int(root_x + (self.winfo_width() / 2)),
root_y + 50
))
if __name__ == '__main__':
App()
This is what is looks like:
If I take out top.overrideredirect(1) then it works as expected but I get it with the title bar which I don't want:
One other interesting occurrence is when I run self.update_idletasks() right before calling top.overrideredirect(1) then it again works but with a frozen title bar. I'm not sure why that doesn't happen without self.update_idletasks() (this may be a different question):
I have tried a number of combinations of the following with no wanted results:
top.attributes('-topmost', True)
top.lift(aboveThis=self)
top.focus_force()
top.grab_set()
All in all, I would just like to get the "highlighting" effect to work but with the look of image 1 with no title bar. I'm open to all other suggestions or approaches.
In the below code I am having trouble with the line self.dmenu1.bind("<Button-1>", self.branches), and I'd be really grateful if someone can please set me in the right direction.
I'm expecting to select the an option in the dropdown menu and it changes the sorting inside the Listbox below it.
However what is actually happening, is that after I make my selection, then I have to click the drop down box one more time before the sorting takes effect.
This is not how users would expect the dropdown menu to work. I've posted the full code, as you can see I'm new to it all, but it's a nice challenge to learn :)
Thanks in advance for your help.
Regards,
from tkinter import *
ALL = N+S+W+E
users = ['Fred Asus','Tom Yahoo','Jessy Samsung','Jermain Sony','Nikki Nikon',
'Ian IBM','Elena Google','Rob Braun','Tammy Tonika','James Intel',
'Murphy Richards','Daniel Denon']
branchlst = {138:'Driving - St Albans', 170:'Brighton', 271:'Driving - Birmingham',
330:'Leeds', 680:'Edinburgh'}
class Application(Frame):
def __init__(self, master=None):
#initiate the primary window.
Frame.__init__(self, master)
self.master.rowconfigure(0, weight=1)
self.master.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=0)
self.rowconfigure(1, weight=0)
self.rowconfigure(2, weight=3)
self.columnconfigure(0, weight=0)
self.columnconfigure(1, weight=1)
self.columnconfigure(2, weight=1)
self.grid(sticky=ALL)
self.frameset()
def frameset(self):
#define and setup frames with columns and rows for widgets
#Colours added to framesets to help designing layout. delete them
self.Frame1 = Frame(self) # D
self.Frame2 = Frame(self, bg='blue') # E
self.Frame3 = Frame(self) # L
self.Frame4 = Frame(self, bg='blue') # E
self.Frame5 = Frame(self) # T
self.Frame6 = Frame(self) # E colours
self.Frame1.rowconfigure(0,weight=0)
self.Frame2.rowconfigure(0,weight=0)
self.Frame3.rowconfigure(0,weight=1)
self.Frame4.rowconfigure(0,weight=1)
self.Frame5.rowconfigure(0,weight=1)
self.Frame6.rowconfigure(0,weight=1)
self.Frame1.columnconfigure(0,weight=0)
self.Frame2.columnconfigure(0,weight=0)
self.Frame3.columnconfigure(0,weight=1)
self.Frame4.columnconfigure(0,weight=1)
self.Frame5.columnconfigure(0,weight=1)
self.Frame6.columnconfigure(0,weight=1)
self.Frame1.grid(row=0, column=0, rowspan=1, columnspan=1, sticky=ALL)
self.Frame2.grid(row=0, column=1, columnspan=2, sticky=ALL)
self.Frame3.grid(row=1, column=0, rowspan=2, sticky=ALL)
self.Frame4.grid(row=1, column=1, columnspan=2, sticky=ALL)
self.Frame5.grid(row=2, column=1, rowspan=1, columnspan=1, sticky=ALL)
self.Frame6.grid(row=2, column=2, sticky=ALL)
label4a = Label(self.Frame4, text='table1', bg='orange')
label4b = Label(self.Frame4, text='table2', bg='yellow')
label4a.pack(side=LEFT)
label4b.pack(side=RIGHT)
self.objects()
def objects(self):
var = StringVar()
var.set('Name')
self.dmenu1 = OptionMenu(self.Frame1, var,'Costcode','Name')
self.dmenu1.pack(side=TOP, fill=BOTH)
self.dmenu1.bind("<Button-1>", self.branches)
self.f3ListBox = Listbox(self.Frame3, selectmode='single')
#self.branches()
self.f3ListBox.grid(sticky=ALL)
self.f3ListBox.bind("<Button-3>", self.f1handler1)
f5ListBox = Listbox(self.Frame5, selectmode='single')
n = 0
for item in users:
f5ListBox.insert(n,item)
n += 1
f5ListBox.grid(sticky=ALL)
f6ListBox = Listbox(self.Frame6, selectmode='single')
f6ListBox.insert(1,'S123456') # DELETE
f6ListBox.insert(2,'S313414') # DELETE
f6ListBox.insert(3,'S573343') # DELETE
f6ListBox.grid(sticky=ALL)
def f1handler1(self, event):
"""Creates a popup menu for the alternative mouse button.
Edit this to add more options to that popup"""
select = lambda: self.f3ListBox.delete(ACTIVE)
popup = Menu(self, tearoff=0)
popup.add_command(label='Quit',command=self.quit)
popup.add_command(label='delete',command=select) #add more of these for more options
try:
popup.post(event.x_root, event.y_root)
except:
pass
def branches(self, event):
self.f3ListBox.delete(0,END)
n = 0
if self.dmenu1.cget('text') == 'Costcode':
cc = sorted(list(branchlst.keys()))
for item in cc:
self.f3ListBox.insert(n,str(item)+' '+branchlst[item])
n += 1
elif self.dmenu1.cget('text') == 'Name':
bb = sorted(list(branchlst.values()))
for item in bb:
for name,val in branchlst.items():
if item == val:
self.f3ListBox.insert(n,item+' '+str(name))
root = Tk()
app = Application(master=root)
app.mainloop()
I prefer the route of understanding the problem and solving it, so let us go through it. In your code you have self.dmenu1.bind("<Button-1>", self.branches).
Did you ask yourself when is this event actually fired ? It is fired when you click on the OptionMenu. This means that the current option will be the one used. So, suppose option "a" was active and you changed to option "b". This selection change doesn't fire a Button-1 event, but when you click on your OptionMenu again it will fire and then the widget will have "b" as the current option.
What you actually in your code is:
self.dmenu1 = OptionMenu(self.Frame1, var,'Costcode','Name',
command=self.branches)
and the earlier mentioned binding can be safely eliminated. The just added command option will call a certain function whenever a selection is made on your OptionMenu. Besides this change, you probably also want to populate the listbox bellow it when the program starts. For that, call self.branches(None) after you have defined self.f3ListBox.
The StringVar class has a trace method, which allows you to attach a callback function to it. The function will be called when the variable changes value.
In your code, add this line just below the var.set('Name') line in the objects method.
var.trace('w', self.branches)
This will cause self.branches to be called whenever var changes. It will be called with three arguments, so you'll need to change branches' definition to:
def branches(self, name, index, mode):
You should also delete the self.dmenu1.bind("<Button-1>", self.branches) line, as it is now redundant.