Tkinter Button to creat entries - python-3.x

I am trying to learn tkinter and got stuck with a little problem.
I have created a very simple window that has 2 messages on top "Activity:", "Time Spend:" and a Button "+" on the Bottom of the Window.
from tkinter import *
root = Tk()
root.geometry("274x520+868+240")
root.minsize(120, 1)
root.maxsize(3284, 1068)
root.resizable(0, 0)
root.title("Learning")
root.configure(background="black")
def new_entry():
n = [n for n in range(30, 490, 30)]
for i in n:
e1 = Entry(root)
e2 = Entry(root)
e1.place(y=i, width=133)
e2.place(x=141, y=i, width=137)
print(e1, e2)
return e1, e2
b1 = Button(root)
b1.place(y=499, width=276)
b1.configure(text="+")
b1.configure(command=new_entry)
b1.configure(background="darkgrey")
msg1 = Message(root)
msg1.place(width=133, height=29)
msg1.configure(text="Activity:")
msg1.configure(background="darkgrey")
msg2 = Message(root)
msg2.place(x=141, width=137, height=29)
msg2.configure(text="Time Spend:")
msg2.configure(background="darkgrey")
root.mainloop()
The Button is supposed to create 2 entrys next to each other, every time it is clicked, but if i click the button, it will create the entrys once, clicking it again wont create the entrys on the window, but just creates this in my terminal:
.!entry .!entry2
.!entry3 .!entry4
.!entry5 .!entry6
.!entry7 .!entry8
If I remove the "return e1,e2" statement, all entrys are created in terminal as well as in the window, with 1 click of the button, instead of being created 2 by 2 for every click of the button.
If I remove the "print(e1,e2)" statement, clicking the button will create the 2 entrys only once, and doesnt show anything in the terminal.
New entrys should be created with a little space in between, until the upper border of the Button is reached (in this case 32 Entrys, 16 rows).
Could someone also explain why msg1 is displayed wrong (it is not centered and it is splitted in two lines), but msg2 is fine, they have the same specs??

Your code stops as soon as the return statement is reached, and thus your loop never goes further than the first iteration.
On the other hand, if you remove the return statement, there is nothing in place for stopping your loop so it keeps running until it has created all your widgets.
I took an approach using a generator to solve your issue, such that instead of having a function new_entry(), instead you have a function generating the entries, and another function placing the entries when the generator is called.
I also simplified your code a little at the widget generation, and changed a spelling error (spent instead of spend). I also replaced from tkinter import * with import tkinter as tk, to not pollute your namespace.
import tkinter as tk
root = tk.Tk()
root.geometry("274x520+868+240")
root.minsize(120, 1)
root.maxsize(3284, 1068)
root.resizable(0, 0)
root.title("Learning")
root.configure(background="black")
def entry_generator():
for i in range(30, 490, 30):
yield place_entry(i)
def place_entry(i):
e1 = tk.Entry(root)
e2 = tk.Entry(root)
e1.place(y=i, width=133)
e2.place(x=141, y=i, width=137)
print(e1, e2)
ne = entry_generator()
b1 = tk.Button(root, command=lambda: next(ne), text='+', background='darkgrey')
b1.place(y=499, width=276)
msg1 = tk.Label(root, text='Activity:', background='darkgrey')
msg1.place(width=133, height=29)
msg2 = tk.Label(root, text='Time Spent:', background='darkgrey')
msg2.place(x=141, width=137, height=29)
root.mainloop()

def new_entry():
n = [n for n in range(30, 490, 30)]
for i in n:
e1 = Entry(root)
e2 = Entry(root)
e1.place(y=i, width=133)
e2.place(x=141, y=i, width=137)
print(e1, e2)
return e1, e2 ```
The return is the problem. Look at following sample code:
def iterate():
for i in range(10):
print(i)
return i
val = iterate()
print(val)
When you run this it will print:
0
0
Because, return makes the function stop immediately and return something.
If you really had to return every value, you could create a list, append the things to it, and return that list after the loop:
def iterate():
arr = []
for i in range(10):
print(i)
arr.append(i)
return arr
val = iterate()
print(val)
which will print:
0
1
2
...
10
[0, 1, 2, ... 10]
or use the yield keyword (it will return the value without making the loop stop):
```python
def iterate():
for i in range(10):
print(i)
yield i
val = iterate()
print(val)
print([i for i in val])
will print:
0
1
2
...
10
<generator object iterate at 0x7fb64e584f68>
[0, 1, 2, 3 ... 10]
With yield, val will be a generator object, over which you must iterate again to get the values out of it.
But, as the return value isn't used anywhere in your code, you can just remove the return e1, e2.

