Tkinter how to make a dynamic optionmenu without using a dictionary - python

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

Related

execute multiple variable functions(var_1,var_2,var_3)

I got another little question...
I want to make multiple variables which I create with 'setattr'
That works quite fine. It creates these variables:
self.sectionButton_1 = Button(text=x)
self.sectionButton_2 = Button(text=x)
self.sectionButton_3 = Button(text=x)
Now I want them to get displayed on the window with tkinter so that this should happen:
self.sectionButton_1.grid(row=i, column=0)
self.sectionButton_2.grid(row=i, column=0)
and so on..
But how do I have to edit the loop that the sectionButtons gonna be created with .grid from tkinter in a loop without writing the above ten times.
# Display Section selection
def checkSection(self):
# Read all sections from config
self.sections = config.sections()
self.sectionsCount = str(len(self.sections))
self.i = 0
self.text = Label(text="Choose Section:" + self.sectionsCount)
self.text.grid(row=1, column=0)
for x in self.sections:
i = +1
setattr(self, 'sectionButton_' + str(i), Button(text=x))
I'm not that good at explaining but hopefully its enough to understand my problem ^^
If not, just comment, I will try to answer it
If you have a group of related variables of the same type and you're doing the same operations to each one then that's a natural place to switch to using a list instead of individual variables.
Your code would become more like:
self.sectionButtons = []
for i, x in enumerate(self.sections):
button = Button(text=x)
button.grid(row=i+1, column=0)
self.sectionButtons.append(button)
This also has the advantage of no longer needing to construct the variable names as strings and use setattr, which is often a sign there's a better way.

Create multiple Entry boxes in a loop tkinter

I was making an application using tkinter and came across an error. I wanted people to input a variable, which I have made, and then have that many Entry boxes popup on the screen for input. I was wondering what is wrong with my code, if it is possible, or if there is a better way. Thanks in advance!
p.s. the NoOfBoxes has been predefined
int(NoOfBoxes)
x = 1
while(NoOfBoxes>=x):
a = a + 50
fill_empty(a)
x = x + 1
def fill_empty():
empty = tk.Entry(self)
empty.grid(row=200,column=a)
return empty
In first line of shown code, you are converting NoOfBoxes to an integer but you are not assigning back it to NoOfBoxes hence, when while line comes, NoOfBoxes is still not an integer. Also, there is no parameter on your fill_empty definition.
Most likely you will need those Entry widgets at some point in your code, so it'll be much better if you keep references.
listOfEntries = [fill_empty(idx) for idx in range(int(NoOfBoxes))]
def fill_empty(a):
empty = tk.Entry(self)
empty.grid(row=200,column=a)
return empty
When you want to make any operation on those, you can easily do something like:
listOfEntries[0].get()

how to find if last row has value for a specific column in Excel using Python Win32com

