Data doesn't wrap in tkinter Entry widget - python

I have data which looks like this.
tempList2=[{'Date': '21-Aug-2019', 'Day': 'Sunday', 'Status': 'This is the message. It should be wrapped!!'}, {'Date': '22-Aug-2019', 'Day': 'Monday', 'Status': 'Message Delivered'}, {'Date': '23-Aug-2019', 'Day': 'Tuesday', 'Status': 'Invalid Data found!! Please retry'}]
Next I am creating a dataframe for that Data. After that, I am using tkinter module to view the data.
Here's the Sample Code:
import pandas as pd
import tkinter as tk
a =[]
tempList2=[{'Date': '21-Aug-2019', 'Day': 'Sunday', 'Status': 'This is the message. It should be wrapped!!'}, {'Date': '22-Aug-2019', 'Day': 'Monday', 'Status': 'Message Delivered'}, {'Date': '23-Aug-2019', 'Day': 'Tuesday', 'Status': 'Invalid Data found!! Please retry'}]
for i in tempList2:
print(i)
print(type(i))
b = list(i.values())
a.append(b)
print(a)
tempList = a
df = pd.DataFrame(tempList)
# --- functions ---
def change(event, row, col):
# get value from Entry
value = event.widget.get()
# set value in dataframe
df.iloc[row,col] = value
print(df)
# --- main --
root = tk.Tk()
# create entry for every element in dataframe
rows, cols = df.shape
for r in range(rows):
for c in range(cols):
e = tk.Entry(root)
e.insert(0, df.iloc[r,c])
e.grid(row=r, column=c)
# ENTER
e.bind('<Return>', lambda event, y=r, x=c: change(event,y,x))
# ENTER on keypad
e.bind('<KP_Enter>', lambda event, y=r, x=c: change(event,y,x))
# start program
root.mainloop()
Here's the output image I got:
In the Output, the data doesn't get wrapped up. The data gets breaked. I need the data to be wrapped up with in the certain row. I have lots of data to be viewed. So I need a Scrollbar to access it. Help me with some solutions to wrap up the data and a scrollbar attached to the entire window.

As #stovfl commended, you can use tk.Text, which supports word wrap via the WORD keyword argument.
tk.Text(root, height=col_height, width=col_width, wrap=tk.WORD)
Two issues to handle:
Adjusting the height of all columns in a row to be the same.
You can find the largest string in a row and determine the height of the column based on that.
On Enter key the Text field will move the cursor down, sometimes hiding the text.
Strip the text and update the column on Enter key.
Code
# ...
def change(event, row, col):
# get value from Entry
value = event.widget.get("1.0", tk.END)
# update column & set value in dataframe
event.widget.delete("1.0", tk.END)
event.widget.insert("1.0", value.strip())
df.iloc[row, col] = value.strip()
print(df)
root = tk.Tk()
rows, cols = df.shape
col_width = 30
for r in range(rows):
longest_string = max(df.loc[r].values, key=len)
# If you know last element will be longest,
# No need to calculate longest string
# col_height = len(df.loc[r].values[-1])//col_width +1
col_height = len(longest_string)//col_width + 1
# or int(len(col_text)/col_width)+1
for c in range(cols):
col_text = df.iloc[r, c]
e = tk.Text(root, height=col_height, width=col_width, wrap=tk.WORD)
e.insert("1.0", df.iloc[r, c])
e.grid(row=r, column=c)
e.bind('<Return>', lambda event, y=r, x=c: change(event, y, x))
e.bind('<KP_Enter>', lambda event, y=r, x=c: change(event, y, x))
# ...

Related

Listbox in Tkinter: output value based on input by using corresponding Excel columns