Another method achieve might be to keep a count of the entry and multiply the count with the height of the Entry widget
Here is an example:
count = 0
def new_entry():
global count
e1 = Entry(root)
e2 = Entry(root)
count += 1
y_pos = count*e1.winfo_reqheight()
e1.place(x=0, y=y_pos*1.5, width=133) # change the multiplication factor to change the padding b/w entry
e2.place(x=141, y=y_pos*1.5, width=137) # change the multiplication factor to change the padding b/w entry
print(e1, e2)
Also note that it might be better to use grid layout for such programs.

Related

I want to place a scrollbar on the bottom of the screen but I get an error: object has no attribute 'xview'

I want to place a Scrollbar at the bottom of the screen. The error I get is: object has no attribute 'xview'. Now I understand that this means that the grid must be placed at a separate line. But I can't find the wrong code. Can someone tell me what have I done wrong?
my_w = tk.Tk()
my_w.geometry("1514x380")
my_w.resizable(False, False)
my_w.configure(bg='#2e7287')
my_w.title('Openbare Verlichting Administratie record lijst')
def my_display(offset):
#Add a Horizontal Scrollbar
scroll_h = Scrollbar(my_w, orient= HORIZONTAL)
scroll_h.grid(row=31,column=0, sticky=N+S+E+W) #pack(side= BOTTOM, fill= "x")
q="SELECT * from masten order by project asc LIMIT "+ str(offset) +","+str(limit)
r_set=my_conn.execute(q);
i=0 # row value inside the loop
for persoon in r_set:
for j in range(len(persoon)):
e = Entry(my_w, width=13, fg='black',bg="#86b9d4", font=("Arial",6))
e.grid(row=i, column=j)
e.insert(END, persoon[j])
i=i+1
while (i<limit): # required to blank the balance rows if they are less
for j in range(len(persoon)):
e = Entry(my_w, width=13, fg='black',bg="#86b9d4", font=("Arial",6))
e.grid(row=i, column=j)
e.insert(END, "")
i=i+1
# Show buttons
back = offset - limit # This value is used by Previous button
next = offset + limit # This value is used by Next button
b1 = tk.Button(my_w, text='----->',bg="#a6897c", fg="#000080", font=("Arial",12),
disabledforeground="red", activebackground='#2e7287', command=lambda: my_display(next))
b1.grid(row=30,column=13)
b2 = tk.Button(my_w, text='<-----', bg="#a6897c", fg="#000080", font=("Arial",12),
disabledforeground="red", activebackground='#2e7287', command=lambda: my_display(back))
b2.grid(row=30,column=11)
b3 = tk.Button(my_w, text='Stop', bg="#a6897c", fg="#000080", font=("Arial",12),
disabledforeground="red", activebackground='#2e7287', command=my_w.destroy)
b3.grid(row=30,column=15)
if(no_rec <= next):
b1["state"]="disabled" # disable next button
else:
b1["state"]="active" # enable next button
if(back >= 0):
b2["state"]="active" # enable Prev button
else:
b2["state"]="disabled"# disable Prev button
#Attact the scrollbar with the text widget
scroll_h.config(command = my_w.xview)
my_w.config(xscrollcommand=scroll_h.set)
my_display(0)
my_w.mainloop()

Tkinter image_create performance when inserting image into text box