I have an Excel file and I use python Win32com to operate on it.
how do I check if the last row has value in a specific column ?
when I try , the nrows is 28, not 23 (the last row have value not empty)
used = sht1.UsedRange
nrows = used.Row + used.Rows.Count - 1
UsedRange is not reliable always when you want to get last row of any specific column. You should use EndXlUp or EndXlDown function.
Check this below line:
LastRow = Sheets("Sheet1").Range("A" & Sheets("Sheet1").Rows.Count).End(xlUp).Row
Where,
A is the column to get last row.
Also, check this URL: Error in finding last used cell in VBA
As your are using python win32 com, Endxlup will not work. There's one basic thing you can do. Check the below code:
ws = wb.Worksheets('Sheet1')
rw = 2
While ws.cells(rw, 1) <> ""
rw +=1
Where,
rw is the starting row from where you want to start row count.
1 in (rw, 1) represents column. Column A represents A.
Logic behind this is while loop will run till it does not get blank cell in column A and you will get row count in variable rw
I'm currently writing an Excel wrapper, and part of it is this function returning all data from a certain offset to the last row of that column. For example, if I want everything from the 3rd row up till including the last line. This should work for your problem as well. Note where the constant is retrieved from and how all methods are stacked together to get the data and the last row.
Key functions:
def get_column_after(self, column, offset):
for item in self.ws.Range("{0}{1}:{0}{2}".format(column, offset, self.get_last_row_from_column(column))).Value:
print(item[0])
def get_last_row_from_column(self, column):
return self.ws.Range("{0}{1}".format(column, self.ws.Rows.Count)).End(win32com.client.constants.xlUp).Row
NOTE: This code is a work in progress and at the moment only supports one worksheet, one workbook per instance. I'm sure you can figure out a way to get this to work in your project though.
import string
import win32com.client
SOURCE_PATH = "C:\ExternData\somefile.xlsx"
WORKSHEET_NAME = "WS_1"
class ExcelInstance():
def __init__(self, wb=None):
self.source_path = SOURCE_PATH
try:
self.app = win32com.client.gencache.EnsureDispatch('Excel.Application')
except:
print("Application could not be opened.")
return
try:
self.open_workbook()
except:
print("Workbook could not be opened.")
return
try:
self.ws = self.wb.Worksheets(WORKSHEET_NAME)
except:
print("Worksheet not found.")
return
self.app.Visible = True
self.app.WindowState = win32com.client.constants.xlMaximized
def open_workbook(self):
"""
If it doesn't open one way, try another.
"""
try:
self.wb = self.app.Workbooks(self.source_path)
except Exception as e:
try:
self.wb = self.app.Workbooks.Open(self.source_path)
except Exception as e:
print(e)
self.wb = None
def get_column_after(self, column, offset):
for item in self.ws.Range("{0}{1}:{0}{2}".format(column, offset, self.get_last_row_from_column(column))).Value:
print(item[0])
def get_last_row_from_column(self, column):
return self.ws.Range("{0}{1}".format(column, self.ws.Rows.Count)).End(win32com.client.constants.xlUp).Row
def main():
f = ExcelInstance()
f.get_column_after("A", 3)
if __name__ == "__main__":
main()
xlUp is nothing short of a pre-defined constant.
So the most simple way is to type the code in the direct window of the VBE
? xlUp
you will see the following result.
-4162
add the line to your python code
xlUp = -4162
Done!
Replace .End(xlUp).Row with .End(3).Row.
i use this stupid way to do . not sure have problem or not. so far work for myself
def find_lastrow_oncolumn(worksht, colno):
used = worksht.UsedRange
nrows = used.Row + used.Rows.Count - 1
lastrow_havevalue = 0
for k in range(nrows):
if worksht.Cells(nrows-k, colno).Value is not None:
print worksht.Cells(nrows-k, colno).Value, nrows-k , k
lastrow_havevalue = nrows-k
break
return lastrow_havevalue

Tkinter radio button clear / reset values

I am writing a Tkinter program for the first time and have a question on radio buttons. What I am trying to do is this:
open a set of images (one at a time).
When an image is opened, annotate a value using the radio button.
Collect this value in a list
So, in this example I have 2 compounds and the list would have 2 annotations.
The problem I have is, if by mistake the user clicks radiobutton 2 instead of one, and then corrects him/herself, the list will have 4 items (3 for the first image, 1 for the second). How do I handle this, so that the list will have only 2 values?
import Tkinter as tk
from PIL import ImageTk, Image
from tkFileDialog import askopenfilename
cmp_list = ["VU435DR","VU684DR"]
li = []
li_final = []
def sel():
selection = str(var.get())
if selection == "1":
li.append("Antagonist")
elif selection == "2":
li.append("Agonist")
for i in range(len(cmp_list)):
root = tk.Tk()
var = tk.IntVar()
ig = str(cmp_list[i] + '.png')
img = ImageTk.PhotoImage(Image.open(ig))
panel = tk.Label(root,image=img)
panel.pack(side = "top",fill="none",expand="no")
#w = tk.Text(height=2,width=50)
#w.pack(side='right")
q = tk.Radiobutton(root,text="Antagonist",command=sel,value=1,variable=var)
q.pack()
r = tk.Radiobutton(root,text="Agonist",command=sel,value=2,variable=var)
r.pack()
root.mainloop()
print li
Your code is creating more than one instance of tk.Tk(). This is not how Tkinter was designed to work, and it will yield unpredictable behavior. A proper Tkinter program always has exactly one instance of tk.Tk().
If you need more than one window, for the second and subsequent windows you should create an instance of tk.Toplevel.
To answer your specific question about how to handle someone first hitting one radiobutton and then the other -- the problem is that you are unconditionally appending to your list each time they click on a radiobutton. The solution is to use some sort of flag or indicator to know whether one of the radiobuttons has been clicked, or change your code so that it doesn't matter.
Let's look at that second option - make it so it doesn't matter. When you open up a new image you can automatically append a value to your list. In this case, set it to None to say that nothing has been picked yet. Then, in sel, you would always replace the last element rather than append a new element, since you know that the last element always refers to the current compound.

Variable updating one step behind in list searcher in Tkinter

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

Categories

Resources