I'm aware that my code isn't very clean, , my primary focus at the moment is to make the program work.
I’m working with Tkinter and I created a search- and listbox based on a column in Excel. The Excelfile is imported by pandas, as a dataframe. The idea is that people can search for something (for example ‘Eiffel Tower’), that the value (‘Eiffel Tower’) is selected and that Python gives the construction date as output (so for example the year 1889) in the interface.
You search and make sure that the value is visible in the entrybox, and then you click on a button. After clicking on the button, you will see ‘1889’.
Both the buildings as the construction dates are listed in an Excelfile. Column A contains the buildings, column B contains the construction dates.
The search and listbox works. But I’m not ably to connect column A to column B, or to get an output based on the input that the uses gives.
The 'output_Startdate' was to test if the if-statement worked (what it does). The 'def connectie()' is me trying to find a solution.
My code:
import tkinter as tk
from tkinter import *
from tkinter import Listbox
from tkinter import ttk
import pandas as pd
interface = tk.Tk()
interface.configure(bg="#60c1c9")
interface.geometry('1500x750')
interface.title('Construction Dates')
title = Label(interface, text='1. BUILDINGS')
title.configure(bg="#60c1c9", fg="#000000", font=("Calibri", 20, "bold"))
title.place(relx=0.15, rely=0, anchor=N)
file_name = “List_Buildings.xlsx”
xl_workbook = pd.ExcelFile(file_name)
df = xl_workbook.parse(“Buildings”)
alist = df['MONUMENT'].tolist()
Startdate = df['Startdate'].tolist()
Enddate = df['Enddate'].tolist()
Label(
text="Select what you see on the picture.",
bg="#60c1c9",
fg="#000000",
font=("Calibri", 12)
).place(relx=0.29, rely=0.05, anchor=N)
def update(data):
my_list_1.delete(0, END)
for entry in data:
my_list_1.insert(END, entry)
def check(e):
typed = entry_1.get()
if typed == '':
data = alist
else:
data = []
for item in alist:
if typed.lower() in item.lower():
data.append(item)
update(data)
def fillout(e):
entry_1.delete(0, END)
entry_1.insert(0, my_list_1.get(ACTIVE))
entry_1 = Entry(interface, width=53)
entry_1.place(relx=0.205, rely=0.12, anchor=N)
entry_1.bind('<KeyRelease>', check)
my_list_1: Listbox = Listbox(interface, height=20, width=50)
my_list_1.place(relx=0.2, rely=0.15, anchor=N)
my_list_1.bind("<<ListboxSelect>>", fillout)
scrollbar_v = Scrollbar(interface, orient=VERTICAL, command=my_list_1.yview)
scrollbar_v.place(relx=0.301, rely=0.151, height=324)
scrollbar_h = Scrollbar(interface, orient=HORIZONTAL, command=my_list_1.xview)
scrollbar_h.place(relx=0.0985, rely=0.583, width=320.5)
#alist = df['MONUMENT'].tolist()
#output = df['Startdate'].tolist()
#df2 = pd.DataFrame(columns=['MONUMENT', 'Startdate', 'Enddate'])
#df2 = df.apply(lambda x: df['MONUMENT'] == df['Startdate'])
#print(df2)
def connectie():
value = entry_1.get()
for i in df['MONUMENT']:
if value == alist:
BLOCK_NAME.set(output)
return
def output_Startdate():
if entry_1.get() == ‘Eiffeltower’:
tekst = tk.Label(interface, text="good")
tekst.place(relx=0.3, rely=0.8)
else:
tekst = tk.Label(interface, text="this value doesn't excist")
tekst.place(relx=0.3, rely=0.8)
button = Button(interface, text='click here', command=output_Startdate)
button.place(relx=0.29, rely=0.7)
interface.mainloop()
I'm not sure what your data looks like (you didn't hand us a sample), so I hope I did it right. There are two parts to my answer, the first is for loading the file and setting the index column (I hope the names are all unique), and the second part is how to loc for the data you are looking for.
file_name = 'List_Buildings.xlsx' # file name
# read the file's Sheet1 and create dataframe with column 'MONUMENT' as index
df = pd.read_excel(file_name, 'Sheet1', index_col='MONUMENT')
# create alist from the index
alist = df.index.tolist()
def output_Startdate():
# get the entry_1 value
monument = entry_1.get()
# use monument (the entry_1 value) as index for dataframe loc and 'Startdate' as the column
start_date = df.loc[monument, 'Startdate']
# set the text for the label
tekst = tk.Label(interface, text=f"Start date: {start_date}")
tekst.place(relx=0.3, rely=0.8)

Make an editable table in PySimpleGUI?