I'm building a Python (3.7.4) app to display items from a text database of vendor manuals. One of the functions is to display a list of "item names" from the database for each manual (aka "product"). I'm using a Tkinter scrolled text box widget for this function and simply building rows with item names with tabs to separate them. I would like to insert a small icon (16x20 pixels) in front of each item name, and have successfully done so using the code below. The problem I am having is that for large manuals in the database (e.g., 10K+ item names) I take a significant performance hit from the image_create function. Without inserting the images, displaying the list of item names is almost instantaneous for 10K item names. With inserting the images, it may take upwards of 10-12 seconds.
Any suggestions on how to improve performance? I've even looked into creating a "image" font using FontForge, but would prefer not to go that route if possible.
Thanks in advance for your help.
Per Bryan Oakley's request, I have attached executable code here. If you un-comment the following lines:
tabContents_text.image_create("current", image=open_book_image)
you will see the difference in performance.
Link to the png file is HERE
import tkinter as tk
import tkinter.scrolledtext as tkst
win = tk.Tk()
frame1 = tk.Frame(master = win)
frame1.pack(fill='both', expand='yes')
win.state('zoomed')
tabContents_text = tkst.ScrolledText(frame1, cursor="arrow", wrap="none", tabs=150, font=('helvetica', 10, 'normal'))
tabContents_text.pack(side='top')
open_book_image = tk.PhotoImage(file="C:\\Users\\rhkea\\Downloads\\document_icon.png")
product_item_list=[]
for i in range(10000):
product_item_list.append("Item" + str(i).zfill(5))
col_count=4
row_count=2500
row_remain=0
tabContents_text.delete("1.0", "end")
row = 0
item = 0
while row < row_count:
col = 0
while col < col_count:
# tabContents_text.image_create("current", image=open_book_image)
tabContents_text.insert("end", product_item_list[item] + '\t')
col += 1
item += 1
tabContents_text.insert("end", "\n")
row += 1
col = 0
while col < row_remain:
# tabContents_text.image_create("current", image=open_book_image)
tabContents_text.insert("end", product_item_list[item] + '\t')
col += 1
item += 1
tabContents_text.insert("end", "\n")
win.mainloop()
Unfortunaly,I think there is no way to have a better performance.But you can use .update()
to show the image right away instead of blocking it in your code.
I don't know what's the purpose of second while loop
You can try this code,it will continue to load the image after you run it:
import tkinter as tk
import tkinter.scrolledtext as tkst
win = tk.Tk()
frame1 = tk.Frame(master = win)
frame1.pack(fill='both', expand='yes')
win.state('zoomed')
tabContents_text = tkst.ScrolledText(frame1, cursor="arrow", wrap="none", tabs=150, font=('helvetica', 10, 'normal'))
tabContents_text.pack(side='top')
open_book_image = tk.PhotoImage(file="document_icon.png")
product_item_list=[]
for i in range(10000):
product_item_list.append("Item" + str(i).zfill(5))
col_count=4
row_count=2500
row_remain=0
tabContents_text.delete("1.0", "end")
row = 0
item = 0
try:
while row < row_count:
col = 0
while col < col_count:
tabContents_text.image_create("end", image=open_book_image) # It's "end" instead of current.
tabContents_text.insert("end", product_item_list[item] + '\t')
win.update() # update right away.
col += 1
item += 1
tabContents_text.insert("end", "\n")
win.update()
row += 1
col = 0
# This while loop seems that it will create two empty lines
while col < row_remain:
tabContents_text.image_create("current", image=open_book_image)
tabContents_text.insert("end", product_item_list[item] + '\t')
win.update()
col += 1
item += 1
tabContents_text.insert("end", "\n")
except BaseException: # Catch the raised exception when you close the window if it doesn't load image completely.
pass
except Exception as e:
print(e.args)
win.mainloop()
Maybe use .after() is also OK.
You can combine the image and the text into a single Label and then use .window_create(...) to insert into the Text box:
font = tabContents_text.cget('font') # get the Text font
for i in range(len(product_item_list)):
# create label with both image and text
lbl = tk.Label(tabContents_text, text=product_item_list[i]+'\t',
image=open_book_image, compound='left',
bg='white', font=font)
# insert the label into text box
tabContents_text.window_create('end', window=lbl)
# insert line break after showing 'col_count' items
if i%col_count == col_count-1:
tabContents_text.insert('end', '\n')
It can reduce the total items inserted into text box by half.

How to make dynamic list changes in python?

