How to not display duplicates in a list using tkinter - python

How do I get this program to not display the same selection again to the user when its already been selected ?
I know behind the scenes I can create a set of my entries for the code to continue using, but I cant seem to stop the display showing duplicate items if the user selects the same thing again. I thought the 'not in' statement might have worked. Any help please ?
from tkinter import *
from tkinter import ttk
root = Tk()
# set in pixels
root.geometry("1000x750+100+100")
my_list = set()
def combo_click(event):
my_label = Label(root, text=myCombo.get()).pack()
if myCombo.get() not in my_list:
my_list.add(myCombo.get())
# print('List without duplicate items (Set) ' + '\n')
OptionList = [
"Aries",
"Taurus",
"Gemini",
"Cancer"
]
clicked = StringVar()
clicked.set(OptionList[0])
# *Options - https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists
#drop = OptionMenu(root, clicked, *OptionList, command=selected)
#drop.pack(pady=100)
myCombo = ttk.Combobox(root, value=OptionList)
myCombo.current(0)
myCombo.bind("<<ComboboxSelected>>", combo_click)
myCombo.pack()
root.mainloop()

A method you could use to filter out duplicates in a list is the following code. I am assuming you are adding each bit of text to the Tkinter list through variables.
In my code the variable: a is the item we wish to add to the list.
Code to filter through entries to a Tkinter list:
While True: #replace the bit inbetween w and t with your how long you want to do it
#add everything you add to a list before you add to Tkinter Listbox as shown below.
for x in tlist:
if tlist contains a:
print("Duplicate! This will not be added.")
else:
[whatever_you_called_your_listbox].insert(END,a)
print(a,"Was added.")
Hope this helps!

Related

How to get a reference to individual buttons created in a loop in tkinter?

Attempting to create a book issue page.
I get the data (the document variable) displayed as an array of n-ples, and add the borrow/return buttons in the loop with these data fields. However, since I create multiple instances of the same button variable as seen below, I have no way of knowing which one is clicked. Here is the code:
document = self.app.Member(MemberVerification.mem_id, self.app).searchDocument(SearchBooks.inputvalues, "Books")
logger.info("Results: " + str(document) + " for string filters: " + str(filters))
cells={}
for i in range(len(document)): # Rows
self.borrow = tk.Button(self, text="Borrow", command= lambda: self.checkBorrows(document[i], self.controller, i))
self.retbook = tk.Button(self, text="Return")
for j in range(1, len(document[1])): # Columns
try:
b = tk.Entry(self,justify=tk.CENTER)
b.grid(row=i, column=j)
b.insert(tk.END, str(document[i][j]))
cells[(i, j)] = b
except Exception as e:
logger.error("Error in populateTable: " + str(e) + traceback.format_exc())
sys.exit(-1)
if i!=0:
self.borrow.grid(row=i, column=j+1)
self.borrowbuttons.append(self.borrow)
self.retbook.grid(row=i, column=j+2)
A little explanation - i == 0 is where the column names come in. The button in question is self.borrow. I tried maintaining a list of them to search which button it is, but that didn't work. I would like to be able to maintain a reference to the specific button out of the 3 that have been created, so that the callback function checkBorrows can know which book has to be borrowed. The i parameter was just experimentation, doesn't seem to pass the index in due to the async nature of the button click listener
Any suggestions? I tried maintaining a class variable, but that always ends up referring to the last button, since the callback is technically async.
Thanks!

Why am I getting a type error from this function?

I am trying to create a drop down menu in tkinter that allows the user to select a machine, or row # from the excel sheet, and then all the data from that entire row is displayed in tkinter through the display_selected function. I keep getting this error.
This is my error:
TypeError: line_7_window.<locals>.display_selected() takes 0 positional arguments but 1 was given
This is my code:
def get_machine():
for row in sheet.iter_rows(min_row=2, max_col=1, max_row=60):
for cell in row:
if cell.value == inputmachine:
machineindex = cell
return machineindex.row
def get_machineattributes():
for col in sheet.iter_cols(min_row = (get_machine()), max_col = 15, max_row = (get_machine())):
for cell in col:
return (cell.value)
def display_selected():
data = Message(line_7, text=get_machineattributes())
data.pack()
data.place(x=650, y=30)
copy = Button(line_7, text="Copy to Clipboard", command=pyperclip.copy(line7_choice))
copy.pack()
copy.place(x=550, y=45)
return
inputmachine = StringVar(line_7)
inputmachine.set("Click to select a machine")
dropdown = OptionMenu(line_7, inputmachine, *lst, command=display_selected)
dropdown.pack()
dropdown.place(x=670, y=25)
I have tried everything and I cant figure out why this would not work.
Your callback function display_selected, specified when creating the option menu, will receive the actual option chosen from Tk, so you need that parameter when defining it, even if you do nothing with it.
In other words, use something like:
def display_selected(choice):
del choice # not used
# rest of function
As an aside, I suspect you may be better off with an option menu to select the item, and a separate button to act on that selection. That would allow you to confirm the selected item before performing any action, in case you inadvertently choose the wrong one.

