Variable updating one step behind in list searcher in Tkinter - python

I'm trying to build a search engine that will check a list and then remove all list items that do not meet the search parameters. I know there is several problems with my program such as it will not add things back to the list when you backspace and in my updating for loop I simply tack on a '*' thinking that it will search for strings only beginning with the current parameters, but I will cross those bridges later.
class StudentFinderWindow(Tkinter.Toplevel):
def __init__(self):
Tkinter.Toplevel.__init__(self) # Create Window
searchResultList = ['student1', 'student2', 'student3'] # Test list.
##### window attributes
self.title('Edit Students') #sets window title.
##### Puts stuff into the window.
# text
editStudentInfoLabel = Tkinter.Label(self,text='Select the student from the list below or search for one in the search box provided')
editStudentInfoLabel.grid(row=0, column=0)
# Entry box
self.searchRepositoryEntry = Tkinter.Entry(self)
self.searchRepositoryEntry.grid(row=1, column=0)
# List box
self.searchResults = Tkinter.Listbox(self)
self.searchResults.grid(row=2, column=0)
This fills the Tkinter Listbox with the original list.
# Search results initial updater.
self.getStudentList()
for student in self.studentList:
self.searchResults.insert(Tkinter.END, student)
##### Event handler
Right here I bind to run the list updater after a key is entered into the search box
self.searchRepositoryEntry.bind('<Key>', self.updateSearch)
This is supposed to run every time a key is pressed. It gets the string that is in the Entry then starts a variable count so I know which index the name is at. After that it run a for loop on the current list supposedly checking to see if it fits the requirement of the parameters and any other letter after it. If it does not match it should delete. The problem is the first time I hit a letter the parameters string is just a blank space and then the next letter the string is the first letter and so on. It is always one step behind. And that is the problem
def updateSearch(self, event):
parameters = self.searchRepositoryEntry.get()
int = 0
currentList = self.searchResults.get(0, Tkinter.END)
for i in currentList:
if not i == parameters + '*':
self.searchResults.delete(int)
int += 1
def getStudentList(self):
global fileDirectory # Gets the directory that all the files are in.
fileList = listdir(fileDirectory)
self.studentList = []
for file in fileList:
self.studentList.append(file[:-4])

I believe I have run into this same problem you describe before, when attempting to make an actively searching ctrl-F feature in one of my programs.
What I found to work is not bind on Key but instead KeyRelease. I'm not entirely sure why this works (probably just a quirk with Tkinter). However, it works.
Snippet's:
The binding
# self.FW.En is an entry widget.
self.FW.En.bind('<KeyRelease>', self.find)
Which would run
def find (self, event):
self.Te.tag_remove('found', '1.0', 'end')
pat = self.FW.En.get()
if len(pat) > 1:
index = '1.0'
while True:
index = self.Te.search(pat, index, nocase=1, stopindex='end')
if not index:
break
lastidex = '%s+%dc' % (index, len(pat))
self.Te.tag_add('found', index, lastidex)
index = lastidex
self.Te.tag_config('found', background='#80ff00')

Related

Python - .insert() method only replaces the first letter of word?