Hello I am using a Table element from PySimpleGUI. I would like for the user to be able to edit the data inside it.
I have seen some mentions of it. Is it possible? This guy was using PySimpleGUIQt, while I am using the PySimpleGUI on top of tkinter.
It's much difficult for me to build it by pure PySimpleGUI code.
If go it with new class inherited from sg.Table, some variables as argument or global variables not required.
Here, colors of cell not considered in Entry.
All tkinter code in function edit_cell of following example.
import PySimpleGUI as sg
import random, string
# ------ Some functions to help generate data for the table ------
def word():
return ''.join(random.choice(string.ascii_lowercase) for i in range(10))
def number(max_val=1000):
return random.randint(0, max_val)
def make_table(num_rows, num_cols):
data = [[j for j in range(num_cols)] for i in range(num_rows)]
data[0] = [word() for _ in range(num_cols)]
for i in range(0, num_rows):
data[i] = [i, word(), *[number() for i in range(num_cols - 1)]]
return data
def edit_cell(window, key, row, col, justify='left'):
global textvariable, edit
def callback(event, row, col, text, key):
global edit
widget = event.widget
if key == 'Return':
text = widget.get()
print(text)
widget.destroy()
widget.master.destroy()
values = list(table.item(row, 'values'))
values[col] = text
table.item(row, values=values)
edit = False
if edit or row <= 0:
return
edit = True
root = window.TKroot
table = window[key].Widget
text = table.item(row, "values")[col]
x, y, width, height = table.bbox(row, col)
frame = sg.tk.Frame(root)
frame.place(x=x, y=y, anchor="nw", width=width, height=height)
textvariable = sg.tk.StringVar()
textvariable.set(text)
entry = sg.tk.Entry(frame, textvariable=textvariable, justify=justify)
entry.pack()
entry.select_range(0, sg.tk.END)
entry.icursor(sg.tk.END)
entry.focus_force()
entry.bind("<Return>", lambda e, r=row, c=col, t=text, k='Return':callback(e, r, c, t, k))
entry.bind("<Escape>", lambda e, r=row, c=col, t=text, k='Escape':callback(e, r, c, t, k))
def main_example1():
global edit
edit = False
# ------ Make the Table Data ------
# sg.Print('Creating table...')
data = make_table(num_rows=1_000, num_cols=6)
# headings = [str(data[0][x])+' ..' for x in range(len(data[0]))]
headings = [f'Col {col}' for col in range(len(data[0]))]
# sg.Print('Done creating table. Creating GUI...')
sg.set_options(dpi_awareness=True)
layout = [[sg.Table(values=data, headings=headings, max_col_width=25,
auto_size_columns=True,
# display_row_numbers=True,
justification='right',
num_rows=20,
alternating_row_color=sg.theme_button_color()[1],
key='-TABLE-',
# selected_row_colors='red on yellow',
# enable_events=True,
# select_mode=sg.TABLE_SELECT_MODE_BROWSE,
expand_x=True,
expand_y=True,
enable_click_events=True, # Comment out to not enable header and other clicks
)],
[sg.Button('Read'), sg.Button('Double'), sg.Button('Change Colors')],
[sg.Text('Cell clicked:'), sg.T(k='-CLICKED-')]]
window = sg.Window('Table Element - Example 1', layout, resizable=True, finalize=True)
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Exit'):
break
elif isinstance(event, tuple):
cell = row, col = event[2]
window['-CLICKED-'].update(cell)
edit_cell(window, '-TABLE-', row+1, col, justify='right')
window.close()
main_example1()
I would strongly suggest use MutiLine for showing table, which is much simpler to manage and can be easily updated etc. The Same table effects can be achieved by less lines of code. Giving a sample code here from my own database program with uses Sqlite3 table.
def ViewTable (TableNme):
try: # LOAD TABLE FROM Sqlite3 Database
txt2="SELECT * FROM {} ".format(TableNme)
cursor.execute(txt2)
##Include headers###
txt2='S.No '
for j in fields: txt2=txt2+j +" "
txt2=txt2+'\n'
#converts the table into string separated by Comma
#fine as long as table is not very massive running more than 3 Mbs, having more than 1000 records
while True:
h1=cursor.fetchone()
if not h1: break
for j in h1:txt2=txt2+str(j)+" "
txt2=txt2+'\n'
#sg.popup('Archieved table', txt2)
#Define text to load by text= before calling me
layout2 = [[sg.Multiline(txt2,size=(28,28),key='-Items-'),],[sg.Ok()] ]
sg.Window('Scroll to see the whole database', layout2,finalize=True).read(close=True)
except:
sg.popup_error('Failed','Access denied or Memory full.')

