Tkinter GUI: Adding new entry boxes using .grid() when button is pressed - python

I'm trying to create new entry boxes when the "ADD entry" is used for my program. I do realize that the 'pack codes' works when I run it individually, but when I combine with existing program which is in grid(), the window is not showing when I run my program.
I also understand that we should not to use both .pack() when I have other things using .grid() in the same program. Hence, my question is, how do I create new entry boxes in grid.
I have tried finding elsewhere but they all suggested pack. For instance: I have looked here here, etc etc, to name a few; but couldn't find anything similar to mine. I would like to add entry boxes below the current entry boxes which is at row 3.
I'm kind of new to Python; (am using Python 2.7 and tkinter module in this program. Thank you very much for the help!
My simplified error codes are as follows:
from Tkinter import *
import tkFileDialog
import tkMessageBox
import Tkinter
import os
class Window:
def __init__(self, master):
self.filename3=""
csvfile=Label(root, text="NUMBERS").grid(row=3, column=0)
bar=Entry(master).grid(row=3, column=3)
self.entryText3 = StringVar()
self.bar = Entry(root, textvariable=self.entryText3).grid(row=3, column=3)
#BUTTONS
self.cbutton= Button(root, text="OK", command=self.process_csv)
self.cbutton.grid(row=15, column=6, sticky = W + E)
####PROBLEM FROM HERE#####
all_entries = []
addboxButton = Button(root, text='ADD', fg="BLUE", command=self.addBox)
addboxButton.pack()
#I have also tried replacing the last 2 lines with the following 2 lines instead but to no avail:
#self.addboxButton = Button(root, text='ADD THA-ID', fg="BLUE", command=self.addBox)
#self.addboxButton.grid(row=3, column=6)
frame_for_boxes = Frame(root)
frame_for_boxes.pack()
def addBox(self):
print "ADD"
next_row = len(all_entries)
lab = Label(frame_for_boxes, text=str(next_row+1))
lab.grid(row=next_row, column=0)
ent = Entry(frame_for_boxes)
ent.grid(row=next_row, column=0)
all_entries.append( ent )
def process_csv(self):
#own program here
print "program"
root = Tk()
window=Window(root)
root.mainloop()

There are few issues with your program other than the one you stated:
Inside the initializer (__init__()) you attached the widgets to root which is not defined within the scope of your Window class. The reasonable way to fix this issue is when you use an instance of Tk(), id est root id est master in Window class, make it as an instance variable. This means the first thing you have to do in the inititializer is this : self.master = master. This will result in you having to replace all root occurrences within __init__() by self.master
The second issue to fix is the one you specified in your question's title: you can not mix the grid() and pack() layout managers for the same widget container. You have to decide which one. Since you placed most of the widgets using grid(), I suggest you to get rid of pack(). This means:
addboxButton.pack() becomes, for example, addboxButton.grid(row=0, column=1)
frame_for_boxes.pack() becomes, for example, frame_for_boxes.grid(row=0, column=0)
The previous list item fixes the problem but it will make you discover other issues within your program which are related:
NameError: global name 'all_entries' is not defined
NameError: global name 'frame_for_boxes' is not defined
This is because those widget variables are not reachable within the scope of addBox() function. To resolve this issue, you have to make those 2 elements as instance variables. This means:
all_entries = [] becomes self.all_entries = []
frame_for_boxes = Frame(root) becomes self.frame_for_boxes = Frame(self.master) (remember we replaced root by self.master in 1.)
The consequence of this error fixing is that you have to use all over inside your program:
self.all_entries instead all_entries
self.frame_for_boxes instead of frame_for_boxes
For scalability reasons, I think you will have at least to make the rest of widgets as instance variables (i.d. prefix them with the self keyword)
As your real project is more complicated than what you show in this MCVE, I encourage you to adopt the SaYa idiom when creating and placing widget elements. This means you will need to replace:
csvfile=Label(root, text="NUMBERS").grid(row=3, column=0)
by
self.csvfile = Label(self.master, text="NUMBERS")
self.csvfile.grid(row=3, column=0)
To avoid unexpected bugs in your program, you must do the same for the remaining widgets you declared in the inititialzer.
There are also other things I would like to mention, but most of them are available on PEP8

What you have to do it to create a command which creates the entries and stores the new entries inside of a variable.
In my case, I use Entry_i and store in Entries but you can use self.Entries to make communication easier. (python 3.5)
def Make_Entry(self, root, Entries, x, y):
Entry_i = Entry(root, bd = 5)
Entry_i.grid(row = x, column = y, sticky = W+E+N+S)
Entries.append(Entry_i)
return(Entries, x+1, y+1)

Related

Tkinter: ttk.Label displaying nothing when given StringVar as textvariable, inside a class

