How to make dynamic list changes in python? - python-3.x

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

Related

Creating a 4 x 5 grid of images within Report Lab - Python

I'm attempting to use ReportLab to create a 4 x 5 grid of images (per page) from a directory of images, in a similar manner to photographic contact sheets. I need to maintain the aspect ratio of the images and also need the filename of each image underneath.
I originally started by using drawimage and adding everything manually but now I think a table and adding the image and filename into each cell might be a better approach. Can anyone give me some pointers on the best way to do this?
I've spent a few days trying to figure this out and I think I am going around in circles with this. Thank you in advance!
Script as of now;
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle
from reportlab.lib.pagesizes import A4
from PIL import Image
import os
def rowGen(lst, n):
for i in range(0, len(lst), n):
yield lst[i:i + n]
def makePDF(document_title, data):
pdf = SimpleDocTemplate(
document_title,
pagesize = A4
)
style = TableStyle([
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('BOTTOMPADDING', (0, 0), (-1, -1), 12),
])
table = Table(data) #Creates and adds the data to the table
table.setStyle(style) #Sets style to the above defined style
elements = [] #Creates an empty elements list
elements.append(table) #Lays out the elements
pdf.build(elements)
path = 'Images/'
image_list = [i for i in os.listdir(path) if i.endswith('.jpg')] #list of images in selected directory
data = [i for i in rowGen(image_list, 4)]
makePDF('Test.pdf', data)
So, after a few days of working through some ideas I came up with the below. I am fairly new to this and I'm sure there are better approaches but if it helps anybody:
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib.utils import ImageReader
from PIL import Image
import os
path = 'path/to/my/images/'
filename = 'test.pdf'
title = 'Test'
def createPDF(path_to_images, document_name, document_title):
def rowGen(list_of_images): #Creates a list of 4 image rows
for i in range(0, len(list_of_images), 4):
yield list_of_images[i:i + 4]
def renderRow(path_to_images, row, y_pos): #Renders each row to the page
x_pos = 5 #starting x position
thumb_size = 180, 180 #Thumbnail image size
for i in row:
image_filename = i #Saves the filename for use in the draw string operation below
img = Image.open(os.path.join(path_to_images, i)) #Opens image as a PIL object
img.thumbnail(thumb_size) #Creates thumbnail
img = ImageReader(img) #Passes PIL object to the Reportlab ImageReader
#Lays out the image and filename
pdf.drawImage(img, x_pos * mm , y_pos * mm, width = 125, height = 125, preserveAspectRatio=True, anchor='c')
pdf.drawCentredString((x_pos + 22) * mm, (y_pos - 7) * mm, image_filename)
x_pos += 51 #Increments the x position ready for the next image
images = [i for i in os.listdir(path_to_images) if i.endswith('.jpg')] #Creates list of images filtering out non .jpgs
row_layout = list(rowGen(images)) #Creates the layout of image rows
pdf = canvas.Canvas(document_name, pagesize=A4, pageCompression=1)
pdf.setTitle(document_title)
rows_rendered = 0
y_pos = 250 #Sets starting y pos
pdf.setFont('Helvetica', 10)
for row in row_layout: #Loops through each row in the row_layout list and renders the row. For each 5 rows, makes a new page
if rows_rendered == 5:
pdf.showPage()
pdf.setFont('Helvetica', 10)
y_pos = 250
rows_rendered = 0
else:
renderRow(path_to_images, row, y_pos)
y_pos -= 60
rows_rendered += 1
pdf.save()
createPDF(path, filename, title)

Tkinter Button to creat entries

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.

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.

ValueError with entry-field. Tkinter math-program

writing a tkinter program for my kids to do some math.
It first shows a label with the math-question, for example "What is 5 + 3?"
Then an inputfield (entry) shows to get the answer (entry-field checks if integers are entered)
A Button is created to start a function to check the solution.
Function to create random numbers and create entry-field:
def Do_Math(self):
nr1 = random.choice(self.numbers) #random number from list
nr2 = random.choice(self.numbers) #random number from list
do_math = nr1 + nr2 #do the math
txt_som = Label(tk, text = 'wat is de som van ' + str(nr1) + ' en ' + str(nr2) + ' ?', state='normal', \
font=('Courier', 14, "bold"), justify=LEFT, padx = 10, background='LightCyan2')
txt_som.place(x = 20, y = 170)
tk.update()
oplossing = Entry(tk, validate="key") #create input-field #validate to check for integer
oplossing['validatecommand'] = (oplossing.register(self.testVal),'%P','%d') #validate to check for integer
oplossing.place(x = 20, y = 200) #place inputfield on screen
oplossing.focus_set() #set focus so user can directly input.
self.oplossing = oplossing
self.do_math = do_math
#knop voor ingeven oplossing:
knop_oplossing = Button(tk, text='Ok?', command=self.solution) #create button
knop_oplossing.place(x = 20, y = 250) #place button on screen
tk.update()
Function to check the solution:
If the solution is good then the above function runs again to create new numbers.
def solution(self):
while True:
if int(self.oplossing.get()) == self.do_math:
#self.oplossing.delete(0, END) #clear entr-value
play.random_sound('good')
self.lbl.place(self.lbl.pi) #Place label
self.lbl_w.place_forget() #Hide label
tk.update()
#global score
#score = score + 1
#print('GOED GEDAAN!', score, 'van', asked_maths, 'pogingen goed')
game.Do_Math() #Choose new random numbers
elif int(self.oplossing.get()) != self.do_math:
play.random_sound('wrong')
self.lbl.place_forget() #Hide label
self.lbl_w.place(self.lbl_w.pi) #Place label
tk.update()
break #Go out the while loop and do nothing
Every runs ok, but in the background i get following error every time the solution is good:
if int(self.oplossing.get()) == self.do_math:
ValueError: invalid literal for int() with base 10: ''
I think the problem is that the function "solution" tries this:
int(self.oplossing.get()) == self.do_math
but the entry-field has no value jet.
What do you think? I wanna solve these error messages before going further.
Thnx in advanced!