How do I implement a find and replace function in a tkinter Text widget?

I have the following code:
def find_and_replace(self, *args):
findandreplace = tk.Toplevel(master)
findandreplace.title('Find & Replace')
find_label = tk.Label(findandreplace, text='Find')
find_label.pack(side = tk.LEFT)
find_words = tk.StringVar()
find_entry = tk.Entry(findandreplace, textvariable=find_words)
find_entry.pack(side = tk.LEFT, fill = tk.BOTH, expand = 1)
find_button = tk.Button(findandreplace, text='Find', command=self.find)
find_button.pack(side = tk.LEFT)
replace_label = tk.Label(findandreplace, text='Replace')
replace_label.pack(side = tk.LEFT)
replace_words = tk.StringVar()
replace_entry = tk.Entry(findandreplace, textvariable=replace_words)
replace_entry.pack(side = tk.LEFT, fill = tk.BOTH, expand = 1)
replace_button = tk.Button(findandreplace, text='Replace', command=self.replace)
replace_button.pack(side = tk.LEFT)
find_string = find_words.get()
replace_string = replace_words.get()
return find_string, replace_string
def find(self, *args):
self.textarea.tag_remove('found', '1.0', tk.END)
find_word = self.find_and_replace()[0]
if find_word:
idx = '1.0'
while True:
idx = self.textarea.search(find_word, idx, nocase=1,
stopindex=tk.END)
if not idx:
break
lastidx = '% s+% dc' % (idx, len(find_word))
idx = lastidx
self.textarea.tag_config('found', foreground='red')
def replace(self, *args):
self.textarea.tag_remove('found', '1.0', tk.END)
find_word = self.find_and_replace()[0]
replace_word = self.find_and_replace()[1]
if find_word and replace_word:
idx = '1.0'
while True:
idx = self.textarea.search(find_word, idx, nocase=1,
stopindex=tk.END)
if not idx:
break
lastidx = '% s+% dc' % (idx, len(find_word))
self.textarea.delete(idx, lastidx)
self.textarea.insert(idx, replace_word)
lastidx = '% s+% dc' % (idx, len(replace_word))
idx = lastidx
self.textarea.tag_config('found', foreground='green', background='yellow')
And I am using a menubar in a different class to access this:
edit_dropdown.add_command(label="Find",
command=parent.find)
edit_dropdown.add_command(label="Replace",
command=parent.find_and_replace)
So, find_and_replace() creates a new tk.Toplevel widget where I can access the find() and replace() functions.
However, when I press on the respective buttons, all I get is two more windows created. I want to highlight the find_words string and then have it replaced by the replace_words string.
I feel I'm messing up by accessing variables of one method in another and in the opposite manner.
The root of your problem are these lines:
find_word = self.find_and_replace()[0]
replace_word = self.find_and_replace()[1]
self.find_and_replace creates the dialog. You don't want to be creating new dialogs. Instead, you need to access the widgets in the current dialog. That means you need to save references to the widgets as instance attributes, and then use those instance attributes.
Note: you can reduce the complexity by not using the textvariable attribute. You're just adding overhead since you aren't taking advantage of any features of the variable that you can't do by just calling the entry widget directly
For example:
def find_and_replace(self, *args):
...
self.find_entry = tk.Entry(findandreplace)
self.replace_entry = tk.Entry(findandreplace)
...
def replace(self, *args):
...
find_word = self.find_entry.get()
replace_word = self.replace_entry.get()
...
Something like this should do the trick:
from tkinter import *
# to create a window
root = Tk()
# root window is the parent window
fram = Frame(root)
# Creating Label, Entry Box, Button
# and packing them adding label to
# search box
Label(fram, text ='Find').pack(side = LEFT)
# adding of single line text box
edit = Entry(fram)
# positioning of text box
edit.pack(side = LEFT, fill = BOTH, expand = 1)
# setting focus
edit.focus_set()
# adding of search button
Find = Button(fram, text ='Find')
Find.pack(side = LEFT)
Label(fram, text = "Replace With ").pack(side = LEFT)
edit2 = Entry(fram)
edit2.pack(side = LEFT, fill = BOTH, expand = 1)
edit2.focus_set()
replace = Button(fram, text = 'FindNReplace')
replace.pack(side = LEFT)
fram.pack(side = TOP)
# text box in root window
text = Text(root)
# text input area at index 1 in text window
text.insert('1.0', '''Type your text here''')
text.pack(side = BOTTOM)
# function to search string in text
def find():
# remove tag 'found' from index 1 to END
text.tag_remove('found', '1.0', END)
# returns to widget currently in focus
s = edit.get()
if (s):
idx = '1.0'
while 1:
# searches for desried string from index 1
idx = text.search(s, idx, nocase = 1,
stopindex = END)
if not idx: break
# last index sum of current index and
# length of text
lastidx = '% s+% dc' % (idx, len(s))
# overwrite 'Found' at idx
text.tag_add('found', idx, lastidx)
idx = lastidx
# mark located string as red
text.tag_config('found', foreground ='red')
edit.focus_set()
def findNreplace():
# remove tag 'found' from index 1 to END
text.tag_remove('found', '1.0', END)
# returns to widget currently in focus
s = edit.get()
r = edit2.get()
if (s and r):
idx = '1.0'
while 1:
# searches for desried string from index 1
idx = text.search(s, idx, nocase = 1,
stopindex = END)
print(idx)
if not idx: break
# last index sum of current index and
# length of text
lastidx = '% s+% dc' % (idx, len(s))
text.delete(idx, lastidx)
text.insert(idx, r)
lastidx = '% s+% dc' % (idx, len(r))
# overwrite 'Found' at idx
text.tag_add('found', idx, lastidx)
idx = lastidx
# mark located string as red
text.tag_config('found', foreground ='green', background = 'yellow')
edit.focus_set()
Find.config(command = find)
replace.config(command = findNreplace)
# mainloop function calls the endless
# loop of the window, so the window will
# wait for any user interaction till we
# close it
root.mainloop()