I've posted about this before and I've been able to whittle the program down to a single function for testing purposes. I'm not getting any errors, but I am getting a bug that is driving me up the wall. I'm able to replace the shortcut, but it only replaces the first letter of the shortcut.
Here's the code:
from tkinter import *
root = Tk()
text = Text(root)
text.pack(expand=1, fill=BOTH)
syntax = "shortcut = sc" # This will be turned into a function to return the shortcut
# and the word, I'm only doing this for debugging purposes.
def replace_shortcut(event=None):
tokens = syntax.split()
word = tokens[:1]
shortcut = tokens[2:3]
index = '1.0'
while 1:
index = text.search(shortcut, index, stopindex="end")
if not index: break
last_idx = '%s + %dc' % (index, len(shortcut))
text.delete(index, last_idx)
text.insert(index, word)
last_idx = '%s + %dc' % (index, len(word))
text.bind('<space>', replace_shortcut)
text.mainloop()
The shortcut given, in our case 'sc' will turn into 'shortcutc' after the space is typed. Any help is appreciated!
You have two problems.
You have the variable shortcut defined to be ['sc'] instead of 'sc'. So len(shortcut) will always be 1 (the length of the array) rather than 2 (the length of the string). You'll always end up deleting just one character. Probably you want len(shortcut[0])
[You also have the same problem with len(word). You'll always get 1, the length of the array.]
Also, the last line of your while loop should set index rather than last_idx, since that's the variable that's going to be used in the next search.

How to add Combobox every time when i press button in Tkinter?

I wanna build GUI and there is a button which Combobox adds every time the button is pressed. but i cant find out how to figure or code this concept
And, is there any other Widget? or is there other way to build my concept??
please some one help me..
the main concept without using tkinter is Like this shown . i want this concept to be made in Tkinter.
[THIS IS WHAT I TRIED BUT WHENEVER I PRESS THE BUTTON, ALL COMBOBOX ADDED WORKS TOGETHER (which means when i change the list name, all combobox also changes..)]
def Plus_EXT():
button_plus = Button(window3,justify = CENTER,command = Add_EXT, text= "+")
button_plus.grid(row=0,column=0)
def Add_EXT():
global Num
window3.update()
Num += 1
CEList1 = [x for x in os.listdir(cur_dir) if ('CE' in x.upper()) and ('.rpt' in x)]
RPT_EXT_file=ttk.Combobox(window3, values=CEList1,textvariable= EXT_String)
RPT_EXT_file.grid(row=8+Num,column=1)
RPT_EXT_file.set("FILE")
[MAIN CONCEPT WITHOUT USING TKINTER]
CE_Num= int(input("How Many EXTRA Chordae?: "))
User_File = input("Type the New File Name: ")
for Num_Of_EXT in range(CE_Num):
RPT_EXT_file = input("Type the RPT of EXTRA CHORDAE: ") + ".rpt"
EXTRA(User_File,RPT_EXT_file,Num_Of_EXT)
EXTRA_PLT(User_File,Num_Of_EXT)
the output i want is { everytime i press the button a new Combobox is added in the frame with the lists which works separately.}
The issue is that all your Combobox widgets are linked with same textvariable called EXT_String.So when you change the value of one Combobox widget, it updates same value in all of them.
I would suggest to keep a list of such variables and index that list using NUM in your loop to assign the textvariable.
UPDATE:
Just an example based on limited code shared in Add_EXT method:
global var_list
var_list.append(IntVar()) #Or StringVar(), whatever you are using
RPT_EXT_file=ttk.Combobox(window3, values=CEList1,textvariable=var_list[-1])

Check if Python (3.4) Tkinter Entry is Full

I am using a standard tkinter entry to input a directory path. When the user presses enter - if the physical length of the string exceeds that of the entry, I want the program to modify the displayed text to ...[end of directory]. I have the logistics of this figured out but as of yet I have no accurate way to test whether the entry box is full, or how full
Things I have tried:
Using 'PIL.ImageFont.truetype("font.otp",fontsize).size' - cannot calculate the point at which to cut the directory
Simply using the length of the string against the length of the entry- inaccurate as the font I am using (which I don't want to change if possible) varies in length with each character
Another compromise behaviour which I tried was to make the entry "look" at the start of the path. I tried inserting at, selecting at and moving the cursor to position 0 but none of these worked
You can use xview method of Entry. xview return the visible part of the text displayed in the entry. You can use it to interactively create a text that fit the entry.
Here is a quick and dirty proof of concept
from tkinter import *
root = Tk()
v = StringVar(root)
e = Entry(root,textvariable=v)
e.pack(fill=BOTH)
v.set('abcdefghijklmnopqrstuvwxyz0123456789')
new_s = None
def check_length():
global new_s
original_s = v.get()
def shorten():
global new_s
e.xview(0)
if e.xview()[1] != 1.0:
new_s = new_s[:-4] + '...'
v.set(new_s)
print("new_s: " + new_s)
e.xview(0)
e.after(0,shorten)
print(e.xview()[1])
if e.xview() != (0.0,1.0):
new_s = original_s + '...'
shorten()
b = Button(root,text="hop",command=check_length)
b.pack()
e.mainloop()

Python function not working - Tkinter

My function isn't giving me the right output, and it doesn't want to work. I keep getting this error:
TypeError: list indices must be integers, not str
This is my code:
def showShop(level = level, cash = cash):
top = Tkinter.Tk()
shop = ["$100 & level 2 - Shotgun", "$250 & level 3 - 5 Grenades", "$500 & level 5 - Rocket Launcher"]
buttons = []
for i in shop:
temp = shop[i]
temp = Tkinter.Button(top, height=10, width=100, text = temp, command = shopping(i))
temp.pack()
buttons.append(temp)
top.mainloop()
I want it to display what is in the shop list based on what button it is...
Remove temp = shop[i] from the code
for i in shop:
temp = Tkinter.Button(top, height=10, width=100, text = temp, command = shopping(i))
temp.pack()
buttons.append(temp)
The for loop iterates over the elements in the list and not the indices!. The python docs make it more clear
The for statement in Python differs a bit from what you may be used to in C or Pascal. Rather than always iterating over an arithmetic progression of numbers (like in Pascal), or giving the user the ability to define both the iteration step and halting condition (as C), Python’s for statement iterates over the items of any sequence (a list or a string), in the order that they appear in the sequence.
Also note that the command argument in the Button constructor takes a function as an argument. So you maybe better off by writing command = shopping there instead of the call command = shopping(i).
Change for i in shop to for i in xrange(shop).
You have to use something like partial to pass arguments to the function called by the button press. Note that you have declared the variable "temp" as 2 different things. The only reason it works is because the second declaration is after you use the first. Also note that the "buttons" list can not be used outside of the function showShop() because it is created in/local to that function. The following is working code based on what you posted. Also, please do not use "i", "l" or "O" as single digit variable names as they can look like numbers.
import Tkinter
from functools import partial
def shopping(btn_num):
print "button number %d pressed" % (btn_num)
buttons[btn_num]["bg"]="lightblue"
def showShop(buttons):
top = Tkinter.Tk()
shop = ["$100 & level 2 - Shotgun", "$250 & level 3 - 5 Grenades",
"$500 & level 5 - Rocket Launcher"]
##buttons = []
for ctr in range(len(shop)):
temp = Tkinter.Button(top, height=10, width=100, text = shop[ctr],
command = partial(shopping, ctr))
temp.pack()
buttons.append(temp)
top.mainloop()
## lists are mutable
buttons=[] ## not local to the function
showShop(buttons)

Tkinter how to make a dynamic optionmenu without using a dictionary

I found this question, which was helpful to see the methodology, but I want to use their method without using a dictionary.
Basically, in Python I'm trying to use two optionmenu's from Tkinter where the first will contain the tabs of an excel document, and then the second will contain information that I've read from one of the columns once the tab has been selected from the first optionmenu. As I don't want to store and read every single tab prior, it seems much more efficient to just call the function to read the data once the tab was chosen rather than create a dictionary beforehand. Does someone know a way of doing this?
Here is what I have so far(excel_transfer is a self created library based off openpyxl- if you have further questions on what I'm doing feel free to ask, otherwise it doesn't seem necessary to go into it for the purposes of this question):
import Tkinter
from Tkinter import *
from ttk import Frame
import manipulate_excel
import openpyxl
from openpyxl import load_workbook
class GUI(Frame):
def read_tabs(self):
wkbk = load_workbook('file.xlsx')
sheets=wkbk.get_sheet_names()
return sheets
def read_columns(self):
sheet = self.tabs_var.get()
#if no value is selected, keep return a blank value
if(sheet == ''):
return ['']
else:
filepath = 'file.xlsx'
workbook = excel_transfer.workbook(filepath)
wb = workbook.open_existing_workbook()
ws = wb(sheet)
#step through the rows until a blank value is read
row = 1
current_row = 0
previous_row = 0
values = []
#read the column, and append values to the array
while current_row != None:
previous_row = current_row
row = row + 1
cell = 'A' + str(row)
current_row = workbook.read_new_cell(cell, ws)
if(current_row != None):
values.append(current_row)
#if there are no values, still return a single array value so it doesn't barf
if(values== []):
values= ['']
return values
def build_gui(self):
n = Notebook(self)
tab = Tkinter.LabelFrame(self, text='Tab')
n.add(tab, text='Tab')
n.pack()
self.tabsvar = StringVar()
self.tabsvar.set('')
self.valuesvar = StringVar()
self.valuesvar.set('')
self.list_of_tabs = self.read_tabs()
self.list_of_values = self.read_columns()
self.TAB_OPTION = OptionMenu(tab, self.tabsvar, *self.list_of_tabs)
self.TAB_OPTION.grid(row=0, column=0)
self.VALUES_OPTION = OptionMenu(tab, self.valuesvar, *self.list_of_values)
self.VALUES_OPTION.grid(row=1, column=0)
if __name__ == '__main__':
root = Tk()
app = GUI(root)
root.mainloop()
The first option works to find the tabs, but the problem I have is the read_column only happens when the GUI is initially opened, it remains a blank value. How would I make the self.list_of_values responsive to which tab is chosen in the first optionmenu? Can I use trace without a dictionary? I'm sure there's prettier ways of writing the code I did- I'm open to constructive criticism as there's always plenty to learn. Thank you for the help!
Ok, so I found the answer after continuing to work with it to help out anyone in future having the same problem.
I changed self.TAB_OPTION to:
self.TAB_OPTION(tab, self.tabsvar, *self.list_of_tabs, command=self.read_columns)
By adding this, I had to pass in a variable into self.read_columns, as adding the command automatically passes the value the user selected in the first optionmenu. So, I changed self.read_columns to:
def read_columns(self, board):
sheet = board
#sheet = self.tabs_var.get()
I also took out the line shown to be commented above, so it received the value that was selected in the first optionmenu from the TAB_OPTION rather than reading what is selected from the GUI. I also added in:
self.VALUES_OPTION['menu'].delete(0, 'end')
for item in values:
self.VALUES_OPTION['menu'].add_command(label=item)
at the end of the read_columns function so it would regenerate the values for the second optionmenu. It deletes all of the previous values, then adds each value that had been read from the excel in by iterating through the array. I no longer needed the return value at the end of the function.
I hope this helps other confused coders :)

Categories

Resources