Guy am trying to create pages for my tk GUI,so I got this code below that works perfectly,on click of next button it get the first 5 and till end of 100 and vice versa on click of prev button
import tkinter as tk
import math
items = [str(n) for n in range(100)]
page = 0
per_page = 5
n_pages = math.ceil(len(items) / per_page)
def update_list():
print(page)
start_index = int(page * per_page)
end_index = int((page + 1) * per_page)
items_in_page = items[start_index:end_index]
view_text = "Page %d/%d: %s" % (page + 1, n_pages, ", ".join(items_in_page))
def change_page(delta):
global page
page = min(n_pages - 1, max(0, page + delta))
update_list()
def prev_btn():
change_page(-1)
def next_btn():
change_page(+1)
win = tk.Tk()
win.title("clickers")
tk.Button(win, text="next", command=next_btn).pack()
tk.Button(win, text="prev", command=prev_btn).pack()
show = tk.Entry(win)
show.pack()
update_list() # to initialize `show`
win.mainloop()
I want to adjust this part of code
items = [str(n) for n in range(100)]
into a list like this,and get it working as just switching them doesn't work
list_ = [1,2,3], [4,5,6], [7,8,9], [10,11,12], [13,14,15], [16,17,18]]
items =[str(n) for n in list_]
so oncick of next button it return first 2(any number choice) list and vice versa for prev,when that's done,please note that this list originally comes from multiple rows of a database that's has been selected,not just some variable holding a list like I have it,so I need help on how to directly use the loop to get first 2 rows(2 list) from the database,and immediate past 2 rows for prev button

Function traceback says not receiving positional arguments

Issue begins at def displayAnswer
import tkinter as tk
#icftk stands for incompressible-flow toolkit
"""This program is being built to aid in getting all
parameters of a flow given certain initial conditions"""
#CREATES THE WINDOW
root = tk.Tk()
root.title("Incompressible Fluid Toolkit")
class flow:
"""This class contains all necessary parameters needed to define a flow that i
incompressible, 1D-Steady, idiabatic and encounters no energy gain or loss"""
def __init__(self,vel,diameter,density,viscosity,massflow = 0, Re = 0, newdia = 1, jetforce = 0, newvel = 0):
"""initialize a fluid with given basic measured properties"""
self.vel = vel
self.diameter = diameter
self.density = density
self.viscosity = viscosity
self.massflow = massflow # mass flow rate
self.Re = Re #Reynolds Number
self.newdia = newdia # downstream diameter for velocity change
self.jetforce = jetforce # force the stream can produce normal to a surface
self.newvel = newvel # new velocity after a cross sectional area change
def reynolds(self):
"""This function calculates reynolds
Pass ro, v, D and mu in the same unit system, in that order"""
self.Re = (self.diameter*self.vel*self.density)/(self.viscosity)
print(f"The Reynolds number for this flow is {self.Re}")
def mdot(self):
"""This function finds the mass flowrate of a flow"""
flowarea = 3.14159*(self.diameter**2) / 4
self.massflow = self.density*self.vel*flowarea
print(f"The mass flowrate is {self.massflow}")
def streamforce(self):
"""This function gives the max force that the fluid jet can apply
normal to a surface perpendicular to the flow"""
self.jetforce = self.massflow*self.vel
print(f"The maximum force the jet can apply is {self.jetforce}")
def velchange(self):
"""This function is used to determine the velocity change of
a flow when there is a change in cross sectional area of the pipe"""
newarea = 3.14159 * (self.newdia**2) / 4
self.newvel = self.massflow/(self.density*newarea)
print(f"At the location of the area change, there is a velocity change from {self.vel} to {self.newvel}")
#ALL ABOVE FUNCTIONS HAVE BEEN CONFIRMED TO WORK WITH GIVEN TEST CONDITIONS BELOW
#use test case velocity = 18.64, diameter = 0.017, density = 1.23, and viscosity = 0.0000184
#Display Entry Boxes
velo = tk.Label(root, text="Flow Velocity") # Create a text label
velo.grid(row = 0, column = 0, pady = 10) # Pack it into the window, padding determines how mach space is around a window element
veloent = tk.Entry()
veloent.grid(row = 0, column = 1, pady = 10)
diam = tk.Label(root, text="Pipe Diameter") # Create a text label
diam.grid(row = 1, column = 0, pady = 10) # Pack it into the window, padding determines how mach space is around a window element
diament = tk.Entry()
diament.grid(row = 1, column = 1, pady = 10)
dens = tk.Label(root, text="Fluid Density") # Create a text label
dens.grid(row = 2, column = 0, pady = 10) # Pack it into the window, padding determines how mach space is around a window element
densent = tk.Entry()
densent.grid(row = 2, column = 1, pady = 10)
visc = tk.Label(root, text="Fluid Viscosity") # Create a text label
visc.grid(row = 3, column = 0, pady = 10) # Pack it into the window, padding determines how mach space is around a window element
viscent = tk.Entry()
viscent.grid(row = 3, column = 1, pady = 10)
#Display answers at the bottom of the window
def displayAnswer(veloent,diament,densent,viscent):
ve = float(veloent)#gets velocity entry and turns it into a float
di = float(diament)#gets diameter entry and turns it into a float
de = float(densent)#gets density entry and turns it into a float
vi = float(viscent)#gets viscosity entry and turns it into a float
fluid = flow(ve,di,de,vi)
fluid.reynolds()
fluid.mdot()
fluid.streamforce()
reynoldsanswer = tk.Label(root, text = "f{fluid.reynolds}")
reynoldsanswer.grid(row = 5)
mdotanswer = tk.Label(root, text = "f{fluid.mdot}")
mdotanswer.grid(row = 6)
streamforceanswer = tk.Label(root, text = "f{fluid.streamforce}")
streamforceanswer.grid(row = 7)
calculatebutton = tk.Button(root,command = displayAnswer)
calculatebutton.grid(row = 4)
root.mainloop()
I am new to tkinter, trying to get experience designing simple GUI. I am using a button to initiate a calculation to obtain values about an incompressible flow. When the button is pressed, the console throws this error.
Exception in Tkinter callback
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/tkinter/__init__.py", line 1705, in __call__
return self.func(*args)
TypeError: displayAnswer() missing 4 required positional arguments: 'veloent', 'diament', 'densent', and 'viscent'
Similarly, if I try to convert the Entry into a float from a string outside of the function, the console throws a cannot convert string to float error.
Honestly not sure if all of the code is even right, but I'll cross these bridges one at a time.
Any insight is appreciated.
Best,
T
The problem is that your function requires parameters, but never uses them. When you call the function from a button, the button will not by default pass any options. That's why you get missing 4 required positional arguments - the function requires four, the button passes zero.
Since your function is actually doing the right thing and fetching the values it needs, there's no need to pass in the parameters. The simple fix is to simply remove them from the definition of the function:
def displayAnswer():
ve = float(veloent)#gets velocity entry and turns it into a float
di = float(diament)#gets diameter entry and turns it into a float
de = float(densent)#gets density entry and turns it into a float
vi = float(viscent)#gets viscosity entry and turns it into a float
...

