Tkinter image_create performance when inserting image into text box - python-3.x

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.

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()

How can I simplify my python turtle game to avoid it from taking larger memory as the running time increases?

This is a color-flipping game based on the python turtle module.
The game is composed of a rectangular board divided into a number of flat colored tiles of various colors. The goal of the game is to turn all tiles to the same color by choosing one tile on the board and then changing its color to another color. The newly selected color will spread to all its neighboring tiles which match the original color of the chosen tile. The player continues selecting another tile and changing its color to another until all the tiles within the board flipped to the same color. In addition to the rectangular board, an array of colors is shown below the board. These are the colors to which the player can choose to flip the color of the selected tile.
As I started to run my game, the memory it took up was getting larger and larger as the running time increased. And it's getting slower and slower. I think it might be due to the while loop at the end of my code. But I'm not sure. How can I modify it to make it faster?
from turtle import *
from random import choice
from functools import partial
# set game dimension to be 5 x 5
g_dim = 5
# use random.choice() to create the color for the game
g_game = [choice(['#0000FF', '#FF0000', '#FFFF00', '#008000', '#00FFFF']) for i in range(25)]
# provide the option to flip during the game
optionColor = ['#0000FF', '#FF0000', '#FFFF00', '#008000', '#00FFFF']
# show a set of colors as option for user to flip
def promptColorToFlip(optionColor, # a list that contains a set of the color for user to choose from
height=100, # the height of the option tiles
width=100 # the width of the option tiles
):
# the coordinates of the first tiles
x = -200
y = -200
for i in range(len(optionColor)):
tile = prototype.clone()
tile.goto(i * width + x, -(height+50) + y)
tile.color('white', optionColor[i])
tile.onclick(partial(returnChosenColor, i))
# return the index of the select-to-flip-to color in the optionColor list
def returnChosenColor(userChosenColor, # the index of the select-to-flip-to color in the optionColor list
x, y # take the positional arguments from the onclick() function to avoid errors, no significant meaning
):
global userOption
userOption = userChosenColor
def refreshScreen(game, rows=5, columns=5, height=100, width=100):
x = -200
y = -200
for column in range(columns):
for row in range(rows):
square = prototype.clone()
square.goto(column * (5+width) + x , row * (5+height) + y)
square.onclick(partial(userChosenTile, row, column))
if state['framed'] == row*5+column:
square.color('black', game[row*5+column])
else:
square.color('white', game[row*5+column])
update()
def userChosenTile(ROW, COL, x, y):
global state, R, C
state['framed'] = ROW*5+COL
R = ROW
C = COL
def flipColor(row, col, game, orig, to):
print('excuted')
global userOption, state, R, C
if orig == to:
return game
if row < 0 or row >= g_dim:
return
if col < 0 or col >= g_dim:
return
idx = row*g_dim+col
if game[idx] != orig:
return
print(idx, 'excuted')
game[idx] = to
flipColor(row-1, col, game, orig, to)
flipColor(row+1, col, game, orig, to)
flipColor(row, col-1, game, orig, to)
flipColor(row, col+1, game, orig, to)
state = {'framed':None}
R = None
C = None
userOption = None
return game
# initialize the game status
state = {'framed':None} # stores the number of the last selected tile, which will be framed with a black border
R = None # the row of the last selected tile
C = None # the column of the last selected tile
userOption = None # the index of the select-to-flip-to color in the optionColor list
# create a prototype of the tiles
prototype = Turtle()
prototype.shape('square')
prototype.shapesize(5, 5, 5)
prototype.penup()
# disable auto screen refresh
tracer(False)
# run the game
while True:
# the try and except block here is to prevent error from raising when user terminate the progarm
try:
promptColorToFlip(optionColor)
refreshScreen(g_game)
if state['framed'] is not None and R is not None and C is not None and userOption is not None:
g_game = flipColor(R, C, g_game, g_game[state['framed']], optionColor[userOption])
except:
pass
I haven't fully worked through your somewhat complex code, but I'm pretty sure I see the source of your progressive slowdown as the game runs for a while. It's right here, in your refreshScreen function:
for column in range(columns):
for row in range(rows):
square = prototype.clone()
...
This code makes a new turtle for each square of your board on every frame. Those turtles never go away, so you just keep on stacking up more and more of them, which slows down the performance of the whole game over time. The solution is probably to use only one turtle for each location, and keep references to them so that you can change their colors when necessary, rather than making them anew each time.

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

How to make a button Stay where it is when text is changing length

So i Have been trying to make this button stay in the same position but when ever the text changes its length the button always moves
i have tried removing the row and column values that doesn't work
i have tried changing the row and column values that doesn't work
so i don't really know what to do
from tkinter import *
win = Tk()
win.title('ab')
win.config(bg='blue')
win.geometry('200x100')
i = 0
def changetext():
global i
i = i + 1
if i == 1:
lbl.config(text='a')
if i == 2:
lbl.config(text='ab')
if i == 3:
lbl.config(text='abc')
if i == 4:
lbl.config(text='abcd')
if i == 5:
lbl.config(text='abcde')
lbl = Label(win,text='111111111111', font=("Courier", 9))
lbl.grid(row=1,column=2)
btn = Button(win,text='u', command =changetext)
btn.grid(row=2,column=2)
win.mainloop()
When you make the button (or pretty much any widget for that matter), you can define its width.
lbl = Label(win, width= 20, text = '111111111111', font = ("Courier",9))
btn = Button(win, width = 20, text = 'u', command = changetext)

How to change font size of text within a table created by Python-pptx

I am creating a script to show graphs of product performance and creating a table to display its partnumber, a list of applications and the number of current applications.
However, the default font size is much too large to get all of this information into the slide and needs to be reduced.
How do I reduce the font size of text within a table in Python-pptx?
This is what I have, but I keep getting an error "AttributeError: '_Cell' object has no attribute 'paragraph'"
table = shapes.add_table(rows, cols, left + left_offset, top + Inches(.25), width, height - Inches(.25)).table
#column width
for i in range(3):
table.columns[i].width = col_width[i]
for i in range(len(a_slide)):
#color table
if i % 2 == 0:
for j in range(3):
fill = table.cell(i, j).fill
fill.background()
else:
for j in range(3):
fill = table.cell(i, j).fill
fill.solid()
fill.fore_color.rgb = RGBColor(240, 128, 128)
#populate table
table.cell(i, 0).text = str(item["name"])
try:
table.cell(i, 1).text = ", ".join(item["app"])
except:
table.cell(i, 1).text = " "
finally:
table.cell(i, 2).text = str(item["vio"])
for j in range(0,3):
font = table.cell(i, j).paragraph[0].font
font.size = Pt(12)
A _Cell object doesn't directly contain paragraphs. However, it does include a TextFrame object on .text_frame which contains the paragraphs. So if you just use:
cell.text_frame.paragraphs[0]
..you should get what you expect. Note that it's .paragraphs, not .paragraph.
The API documentation for _Cell is here:
http://python-pptx.readthedocs.io/en/latest/api/table.html#cell-objects
and generally provides all the details needed to resolve the finer points like this one.

Resources