I am trying to use the textvariable attribute of ttk.Label to display & update text according to a given StringVar.
from tkinter import *
from tkinter import ttk
class RenderEvent():
def __init__(self, root):
self.root = root
self.frame = ttk.Frame(self.root, padding="20 20 20 20")
self.frame.grid(column=0, row=0, sticky=(N, W, E, S))
self.dialogue = StringVar(self.frame, value="Placeholder")
L = ttk.Label(self.frame, textvariable=self.dialogue)
L.grid(column=1, row=2, sticky=W)
self.dialogue.set("some text here")
And for reference, root is passed in from another file which looks like this, and is used to start the application:
from tkinter import *
from tkinter import ttk
from renderevent import RenderEvent
root = Tk()
RenderEvent(root)
root.mainloop()
If I use text instead of textvariable in the Label creation, it displays a static string just fine. However, once it is set to textvariable (as shown above), nothing will be displayed at all.
I have tried the same with giving the StringVar() no parameters at all in initialization, or passing in self.root.
Emulating this code outside of a class seems to work as intended (the text appears and updates along with the textvariable), but I can't think of why having a class would cause an issue like this.
The reason is that you aren't keeping a reference to the instance of RenderEvent. Since you don't save a reference, python's garbage collector will try to clean it up which causes the variable to be cleaned up as well. The ttk widgets are more sensitive to this than the normal tk widgets.
The simple and arguably best solution is to assign the result of RenderEvent(root) to a variable so that it isn't affected by python's memory management.
re = RenderEvent(root)

Tkinter destroying an object in a different function isn't working

I'm trying to make a button that saves your username but then goes away after you set it.
this is my code:
def printValue():
User = Name.player_name.get()
label.config(text=f'Hi, {User}')
Name.button.destroy()
Name.player_name.destroy()
def Name():
label.config(text="What's your name?")
Name.player_name = Entry(root)
Name.player_name.pack(pady=15)
Name.button = Button(text="Change", command=printValue)
Name.button.pack()
The code below, with some minor changes like enabling change with [Return] and some layout cosmetics works OK (also with un-commented lines in printValue) . If you want the [Change] button and the entry area to go away un-comment the two lines turned into comments in the printValue function:
# https://stackoverflow.com/questions/72671126/tkinter-destroying-an-object-in-a-different-function-isnt-working
from tkinter import Tk, mainloop, Entry, Button, Label
root = Tk()
label = Label(root, font=('',12), padx=15, pady=5)
label.pack()
def Name():
label.config(text="What's your name?")
Name.player_name = Entry(root, font=('',12))
Name.player_name.pack(padx=15, pady=15)
Name.player_name.focus()
Name.button = Button(text="Change", command=printValue)
Name.button.pack()
def printValue(event=None):
User = Name.player_name.get()
# Name.player_name.destroy()
# Name.button.destroy()
label.config(text=f'Hi, {User}')
Name()
root.bind("<Return>", printValue)
mainloop()
By the way: The in the question provided code demonstrates an interesting approach of making names of variables global by setting function attributes in the function itself. This way it is possible to assign values in one function and retrieve them in another without passing return values or declaring variables global. I am not aware of already having seen such approach used in Python code here on stackoverflow. How does it come you use such code?

Python - Printing to GUI instead of terminal

Very new to Python here, and I'm trying to create a GUI app that returns a random recipe. Currently the print happens at the terminal, and I'd like it to print in the GUI instead.
from tkinter import *
import os
import random
root = tk.Tk()
def printRecipes():
recipes = [
"Tom Yum Soup",
"Carnitas",
"General Tso's Chicken"
]
print(random.choice(recipes))
canvas = tk.Canvas(root, height=600, width=700, bg="#A8D1BB")
canvas.pack()
magic = tk.Button(root, text="Print", padx=10, pady=5, fg="white", bg="black", command=printRecipes)
magic.pack()
root.mainloop()
This doesn't work, as most of you already know. I've read that I need to use a label or text for it, but the example I've found all involved static print statements like
label = Label(root,text="Recipe")
label.pack
To "print" a value to a GUI Window, a Label is used. .config() is a useful function. It is used to configure the provided widget.
Below magic.pack(), add this code. Notice that there is no text parameter. We will use that later.
label1=Label(root,pady=10,font=("arial",15))
label1.pack()
Next, in the function, where you had print(random.choice(recipes)), we will add:
label1.config(text=random.choice(recipes))
Notice that we used .config() and the text parameter. We configured the label, and added some text to it.
You need a tk.StringVar for this, i.e. a variable which you can change and the label can read from:
label_str = tk.StringVar(value="Some text")
label = tk.Label(root, textvariable=label_str)
Then, to update the value of this label and show it on the GUI:
label_str.set("New text")
label.update()

how to copy a string that appears on tkinter GUI

