Tkinter buttons bellow grid to close and save to file [duplicate] - python

This question already has answers here:
Why is my Button's command executed immediately when I create the Button, and not when I click it? [duplicate]
(5 answers)
What is the purpose of the `self` parameter? Why is it needed?
(26 answers)
Closed 6 months ago.
I'm trying to create a grid of buttons that change colour with Tkinter.
from tkinter import *
class App():
def __init__(self, root):
self.root = root
buttonQ = Button(self.root, text = "Quit", command = endProgam())
buttonS = Button(self.root, text = "Save", command = saveToFile())
def Function(self):
self.grid = []
for i in range(5):
row = []
for j in range(5):
row.append(Button(self.root,width=6,height=3,command=lambda i=i, j=j: self.Click1(i, j),background='gray'))
row[-1].grid(row=i,column=j)
self.grid.append(row)
def Click1(self, i, j):
orig_color = self.grid[i][j].cget('bg')
#print(orig_color)
if orig_color=="red":
self.grid[i][j]["bg"]="gray"
else:
self.grid[i][j]["bg"]="red"
#self.grid[i][j]["bg"]="red"
#self.grid[i][j].configure(background="blue")
def endProgam(self):
# top.quit()
top.destroy()
def saveToFile(self):
# save matrix to file
top.destroy()
root = Tk()
app = App(root)
app.Function()
root.mainloop()
My problem is that I cannot add 2 buttons below the grid, one to quit and one to save into a file values based on the button colours (0-grey and 1-red as a matrix) and then quit.
File "--", line 37, in <module>
app = App(root)
File "--", line 6, in __init__
buttonQ = Button(self.root, text = "Quit", command = endProgam())
TypeError: endProgam() missing 1 required positional argument: 'self'
It's my first time coding in Python with Tkinter, so please be gentle :)

First, your indentation levels for your Class are off. The methods need to be indented another level or you'll get a TypeError for each method.
Second, for buttonQ and buttonS, make sure you are referencing the instance of the class, i.e.:
buttonQ = Button(self.root, text = "Quit", command = endProgam)
buttonS = Button(self.root, text = "Save", command = saveToFile)
should be:
buttonQ = Button(self.root, text = "Quit", command = self.endProgam)
buttonS = Button(self.root, text = "Save", command = self.saveToFile)
(Note the use of self)
As far as actually placing the buttons, I would recommend creating an additional frame to manage the layouts separately. You can create and place these just like widgets and it helps make managing the layouts much simpler.
For example:
class App():
def __init__(self, root):
self.root = root
self.TopFrame = Frame(root) # Create a top frame to place the original grid
self.BottomFrame = Frame(root) # Create a frame for the additional buttons
self.TopFrame.grid(row=0) # Place the Frame itself
self.BottomFrame.grid(row=6) # Place the new Frame directly below the first
# Changed to an instance variable to reference in Function method
buttonQ = Button(self.BottomFrame, text="Quit", command=self.endProgam)
buttonS = Button(self.BottomFrame, text="Save", command=self.saveToFile)
buttonS.grid(row=0, column=0, padx=10)
buttonQ.grid(row=0, column=1, padx=10)
def Function(self):
self.grid = []
for i in range(5):
row = []
for j in range(5):
row.append(Button(self.TopFrame,width=6,height=3,command=lambda i=i, j=j: self.Click1(i, j),background='gray'))
row[-1].grid(row=i,column=j)
self.grid.append(row)
Notice the new TopFrame and BottomFrame. The grid buttons are now sitting on the TopFrame while the BottomFrame contains the two new button widgets.
You'll find that placing separate layout objects in its own frame will make managing more complex layouts much simpler.

Related

Tkinter functions from outside of class being immediately called