Setting individual column headers in Python Tkinter's Tksheet widget / rolling back changes in same

I am writing a UI for a simulation program which accepts tabular data.
The basic functionality I need is for the user to be able to enter / change data in cells either by directly typing into them, or by pasting data (usually from an excel sheet). The program checks this data and either accepts or rejects it before running the simulation. I also want to let the user type in their own column headers for the table.
Tksheet is an awesome Tkinter add-on, giving an excel-like "feel" to the input frame, but its documentation leaves much to be desired. (For instance: each event generates a different event-information array--see code for two event-processing routines below--but nowhere is it specified what these parameters are. It is left for the user to discover using trial and error, or trying to read the source code--which is not documented either).
I have two specific questions:
Is there any way to not-commit, or to roll back, changes to the table? If my data-tests fail, how do I prevent potentially harmful user input from being entered into the table?
Obviously I can (and do) add a begin_*** event in which I can keep copies of the original values, and then reset the table values if the data testing at the end_*** event fails, but this is wasteful and inelegant. I have a feeling that the set_data_ref_on_destroy property has something to do with such a capability, but the documentation does not explain what this parameter is or how to use it.
How can I change a single column header at a time? The .headers property seems to work only with a full list of headers starting with column 0 (if I run self.sheet.headers([single_value], index = i) it ignores the index parameter and plugs single_value in column 0)
Again, I can set the column headers to something non-default at init and keep a running list of all headers, so that I can reset all the headers on each change, but this is wasteful and inelegant.
In the following code sample I set up a simple table, and bind three user-generated events: one for typing a value to a cell, one for pasting a block of values, and one for adding an option to the right-click menu of a column header, to allow the user to type a name to the column.
from tksheet import Sheet
import tkinter as tk
import tkinter.messagebox as msg
import tkinter.simpledialog as sd
class demo(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.grid_columnconfigure(0, weight=1) # This configures the window's escalators
self.grid_rowconfigure(0, weight=1)
self.frame = tk.Frame(self)
self.frame.grid_columnconfigure(0, weight=1)
self.frame.grid_rowconfigure(0, weight=1)
self.frame.grid(row=0, column=0, sticky="nswe")
self.sheet = Sheet(self.frame, data=[[]]) # set up empty table
self.sheet.grid(row=0, column=0, sticky="nswe")
self.sheet.enable_bindings(bindings= # enable table behavior
("single_select",
"select_all",
"column_select",
"row_select",
"drag_select",
"arrowkeys",
"column_width_resize",
"double_click_column_resize",
"row_height_resize",
"double_click_row_resize",
"right_click_popup_menu",
"rc_select", # rc = right click
"copy",
"cut",
"paste",
"delete",
"undo",
"edit_cell"
))
# Note that options that change the structure/size of the table (e.g. insert/delete col/row) are disabled
# make sure that pasting data won't change table size
self.sheet.set_options(expand_sheet_if_paste_too_big=False)
# bind specific events to my own functions
self.sheet.extra_bindings("end_edit_cell", func=self.cell_edited)
self.sheet.extra_bindings("end_paste", func=self.cells_pasted)
label = "Change column name" # Add option to the right-click menu for column headers
self.sheet.popup_menu_add_command(label, self.column_header_change, table_menu=False, index_menu=False, header_menu=True)
# Event functions
def cell_edited(self, info_tuple):
r, c, key_pressed, updated_value = info_tuple # break the info about the event to individual variables
if check_input(updated_value):
pass # go do stuff with the updated table
else:
msg.showwarning("Input Error", "'" + updated_value + "' is not a legal value")
pass # what do I do here? How do I make tksheet *not* insert the change to the table?
def cells_pasted(self, info_tuple):
key_pressed, rc_tuple, updated_array = info_tuple # break the info about the event to individual variables
r, c = rc_tuple # row & column where paste begins
if check_input(updated_array):
pass # go do stuff with the updated table
else:
msg.showwarning("Input Error", "pasted array contains illegal values")
pass # what do I do here? How do I make tksheet *not* insert the change to the table?
def column_header_change(self):
r, c = self.sheet.get_currently_selected()
col_name = sd.askstring("User Input", "Enter column name:")
if col_name is not None and col_name != "": # if user cancelled (or didn't enter anything), do nothing
self.sheet.headers([col_name], index=c) # This does not work - it always changes the 1st col
self.sheet.redraw()
# from here down is test code
def check_input(value): # instead of actual data testing we let the tester choose a pass/fail response
return msg.askyesno("Instead of input checking","Did input pass entry checks?")
test = demo()
lst = ["hello", "world"]
test.sheet.insert_column(values=lst)
lst = [0, "hello", "yourself"]
test.sheet.insert_column(values=lst)
test.mainloop()
I realize that the original post is now 5 months old, and I'm a relative n00b, but I hope this helps.
Given a tksheet instance 'sheet' that has already been populated with headers ["A"."B"."C"], the following works to change the header "B" to "NEW":
sheet.headers()[1]="NEW"
Hope this helps.

Python ttk combobox value update?

I use a GUI for some web automation with selenium.
I have several comboboxes (will do one example here)
This is my example code:
app = Tk()
def callback(*args): # should get the updated values in Combobox ?
global v_sv_league
v_sv_league = str(sv_league.get())
#List to be filled by scraper
li_leagues = []
#StringVar
sv_league = StringVar()
sv_league.trace("w", callback)
#Label
l_d_league = tk.Label(app,text='League:',bg='#1d1b29', fg='#f8f09d',font='Tahoma 10')
#Combobox
d_league = ttk.Combobox(app,textvariable=sv_league,values=li_leagues)
#Scrape
def scrape():
btn_tflist = wait.until(EC.element_to_be_clickable((By.XPATH,('/html/body/main/section/nav/button[3]'))))
btn_tflist.click()
btn_tf_filters = wait.until(EC.element_to_be_clickable((By.XPATH,'/html/body/main/section/section/div[2]/div/div/div[2]/div[2]')))
btn_tf_filters.click()
bol_scrape = True
if bol_scrape is True:
print('\n Start scraping... this might take a few minutes. Please wait and dont press anything until trade_buddy is done!\n')
li_leagues = []
print('Getting leagues...\n')
league_dropdown_menu = wait.until(EC.element_to_be_clickable((By.XPATH,('/html/body/main/section/section/div[2]/div/div[2]/div/div[1]/div[1]/div[7]/div'))))
league_dropdown_menu.click()
time.sleep(1)
# scrape all text
scrape_leagues = driver.find_elements_by_xpath("//li[#class='with-icon' and contains(text(), '')]")
for league in scrape_leagues:
export_league = league.text
export_league = str(export_league)
export_league = export_league.replace(',', '')
li_leagues.append(export_league)
app.mainloop()
So basically this is just a small part of my code but this is what I got for one of my combobox's.
You can see that I will call for def scrape at some point in my code to scrape data and to fill my list li_leagues.
However, my combobox is not refreshing the content and stays empty.
For OptionMenu I got it set up (with a it other code) but I cant get it working with combobox.
Any advice what I am missing here?
Thanks a slot!
Try using this line of code, after appending the list with values.
.....
export_league = export_league.replace(',', '')
li_leagues.append(export_league)
c_league.config(values=li_leagues)
config() method acts as a updater that just updates your widget, when called.
Hope it was of some help.
Cheers

Python looping for label variables

I am trying to create a loop that will display each title of product for their individual labels (Tkinter module).
With the current loop i can get it to print my 10 "static_webpage_1_titles" in the list, but i am wanting to also increase the variable Label_1 by +1 increment each time.
For example, it should do somthing like this:
Label_1['text'] = static_webpage_1_titles[0]
Label_2['text'] = static_webpage_1_titles[1]
Label_3['text'] = static_webpage_1_titles[2]
Here is my current code:
def Generate_Product_Name_and_Price_1():
if Button_on:
Find_static_webpage_1()
for i in range(len(static_webpage_1_titles)):
Label_1['text'] = static_webpage_1_titles[i]
EDIT:
product_labels = [Label_1['text'], Label_2['text'], Label_3['text'],
Label_4['text'], Label_5['text'], Label_6['text'],
Label_7['text'], Label_8['text'], Label_9['text'],
Label_10['text']]
I have created a list above with each label widget and changed the last line of code in my loop to:
def Generate_Product_Name_and_Price_1():
if Button_on:
Find_static_webpage_1()
for i in range(len(static_webpage_1_titles)):
product_labels[i] = static_webpage_1_titles[i] +': $'+ static_webpage_1_price[i]
When i run this, i do not receive any IDLE error but my label widgets do not get populated with data.
You need to make a list of the Label instances, not the text attributes of Label instances. Like this:
product_labels = [Label_1, Label_2, Label_3,
Label_4, Label_5, Label_6,
Label_7, Label_8, Label_9,
Label_10]
Then you access the attribute like this:
for i in range(len(static_webpage_1_titles)):
product_labels[i]['text'] = static_webpage_1_titles[i] +': $'+ static_webpage_1_price[i]
You could also make this a little neater with zip():
for label, title, price in zip(product_labels, static_webpage_1_titles, static_webpage_1_price):
label['text'] = title +': $'+ price

Categories