i have a password generator script that works fine, problem is, i present the password in the GUI in a label and it doesn't give me the option to copy it so i can put the password where i want it, how can i print the password to the GUI so i can copy it after, is there a better way than label?
I am using the function
tk.Label(root,text=k).grid(row=1)
k being the variable where the password is stored
Alternately if there is some python function that enables me to just straight up copy the contents of k to the clipboard that might be even better, thanks
The simplest solution is to use an Entry widget with the state set to "readonly".
Example:
import tkinter as tk
root = tk.Tk()
entry = tk.Entry(root)
entry.pack(side="top", padx=20, pady=20)
# insert the password
entry.insert(0, "SuperSecretPassw0rd!")
# configure the entry to readonly
entry.configure(state="readonly")
root.mainloop()
You can also automatically add it to the clipboard with the clipboard_clear and clipboard_append methods:
root.clipboard_clear()
root.clipboard_append(entry.get())
If I understand your problem correctly, you want to get or set a value in a TK gui? Instead of using a Label, I would use an Entry, and I would use one of the TK variable classes (such as StringVar) for k, which have get and set methods
here's a example of a script I use to get and set text values in a TK widget:
frame = tk.Frame(master)
frame.pack()
filepath = tk.StringVar()
filepath.set("/Volumes/data/data/test_data/")
fileentry = tk.Entry(frame, textvariable=filepath, width=125)
fileentry.pack()
if something:
a = filepath.get()
reference to TK variable classes: https://effbot.org/tkinterbook/variable.htm

Python3.x: tkinter (ttk) stop displaying widget

So I've been trying to make some basic GUIs with tkinter (not te be confused with Tkinter) and I ran into a problem for which I know no solution and can't really find anything on the almighty Google...
I have a small SQLite database with a table of directories on my pc. I would like to draw all directorypaths into a label and add a 'rempve' button next to that label. The button should be able to remove directory from the database and also remove it from the GUI. I also have a 'add' button where one can add directories to the database and this new directory should be shown in the GUI. This is my basic layout:
---------------
| ADD |
|dir1 REMOVE|
|dir2 REMOVE|
---------------
I use the gridlayout to show the buttons and labels. Most things work, all database related stuff works. Also when starting the GUI the current directories and 'remove'-buttons are shown nicely. BUT... when using the 'remove' button the directory does not disappear from the GUI even though it is not in the database anymore, restarting the GUI fixes it of course. Adding a label works... but I'm not sure if I'm doing it correctly...
How can I somehow 'repaint' the GUI with the new information?
This is my code for the GUI:
class GUI():
def __init__(self,db):
self.root = Tk()
self.root.title("Example")
self.frame = ttk.Frame(self.root, padding="3 3 12 12")
self.frame.rowconfigure(5, weight=1)
self.frame.columnconfigure(5, weight=1)
self.frame.grid(sticky=W+E+N+S)
lbl = ttk.Label(self.frame, text="", width=17)
lbl.grid(row=0, column=2, sticky=W)
ttk.Button(self.frame, text="Add directory", command=lambda:self.load_file(db), width=30).grid(row=0, column=0, sticky=W, padx=(500,50))
ttk.Button(self.frame, text="Sort files", command=lambda:self.sort(db,lbl), width=17).grid(row=0, column=1, sticky=W)
self.draw(db)
self.root.mainloop()
def load_file(self,db):
fname = filedialog.askdirectory()
db.addPath(fname)
self.draw(db)
def remove_dir(self,db,pid):
db.removePath(pid)
self.draw(db)
def sort(self,db,lbl):
lbl['text'] = 'Sorting...'
sortFiles.moveFiles(db)
lbl['text'] = 'Done!'
def draw(self,db):
i = 0
paths = db.getPaths()
for path in paths:
ttk.Label(self.frame,text=path[1]).grid(row=1+i,column=0,sticky=W)
ttk.Button(self.frame, text="Remove directory", command=lambda:self.remove_dir(db,path[0]), width=17).grid(row=1+i,column=1, sticky=E)
i = i+1
for child in self.frame.winfo_children(): child.grid_configure(padx=5, pady=5)
if i == 0:
ttk.Label(self.root,text='No directories added yet').grid(row=1,column=0,sticky=W)
If you prefer to redraw the GUI every time you add or delete something, you need to first destroy any old widgets before creating new ones. For example:
def draw(self, db):
# first, delete any existing widgets
for child in self.frame.winfo_children():
child.destroy()
# next, redraw all the widgets
paths = db.getPaths()
for path in paths:
...
You have another bug, which is how you're using lambda. As it stands with the code in the question, all of your callbacks will see the same value. By specifying the value as a keyword argument to the lambda you'll get the right value:
ttk.Button(..., command=lambda p=path[0]:self.remove_dir(db, p)...)
Unrelated to the actual problem, I don't think you need to be passing db around. Assuming you only use a single db, I recommend you do self.db = db in your GUI constructor. That will make your code just a little easier to maintain because your method signatures will be simplified.
Finally, there's really no need to completely redraw the GUI when you delete one item. You can delete just one label and button at a time. This requires that you spend a little more time thinking about how you manage data in your program. If, for example, you keep a reference to each label and button, you can delete it when you delete the path from the database. Your removeDir function might look something like:
def removeDir(self, pid):
label, button = self.widgets(pid)
label.destroy()
button.destroy()

Categories

Resources