I am having some issues getting these functions to work properly for my tkinter app. I have two files, one containing the main Window class and the other containing a function that I am trying to connect to a button command. The issue is the button is in the main Window and the function I am associating with its "click" is not updating and just seems to execute the function once through.
Here are my two files:
main.py
import customtkinter as ctk
from size import *
class Window(ctk.CTk):
WIDTH = 700
HEIGHT = 600
def __init__(self) -> None:
super().__init__()
self.geometry(f"{Window.WIDTH}x{Window.HEIGHT}")
self.title("Test")
#Setup Frames-----#
#Configure grid layout (2x1)
self.grid_columnconfigure(1, weight=1)
self.grid_rowconfigure(0, weight=1)
#Configure left frame
self.frame_left = ctk.CTkFrame(master=self,width=180,corner_radius=0)
self.frame_left.grid(row=0, column=0, sticky="nswe", padx=10,pady=10)
#Configure right frame
self.frame_right = ctk.CTkFrame(master=self)
self.frame_right.grid(row=0, column=1, sticky="nswe", padx=10, pady=10)
#Far Left Frame
self.frame_left.grid_rowconfigure(0, minsize=10)
#Labels-----#
#Left Frame Labels
size_label = ctk.CTkLabel(master=self.frame_left, text="Size Option:")
monster_name_label = ctk.CTkLabel(master=self.frame_left, text="Monster Name:")
#Right Frame Labels
display_monster_name = ctk.CTkLabel(master=self.frame_right, text='')
display_size = ctk.CTkLabel(master=self.frame_right, text='')
#Comboboxes-----#
#Size
size_combobox = ctk.CTkComboBox(master=self.frame_left, values=size_options_combobox)
size_combobox.set("Random")
#Functions-----#
#Size
size_command = add_size(size_combobox, display_size)
#Buttons-----#
#Size
add_size_btn = ctk.CTkButton(master=self.frame_left,command=size_command, text="+", width=30)
#Grid Layout-----#
#Left frame grid layout
#Row 1
size_label.grid(row=1, column=0)
size_combobox.grid(row=1, column=1)
add_size_btn.grid(row=1, column=2, sticky = "W")
#Right frame grid layout
#Row 1
display_size.grid(row=1,column=1)
if __name__ == "__main__":
window = Window()
window.mainloop()
The other file I am importing from is size.py:
from main import *
import customtkinter as ctk
import random
def add_size(size_combobox, display_size):
size_choice = StringVar()
size_choice = size_combobox.get() #Suspect this maybe the issue
random_size = random.choice(size_options_label)
if size_choice == "Random":
display_size['text'] = random_size
else:
display_size['text'] = size_choice
I am suspecting that the issue may lie with the .get() call off of the add_size function because if I run this in the main.py within the Window class, it works and updates the Labels value with whatever the combobox choice is.
Here is a screen shot of what it looks like when it runs once through and since it is set to "Random" the if statement executes once through with that as its value and won't update after another button click.
You're explicitly asking for add_size to be called in this line:
size_command = add_size(size_combobox, display_size)
You need to change it to use lambda or functools.partial:
size_command = lambda: add_size(size_combobox, display_size)

Pass selection Listbox topwindow to main window