Extracting rows within Python Tkinter-Treeview into a Pandas data frame

I have a GUI where results from a dataframe are populated into a treeview within python based on filters previously input. The user can then click on treeview and update the values to the desired number. The number of rows within the view can vary from 1 - 20+. Once the user has updated the view as desired, I have a button below "Check Allocation".
It is at this point I want to "export" the treeview into a dataframe for me to run against another table. I cant seem to simply export this as a dataframe. Is there any work around this? I only need the first and last columns (of the 8) to check the newly updated file.
Here is what I have so far.
def PrintAllocation(self):
treeview = tkk.Treeview(root)
treeview.grid(column = 1, row = 8, columnspan = 4, padx = 1, pady = 1)
cols = list(matches.columns)
treeview["columns"] = cols
for i in cols:
treeview.column(i, width = 100, anchor = "w")
treeview.heading(i,text=i,anchor='w',)
for index, row in matches.iterrows():
treeview.insert("",0,text=index,values=list(row))
def set_cell_value(event): # Double click to enter the edit state
for item in treeview.selection():
#item = I001
item_text = treeview.item(item, "values")
#print(item_text[0:2]) # Output the value of the selected row
column= treeview.identify_column(event.x)# column
print(column)
row = treeview.identify_row(event.y) #row
cn = int(str(column).replace('#',''))
rn = int(str(row).replace('I',''))
if column == '#8':
entryedit = Text(root,width=50,height = 1)
entryedit.grid(column = 2, row = 9, padx = 1, pady = 1)
else:
entryedit = Text(root,width=10,height = 1)
entryedit.grid(column = 2, row = 9, padx = 1, pady = 1)
def saveedit():
treeview.set(item, column=column, value=entryedit.get(0.0, "end"))
entryedit.destroy()
okb.destroy()
okb = ttk.Button(root, text='OK', width=4, command=saveedit)
okb.grid(column = 3, row = 9,padx = 1, pady=1)
def CheckAllocation():
children = treeview.getchildren()
for child in children:
print(treeview.set(child))
treeview.bind('<Double-1>', set_cell_value) # Double-click the left button to enter the edit
button_check = Button(root,text="Check Allocation", command = CheckAllocation)
button_check.grid(column = 2, row = 10, padx = 10, pady=10)
'''
I don't really understand your code but when I need to get a treeview to pandas I do it as follows:
First I create an empty list for each column in the treeview.
column_a_list = []
column_b_list = []
column_c_list = []
column_d_list = []
Then running through the lines of the treeview in a "for" function, I append to each column list the value of the column in each line.
for child in self.treeview.get_children():
column_a_list.append(self.treeview.item(child)["values"][0])
column_b_list.append(self.treeview.item(child)["values"][1])
column_c_list.append(self.treeview.item(child)["values"][2])
column_d_list.append(self.treeview.item(child)["values"][3])
Then I create a dictionary from all the lists, using the header as the key and lists are the values as a list.
full_treeview_data_dict = {'HEADER A': column_a_list, 'HEADER B': column_b_list, 'HEADER C': column_c_list, 'HEADER D': column_d_list,}
Then I create a dataframe from the dictionary.
treeview_df = pd.DataFrame.from_dict(full_treeview_data_dict)
Here is the full sample code in one chunk:
column_a_list = []
column_b_list = []
column_c_list = []
column_d_list = []
for child in self.treeview.get_children():
column_a_list.append(self.treeview.item(child)["values"][0])
column_b_list.append(self.treeview.item(child)["values"][1])
column_c_list.append(self.treeview.item(child)["values"][2])
column_d_list.append(self.treeview.item(child)["values"][3])
full_treeview_data_dict = {'HEADER A': column_a_list, 'HEADER B': column_b_list, 'HEADER C': column_c_list, 'HEADER D': column_d_list,}
treeview_df = pd.DataFrame.from_dict(full_treeview_data_dict)
I hope it helps.
You can also do this:
row_list = []
columns = ('name', 'school', 'c3', 'c4')
for row in treeview.get_children():
row_list.append(treeview.item(row)["values"])
treeview_df = pd.DataFrame(row_list, columns = columns)
The current answer is good, I just wanted to add my two cents here:
self.treeview_columns = [] # list of names here
# initialize empty df
# if too big you can preallocate but probably not needed
treeview_df = pd.DataFrame(None, columns=self.treeview_columns)
for row in self.treeview.get_children():
# each row will come as a list under name "values"
values = pd.DataFrame([self.treeview.item(row)["values"]],
columns=self.treeview_columns)
# print(values)
treeview_df = treeview_df.append(values)
This way you grow your df row by row using append and you reduce the number of subsets that you need to do.

Removing a selection from a listbox, as well as remove it from the list that provides it

How can I use the following code to delete a selection from a listbox and removing it from the list the contains it also? The selections in the listbox are dictionaries which I store in a list.
.................code..............................
self.frame_verDatabase = Listbox(master, selectmode = EXTENDED)
self.frame_verDatabase.bind("<<ListboxSelect>>", self.OnDouble)
self.frame_verDatabase.insert(END, *Database.xoomDatabase)
self.frame_verDatabase.pack()
self.frame_verDatabase.config(height = 70, width = 150)
def OnDouble(self, event):
widget = event.widget
selection=widget.curselection()
value = widget.get(selection[0])
print ("selection:", selection, ": '%s'" % value)
Example:
When I make a selection in the listbox, this data gets returned:
selection: (2,) : '{'Fecha de Entrega': '', 'Num Tel/Cel': 'test3', 'Nombre': 'test3', 'Num Orden': '3', 'Orden Creada:': ' Tuesday, June 23, 2015', 'Email': 'test3'}'
from tkinter import *
things = [{"dictionaryItem":"value"}, {"anotherDict":"itsValue"}, 3, "foo", ["bar", "baz"]]
root = Tk()
f = Frame(root).pack()
l = Listbox(root)
b = Button(root, text = "delete selection", command = lambda: delete(l))
b.pack()
l.pack()
for i in range(5):
l.insert(END, things[i])
def delete(listbox):
global things
# Delete from Listbox
selection = l.curselection()
l.delete(selection[0])
# Delete from list that provided it
value = eval(l.get(selection[0]))
ind = things.index(value)
del(things[ind])
print(things)
root.mainloop()
Edited for clarity. Since the listbox in this case only included dict objects I simply eval the value that is pulled from the listbox, get its index inside the list object, and delete it.
Everything from the second comment up to the print statement can be accomplished in one line as follows:
del(things[things.index(eval(l.get(selection[0])))])
If you feel like being creative.

Categories