How to print the same text multiple times(5 times) in tkinter/GUI?

from tkinter import *
root=Tk()
textbox=Text(root)
textbox.pack()
button1=Button(root, text='Output Name', command=lambda : print('Hello'))
button1.pack()
def redirector(inputStr):
textbox.insert(INSERT, inputStr)
sys.stdout.write = redirector
root.mainloop()
This is my code with out a timer to do it five times.
This seems a little bit like homework, so lets try to get you on the right track over outright providing the code to accomplish this.
You're going to want to create a loop that performs your code a certain number of times. Let's say we just want to output a certain string 5 times. As an example, here's some really simple code:
def testPrint():
print('I am text!')
for i in range(5):
testPrint()
This will create a function called testPrint() that prints text "I am Text!", then run that function 5 times in a loop. If you can apply this to the section of code you need to run 5 times, it should solve the problem you are facing.
This worked for me. It creates a table using the .messagebox module. You can enter your name into the entry label. Then, when you click the button it returns "Hello (name)".
from tkinter import *
from tkinter.messagebox import *
master = Tk()
label1 = Label(master, text = 'Name:', relief = 'groove', width = 19)
entry1 = Entry(master, relief = 'groove', width = 20)
blank1 = Entry(master, relief = 'groove', width = 20)
def show_answer():
a = entry1.get()
b = "Hello",a
blank1.insert(0, b)
button1 = Button(master, text = 'Output Name', relief = 'groove', width = 20, command =show_answer)
#Geometry
label1.grid( row = 1, column = 1, padx = 10 )
entry1.grid( row = 1, column = 2, padx = 10 )
blank1.grid( row = 1, column = 3, padx = 10 )
button1.grid( row = 2, column = 2, columnspan = 2)
#Static Properties
master.title('Hello')

Resources