This is my first real Python project. I am currently developing a GUI in Tkinter that allows the user to select Tasks and CVs to automatically compile documents using standard predefined task and CV texts from a database.
I have created two "Add" buttons in the main window to add Tasks and CVs that show a popup Listbox that allow the user to select the Tasks and CVs they want to have included in the commercial proposal. I have managed to create the popup window as a separate Class and it stores the selected Tasks in a list, but now I need to pass the list with the selected items to the Listbox in the main window when the user clicks the Select button in the popup window, but I cannot get my head around on how to do that.
I have researched on different fora and watched a variety of Youtube videos, but all focus on entry popups or some sort.
This is the code for the main window:
from tkinter import *
from Add import *
# make main window
root = Tk()
theLabel = Label(root, text="ProposalBuilder")
theLabel.grid(row=0)
# make frames
taskFrame = Frame(root)
taskFrame.grid(row=1, column=0)
CVFrame = Frame(root)
CVFrame.grid(row=1, column=1)
buildFrame = Frame(root)
buildFrame.grid(row=2, columnspan=2)
# add labels to frames
taskLabel = Label(taskFrame, text="Tasks")
taskLabel.pack()
CVLabel = Label(CVFrame, text="CVs")
CVLabel.pack()
# add listboxes to frames
scrollTask = Scrollbar(taskFrame, orient=VERTICAL)
listTask = Listbox(taskFrame, selectmode=MULTIPLE, yscrollcommand=scrollTask.set)
scrollTask.config(command=listTask.yview)
scrollTask.pack(side=RIGHT, fill=Y)
listTask.pack()
scrollCV = Scrollbar(CVFrame, orient=VERTICAL)
listCV = Listbox(CVFrame, selectmode=MULTIPLE, yscrollcommand=scrollCV.set)
scrollCV.config(command=listCV.yview)
scrollCV.pack(side=RIGHT, fill=Y)
listCV.pack()
# add commands to buttons
def addTask():
taskBox = Add('C:\\Users\\204703\\ProposalBuilder\\Database')
sel_test = taskBox.selection
def addCV():
CVBox = Add('C:\\Users\\204703\\ProposalBuilder\\Database')
# add buttons to frames
buttonAddTask = Button(taskFrame, text="Add", command=addTask)
buttonAddTask.pack(fill=X)
buttonDelTask = Button(taskFrame, text="Delete")
buttonDelTask.pack(fill=X)
buttonUpTask = Button(taskFrame, text="Up")
buttonUpTask.pack(fill=X)
buttonDownTask = Button(taskFrame, text="Down")
buttonDownTask.pack(fill=X)
buttonAddCV = Button(CVFrame, text="Add", command=addCV)
buttonAddCV.pack(fill=X)
buttonDelCV = Button(CVFrame, text="Delete")
buttonDelCV.pack(fill=X)
buttonUpCV = Button(CVFrame, text="Up")
buttonUpCV.pack(fill=X)
buttonDownCV = Button(CVFrame, text="Down")
buttonDownCV.pack(fill=X)
buttonBuild = Button(buildFrame, text="Build Proposal")
buttonBuild.pack(side=RIGHT)
root.mainloop()
This is the code for the separate class I created for the popup window:
from tkinter import*
from os import *
class Add:
def __init__(self, path):
# the slected tasks
self.selection = []
# make a frame
top = Toplevel()
# get file names from the directory (path) and save in list
self.path = path
self.dirList = listdir(self.path)
# add listbox to frames and populate with file names
self.scrollList = Scrollbar(top, orient=VERTICAL)
self.listbox = Listbox(top, selectmode=MULTIPLE, yscrollcommand=self.scrollList.set)
self.scrollList.config(command=self.listbox.yview)
self.scrollList.pack(side=RIGHT, fill=Y)
for item in self.dirList:
self.listbox.insert(END,item)
self.listbox.pack()
# add buttons to frame
self.selectButton = Button(top, text="Select", command=self.select)
self.selectButton.pack()
self.quitButton = Button(top, text="Quit", command=top.destroy)
self.quitButton.pack()
# identify selected rows and return a list with the selection
def select(self):
selectedRows = self.listbox.curselection()
for item in selectedRows:
self.selection.append(self.dirList[item])
print(self.selection)
return self.selection
Question: pass the list with the selected items to the Listbox in the main window
You need a reference of the main window Listbox.
I show, how to using listTask
Extend your __init__ to accept the reference target_listbox
class Add:
def __init__(self, target_listbox, path):
self.target_listbox = target_listbox
Insert the selected items into .target_listbox
Note: Your, return self.selection is useless, a Button.command can't process returns.
def select(self):
selectedRows = self.listbox.curselection()
for item in selectedRows:
self.target_listbox.insert(tk.END, self.dirList[item])
Pass the reference listTask to Add(...
taskBox = Add(listTask, ...)

Python - Adding new entry box when button is pressed (Tkinter) W/O global code

I know there is a similar question but I'm wondering how I go about doing this without any global code.
I want a new entry to pop up (as well as a label next to it) when a button is pressed.
class Options(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
def add(self):
new_entry = Entry(self)
new_entry.grid()
def main():
t = Tk()
frame = Options(t)
frame.pack()
b0 = Button(frame, text ="Add entry", command = frame.add())
b0.grid()
Your code actually does what you want. The only problem is you are calling the frame.add function instead of passing it as command, in button creation line by adding (). Remove those parenthesis and you will be OK.
b0 = Button(frame, text ="Add entry", command = frame.add) #no parenthesis here
If you want a pop-up, you need to create a Toplevel and put what you want in it.(Entry and Label for your case)
def add(self):
self.top = Toplevel(self)
new_entry = Entry(self.top)
new_entry.grid()

Tkinter: How can I save text entered in the Text Widget into a new window?

Hi I am new to programming in Tkinter. I have written this program, which is in the making. I made a class called IngredientAdder() and within that class under the method def steps_box I have a variable self.entry2, which calls upon the Text Widget. When I run the program, the text box works fine. However, under my method def save_recipie I wrote for it to print words.get('1.0', 'end') into the terminal window when I press the button in the init.gui method (words.get corresponds with the text entered by the user in the text box called self.entry2). However, when I run the program and enter text into text box and press the save button, nothing is printed into my terminal window. How can I modify my code so that the user entered text in the text box is printed into my terminal window? Help?
If you could possibly add comments in your code, it would be very helpful!! Thanks.
import Tkinter
class Cookbook(Tkinter.Tk):
def __init__(self):
Tkinter.Tk.__init__(self)
self.title("Cookbook")
self.geometry("500x500+0+22")
self.button = []
for r in range(1):
for c in range(1):
b = Button(self).grid(row=r,column=c)
self.button.append(b)
class Button(Tkinter.Button):
def __init__(self,parent):
b = Tkinter.Button.__init__(self, parent, text="Add A New Recipie", height=8, width=15, command=self.make_window)
def make_window(self):
popwindow = IngredientAdder()
popwindow.title_box()
popwindow.ingredients_box()
popwindow.steps_box()
popwindow.init_gui()
class IngredientAdder(Tkinter.Tk):
def __init__(self):
Tkinter.Tk.__init__(self)
self.title("Recipie")
self.geometry("555x500")
def title_box(self):
#Frame for the Title Label and the Title Entry Box
self.frame1 = Tkinter.Frame(self, height=50, width=550, relief=Tkinter.SUNKEN)
self.frame1.pack(anchor=Tkinter.NW,side=Tkinter.TOP)
self.frame1.pack_propagate(False)
#putting in a Title LABEL and ENTRY BOX
Tkinter.Label(self.frame1,text="Title:").pack(anchor=Tkinter.NW,side=Tkinter.LEFT)
self.entry1 = Tkinter.Entry(self.frame1,width=550)
self.entry1.pack(anchor=Tkinter.NW,side=Tkinter.TOP)
def ingredients_box(self):
#Frame for the Ingredients Label and the Ingredients Entry Boxes & Steps label and Steps Textbox
self.frame2 = Tkinter.Frame(self, height=412,width=550, relief=Tkinter.SUNKEN)
self.frame2.pack(anchor=Tkinter.NW,side=Tkinter.TOP)
self.frame2.pack_propagate(False)
# put an Ingredients label at the top of the window and anchor it there
ingredients_label = Tkinter.Label(self.frame2,text="Ingredients:").pack(anchor=Tkinter.NW,side=Tkinter.LEFT) #.grid(row=100, column=0)
def steps_box(self):
self.entry2 = Tkinter.Text(self.frame2,width=40,height=33,font="helvetica 12",padx=5,pady=5).pack(anchor=Tkinter.NW,side=Tkinter.RIGHT)
#putting in an entry box and Steps label for the steps of the recepie
steps_label = Tkinter.Label(self.frame2,text="Steps:").pack(anchor=Tkinter.NW,side=Tkinter.RIGHT) #.grid(row=100,column=1)
def title_save(self):
self.title_entries.append(self.entry1)
def text_box(self):
self.text_entries.append(self.entry2)
# function to add new ingredients
def add_ingredient_entry(self):
entry = Tkinter.Entry(self.frame2)
entry.pack(anchor=Tkinter.NW,side=Tkinter.TOP)
self.ingredient_entries.append(entry)
# get contents of all entry boxes
def save_recipie(self):
print("Title:")
for words in self.title_entries:
print words.get()
print("Ingredients:")
for ingredient in self.ingredient_entries:
print ingredient.get()
print("Steps:")
for text in self.text_entries:
print text.get('1.0', 'end')
print "[Recipie saved]"
# build initial widgets
def init_gui(self):
# title saved in this array, hopefully...
self.title_entries = []
# this is a list of ingredients entry boxes
self.ingredient_entries = []
#this saves the list in this array, hopefully..
self.text_entries = []
#Making a frame at the bottom to put both buttons in line
self.test4 = Tkinter.Frame(self,height=10, relief=Tkinter.SUNKEN)
self.test4.pack(side=Tkinter.BOTTOM)
# Put these two buttons at the bottom of the window and anchor them there
Tkinter.Button(self.test4,text="Save recipe",command=self.save_recipie, width=15).pack(anchor=Tkinter.SE,side=Tkinter.RIGHT)
Tkinter.Button(self.test4,text="Add ingredient",command=self.add_ingredient_entry, width=15).pack(anchor=Tkinter.NW,side=Tkinter.LEFT)
# new ingredients will be added between the label and the buttons
self.add_ingredient_entry()
top = Cookbook()
top.mainloop()
The code
def steps_box(self):
self.entry2 = Tkinter.Text(self.frame2,width=40,height=33,font="helvetica 12",padx=5,pady=5).pack(anchor=Tkinter.NW,side=Tkinter.RIGHT)
register None into self.entry2. You should put
def steps_box(self):
self.entry2 = Tkinter.Text(self.frame2, width=40, height=33, font="helvetica 12", padx=5, pady=5)
self.entry2.pack(anchor=Tkinter.NW, side=Tkinter.RIGHT)
However, you didn't register self.entry2 into self.text_entries because you don't call your method text_box.

Button returning specific string [Tkinter] [duplicate]

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
How to pass arguments to a Button command in Tkinter?
(18 answers)
Closed 6 months ago.
I currently have a Tkinter that displays multiple names as label.
The right side of every labels has a button named "Foo" and when clicked,
it will invoke a function that needs the name of the label on the left of the button that was clicked.
This is how I created the button and the label:
from Tkinter import *
class display():
def __init__(self):
self.namelist = ["Mike","Rachael","Mark","Miguel","Peter","Lyn"]
def showlist(self):
self.controlframe = Frame()
self.controlframe.pack()
self.frame = Frame(self.controlframe,height=1)
self.frame.pack()
row = 0
for x in self.namelist:
label = Label(self.frame,text="%s "%x,width=17,anchor="w") #limit the name to 17 characters
fooButton = Button(self.frame,text="Foo")
label.grid(row=row, column=0, sticky="W")
fooButton.grid(row=row, column=1)
row = row + 1
mainloop()
D = display()
D.showlist()
How do I do something like if I click the Foo button next to Mark then the button will return the name of the label, Mark. The same goes for other Foo buttons next to other labels.
Thanks!
Here's how you can do it:
define a command for every button in the loop
pass a current loop index into the callback (see How to pass arguments to a Button command in Tkinter?)
in the button click callback, get the item from the namelist by the index passed into the callback
Here's the code:
from Tkinter import *
class display():
def __init__(self, controlframe):
self.controlframe = controlframe
self.namelist = ["Mike", "Rachael", "Mark", "Miguel", "Peter", "Lyn"]
def callback(self, index):
print self.namelist[index]
def showlist(self):
self.frame = Frame(self.controlframe, height=1)
self.frame.pack()
row = 0
for index, x in enumerate(self.namelist):
label = Label(self.frame, text="%s " % x, width=17, anchor="w") #limit the name to 17 characters
fooButton = Button(self.frame, text="Foo",
command=lambda index=index: self.callback(index))
label.grid(row=row, column=0, sticky="W")
fooButton.grid(row=row, column=1)
row = row + 1
tk = Tk()
D = display(tk)
D.showlist()
tk.mainloop()
Note how the index is passed to the lambda, this is so called "lambda closure scoping" problem, see Python lambda closure scoping.
Hope that helps.

Categories