Having an issue getting TKinter to track mouse movement to an object

I was unsure whether to post the full code or not, but here's what I have:
from tkinter import *
from random import randint
HEIGHT = 500
WIDTH = 800
MID_X = WIDTH/2
MID_Y = HEIGHT/2
SHIP_R = 15
SHIP_SPD = 10
bub_id = list()
bub_r = list()
bub_speed = list()
MIN_BUB_R = 10
MAX_BUB_R = 30
MAX_BUB_SPD = 6
GAP = 100
window = Tk()
window.title('Bubble Blaster')
c = Canvas(window, width=WIDTH, height=HEIGHT, bg='darkblue')
c.pack()
ship_id = c.create_polygon(5, 5, 5, 25, 30, 15, fill='red')
ship_id2 = c.create_oval(0, 0, 30, 30, outline='red')
c.move(ship_id, MID_X, MID_Y)
c.move(ship_id2, MID_X, MID_Y)
def move_ship(event):
fixed = True
while fixed == True:
ship_x, ship_y = event.x, event.y
c.move(ship_id, ship_x, ship_y)
c.move(ship_id2, ship_x, ship_y)
sleep(0.01)
def create_bubble():
x = WIDTH + GAP
y = randint(0, HEIGHT)
r = randint(MIN_BUB_R, MAX_BUB_R)
id1 = c.create_oval(x-r, y-r, x+r, y+r, outline='white')
bub_id.append(id1)
bub_r.append(r)
bub_speed.append(randint(1, MAX_BUB_SPD))
def move_bubbles():
for i in range(len(bub_id)):
c.move(bub_id[i], -bub_speed[i], 0)
def get_coords(id_num):
pos = c.coords(id_num)
x = (pos[0] + pos[2])/2
y = (pos[1] + pos[3])/2
return x, y
def del_bubble(i):
del bub_r[i]
del bub_speed[i]
c.delete(bub_id[i])
del bub_id[i]
def clean_up_bubs():
for i in range(len(bub_id)-1, -1, -1):
x, y = get_coords(bub_id[i])
if x < -GAP:
del_bubble(i)
from math import sqrt
def distance(id1, id2):
x1, y1 = get_coords(id1)
x2, y2 = get_coords(id2)
return sqrt((x2-x1)**2 + (y2-y1)**2)
def collision():
points = 0
for bub in range(len(bub_id)-1, -1, -1):
if distance(ship_id2, bub_id[bub]) < (SHIP_R+bub_r[bub]):
points += (bub_r[bub] + bub_speed[bub])
del_bubble(bub)
return points
c.create_text(50, 30, text='TIME', fill='white')
c.create_text(150, 30, text='SCORE', fill='white')
time_text = c.create_text(50, 50, fill='white')
score_text = c.create_text (150, 50, fill='white')
def show_score(score):
c.itemconfig(score_text, text=str(score))
def show_time(time_left):
c.itemconfig(time_text, text=str(time_left))
from time import sleep, time
BUB_CHANCE = 20
TIME_LIMIT = 30
BONUS_SCORE = 1000
# MAIN GAME LOOP
c.bind("<B1_Motion>", move_ship)
score = 0
bonus = 0
end = time() + TIME_LIMIT
while time() < end:
if randint(1, BUB_CHANCE) == 1:
create_bubble()
move_bubbles()
move_ship("<B1_Motion>")
clean_up_bubs()
score += collision()
if (int(score / BONUS_SCORE)) > bonus:
bonus += 1
end += TIME_LIMIT
show_score(score)
show_time(int(end-time()))
window.update()
sleep(0.01)
c.create_text(MID_X, MID_Y, \
text='PARTY TIME, EXCELLENT', fil='white', font=('Helvetica', 30))
c.create_text(MID_X, MID_Y + 30, \
text='Score: ' + str(score), fill='white')
c.create_text(MID_X, MID_Y + 45, \
text='BONU TIME: ' + str(bonus*TIME_LIMIT), fill='white')
I'm a complete beginner when it comes to python, and have been given an assignment to only use tkinter and the standard libraries to give mouse movement to this "game". I just can't seem to get the right grasp of it. Any suggestions would be appreciated!
The first step is to remove your while loop, and put most of your functionality into a function. Put everything you want to do in a single frame of animation into this function.
Next, call this function on a regular interval using the after command. This allows the event loop to run continuously, which is important for your UI to be responsive. You can do your time() < end calculation inside this function, and use the result to break the cycle once time is up.
It looks something like this:
def draw_one_frame():
if randint(1, BUB_CHANCE) == 1:
create_bubble()
move_bubbles()
# move_ship("<B1_Motion>")
clean_up_bubs()
score += collision()
if (int(score / BONUS_SCORE)) > bonus:
bonus += 1
end += TIME_LIMIT
show_score(score)
show_time(int(end-time()))
if time() < end:
after(10, draw_one_frame)
You can use the first parameter to after to control how many frames per second you wish your program to run at.
Next, you need to handle mouse movement. You do this by creating a binding to the movement of the mouse. You do this outside of the draw_one_frame method. It looks something like this:
c.bind("<B1-Motion>", move_ship)
You also need to remove the infinite loop from move_ship, and also remove the sleep. You simply need to do all the calculations for the current mouse position. The function will be already be looping -- being called once each time the mouse moves.
Finally, you need to call window.mainloop() after all your other code, so that the program can process events. This should be the very last line of your program. It will run until the window is destroyed.

Resources