Python - Change the textField after browsing - MAYA - python

I've got the most annoying problem with my GUI Exporter in Maya. I've made the textField etc. work, but I can't change the value of the textField after it's created, which is what I need to do.
What I want to do for example is, let's say the filepath is none from the beginning. The textField has now printed out: "None" in it, but after you press browse and select a directory, I want it to change None to the directory path etc.
That's the only problem I currently have and the error code received is this:
Error: RuntimeError: file C:\Program Files\Autodesk\Maya2015\Python\lib\site-packages\pymel\internal\pmcmds.py line 134: Too many children in layout: rowLayout3 #
Code:
#Setup the window using the returned window
def setupWindow(self, new_window):
try:
frame_layout = pm.frameLayout(labelVisible = False, marginWidth = 5, marginHeight = 5)
pm.columnLayout(w = 350, h = 300)
pm.text(label = "Filepath: ")
self.textField = pm.textField("FieldNorm", text = "%s" % self.filePath, editable = False, w = 350, h = 20)
pm.button(label = "Browse", w = 100, h = 20, command = self.browse)
pm.rowLayout(numberOfColumns = 2, adjustableColumn = 1, w = 350, h = 25)
pm.button(label = "Export", w = 200, h = 25, command = self.export)
pm.button(label = "Close", w = 100, h = 25, command = pm.Callback(self.closeButton, new_window))
except:
print "<Setting up window failed>"
#Show the returned window
def showWindow(self, new_window):
if new_window is not None:
pm.showWindow(new_window)
else:
print "<Window does not exist!>"
#Browse Directory and Paste into textField
def browse(self, filePath):
self.filePath = pm.fileDialog2(dialogStyle = 2, returnFilter = 1, fileFilter = "*.obj")
if self.filePath is not None:
self.textField = pm.textField("FieldNorm", text = "%s" % self.filePath, editable = False, w = 350, h = 20)
else:
print "<No changes has been made!>"

Looks like you need the edit flag in the pm.textField line in browse()
pm.textField("FieldNorm", edit=True, text = "%s" % self.filePath)

The error means you are adding a new control, probably to the rowlayout at the end of the setupWindow function which holds the two buttons - Maya thinks you're adding a third
If you want to update the contents of self.textfield in the browse functions, you want
pm.textField(self.textField, e=True, text = "%s" % self.filePath, editable = False, w = 350, h = 20)
which will edit the already created field. The line in the example
self.textField = pm.textField("FieldNorm", text = "%s" % self.filePath, editable = False, w = 350, h = 20)
is trying to create a new one, as #julianMann points out

Related

Tkinter delete not working on referenced entry when referenceing lenth of entry

I have a tkinter window class that I've made and my delete function is not working properly.
my_window = tk.Tk()
class QuoteForm():
def __init__(self,master):
self.file_data = ''
self.master = master
self.master.rowconfigure(0, weight=1)
self.master.rowconfigure(1, weight= 1)
self.master.rowconfigure(2, weight = 1)
master.geometry('600x400')
master.resizable(False,False)
#create the frames
self.directory_frm = tk.Frame(master=master)
self.directory_frm.grid(row=0) #this is the frame for the directory
self.add_on_frm = tk.Frame(master=master)
self.add_on_frm.grid(row=1) #this is the frame for add-ons input
self.button_frm = tk.Frame(master=master)
self.button_frm.grid(row=2) #this is the frame for
#creates buttons, entries, labels
self.load_directory_frame() #creates and grids the directory button
self.load_add_on_frame() #creates and grids the entry buttons and labels
self.load_button_frame() #creates and grids the buttons
my_window.mainloop()
def load_add_on_frame(self):
vcmd = (self.master.register(self.validate_ent), '%S')
#create inputs and labels for add-ons
self.trip_ent = tk.Entry(master=self.add_on_frm,validate = 'key', validatecommand = vcmd, name='trip_ent')
self.trip_ent.grid(column= 1, row = 0)
self.raw_cutouts_ent = tk.Entry(master=self.add_on_frm,validate = 'key', validatecommand = vcmd)
self.raw_cutouts_ent.grid(column= 3, row = 0)
def clear_entries(self):
entries = (self.trip_ent, self.raw_cutouts_ent) #list of entries to loop (there are a total of 12 in the actual code)
for entry in entries:
entry.delete(0,len(entry.get())) #this is where the trouble seems to happen
new_quote = QuoteForm(my_window)
My problem is that the on the second to last line of code (starting with 'entry.delete')
Typically you would do 'entry.delete(0,END)' but because entry is a variable the code won't run with END.
'END' is an invalid index, and 'end' just does the same as pulling the length, and so i tried to make it dynamic by making the 'end' the length of whatever is in the entry.
When i do that however, it deletes nothing [i also tried forcing it with int(len(entry.get()))]. If i manually enter an integer it will delete everything up to that integer, including if it's the same as the length of that entry, and I put breaks to confirm that i'm getting an int return and I am.
I realize i could just write a line of code to delete each entry individually, but there's a totaly of 12 and I would like to clean it up.
I'm adding the full code to be able to run below
import os
import re
import tkinter as tk
from tkinter import filedialog as fd
from tkinter import messagebox
import pandas as pd
my_window = tk.Tk()
class QuoteForm():
def __init__(self,master):
self.file_data = ''
self.master = master
self.master.rowconfigure(0, weight=1)
self.master.rowconfigure(1, weight= 1)
self.master.rowconfigure(2, weight = 1)
master.geometry('600x400')
master.resizable(False,False)
self.directory_frm = tk.Frame(master=master)
self.directory_frm.grid(row=0) #this is the frame for the directory
self.add_on_frm = tk.Frame(master=master)
self.add_on_frm.grid(row=1) #this is the frame for add-ons input
self.button_frm = tk.Frame(master=master)
self.button_frm.grid(row=2) #this is the frame for
self.load_directory_frame()
self.load_add_on_frame()
self.load_button_frame()
my_window.mainloop()
#staticmethod
def get_quote_data(filepath):
#read csv to get job infomation for pricing
try:
if filepath:
job_info = pd.read_csv(filepath,
index_col=0, #set index column
skiprows=range(4), #skip first 4 rows
usecols=['Item','Quan'])
job_info = job_info.drop(labels='Grand Total:', axis= 0)
customer_info = pd.read_csv(filepath, header=None,
skiprows= lambda x: x not in range(2), #skip any row beyond first two rows
usecols=[0,1]) #use first two columns
customer_info = {customer_info.at[0,0].replace(':',''): customer_info.at[0,1], ##formatting the data for legibility
customer_info.at[1,0].replace(':','') : customer_info.at[1,1]}
return [customer_info, job_info]
except:
messagebox.showerror("Data Invalid", "Please make sure you select a valid estimate CSV file.")
def sink_check(self):
####this is to be used at the submit buttons to confirm that there are not more sinks than cutouts
cutouts = self.um_sink_inst_ent.get()
sink_quan_list = (self.std_sink_ent.get(),self.upgrd_sink_ent.get(),self.van_sink_ent.get(),self.cust_sink_temp_ent.get())
sinks = sum(sink_quan_list)
if sinks > cutouts:
return False
###check that the sinks included does not exceed the number of sinks charged for install
return True
def validate_ent(self,input):
if not input:
return True
elif re.fullmatch(r'[0-9]',input):
return True
return False
def open_file(self):
file = fd.askopenfile(mode='r', filetypes=[('CSV Files', '*.csv')])
if file:
filepath = os.path.abspath(file.name)
file_data = self.get_quote_data(filepath)
cust_name = file_data[0]['Name']
job_addr = file_data[0]['Addr']
self.file_select_text['text'] = f"{job_addr} for {cust_name} is currently selected"
def load_directory_frame(self):
file_select_btn = tk.Button(master=self.directory_frm,text= "Select a file",command=self.open_file)
file_select_btn.grid(column=0, row=0)
self.file_select_text = tk.Label(master=self.directory_frm, text = "No File Selected")
self.file_select_text.grid(column=1, row=0)
def load_add_on_frame(self):
vcmd = (self.master.register(self.validate_ent), '%S')
#create inputs and labels for add-ons
self.trip_ent = tk.Entry(master=self.add_on_frm,validate = 'key', validatecommand = vcmd, name='trip_ent')
self.trip_ent.grid(column= 1, row = 0)
self.raw_cutouts_ent = tk.Entry(master=self.add_on_frm,validate = 'key', validatecommand = vcmd)
self.raw_cutouts_ent.grid(column= 3, row = 0)
self.radii_ent = tk.Entry(master=self.add_on_frm,validate = 'key', validatecommand = vcmd)
self.radii_ent.grid(column= 1, row = 1)
self.arcs_ent = tk.Entry(master=self.add_on_frm,validate = 'key', validatecommand = vcmd)
self.arcs_ent.grid(column= 3, row = 1)
self.splay_ent = tk.Entry(master=self.add_on_frm,validate = 'key', validatecommand = vcmd)
self.splay_ent.grid(column= 1, row = 2)
self.wtrfall_ent = tk.Entry(master=self.add_on_frm,validate = 'key', validatecommand = vcmd)
self.wtrfall_ent.grid(column= 3, row = 2)
self.um_sink_inst_ent = tk.Entry(master=self.add_on_frm,validate = 'key', validatecommand = vcmd)
self.um_sink_inst_ent.grid(column= 1, row = 3)
self.farm_sink_co_ent = tk.Entry(master=self.add_on_frm,validate = 'key', validatecommand = vcmd)
self.farm_sink_co_ent.grid(column= 3, row = 3)
self.std_sink_ent = tk.Entry(master=self.add_on_frm,validate = 'key', validatecommand = vcmd)
self.std_sink_ent.grid(column= 1, row = 4)
self.upgrd_sink_ent = tk.Entry(master=self.add_on_frm,validate = 'key', validatecommand = vcmd)
self.upgrd_sink_ent.grid(column= 3, row = 4)
self.van_sink_ent = tk.Entry(master=self.add_on_frm,validate = 'key', validatecommand = vcmd)
self.van_sink_ent.grid(column= 1, row = 5)
self.cust_sink_temp_ent = tk.Entry(master=self.add_on_frm,validate = 'key', validatecommand = vcmd)
self.cust_sink_temp_ent.grid(column= 3, row = 5)
trip_lbl = tk.Label(master=self.add_on_frm,text = "Extra Trip(s)")
trip_lbl.grid(column= 0, row = 0)
raw_cutouts_lbl = tk.Label(master=self.add_on_frm,text = "Unpolished Cutout(s)")
raw_cutouts_lbl.grid(column= 2, row = 0)
radii_lbl = tk.Label(master=self.add_on_frm,text = "Radii")
radii_lbl.grid(column= 0, row = 1)
arcs_lbl = tk.Label(master=self.add_on_frm,text = "Arc(s)")
arcs_lbl.grid(column= 2, row = 1)
splay_lbl = tk.Label(master=self.add_on_frm,text = "Splay(s)")
splay_lbl.grid(column= 0, row = 2)
wtrfall_lbl = tk.Label(master=self.add_on_frm,text = "Waterfal Leg(s)")
wtrfall_lbl.grid(column= 2, row = 2)
um_sink_inst_lbl = tk.Label(master=self.add_on_frm,text = "Install of UM Sink(s)")
um_sink_inst_lbl.grid(column= 0, row = 3)
farm_sink_co_lbl = tk.Label(master=self.add_on_frm,text = "Farm Sink C/O")
farm_sink_co_lbl.grid(column= 2, row = 3)
std_sink_lbl = tk.Label(master=self.add_on_frm,text = "Standard 18ga Sink(s)")
std_sink_lbl.grid(column= 0, row = 4)
upgrd_sink_lbl = tk.Label(master=self.add_on_frm,text = "Upgrade 18ga Sink(s)")
upgrd_sink_lbl.grid(column= 2, row = 4)
van_sink_lbl = tk.Label(master=self.add_on_frm,text = "Vanity Sink(s)")
van_sink_lbl.grid(column= 0, row = 5)
cust_sink_temp_lbl = tk.Label(master=self.add_on_frm,text = "Customer Sink Template(s)")
cust_sink_temp_lbl.grid(column= 2, row = 5)
def load_button_frame(self):
submit_btn = tk.Button(master=self.button_frm, text='Submit')
submit_btn.grid(column=0,row=0)
clear_btn = tk.Button(master=self.button_frm,text='Clear',command=self.clear_entries)
clear_btn.grid(column=1, row=0)
advanced_btn = tk.Button(master=self.button_frm,text='Advanced')
advanced_btn.grid(column=2, row=0)
def clear_entries(self):
entries = (self.trip_ent, self.raw_cutouts_ent, self.radii_ent, self.arcs_ent, self.splay_ent, #list of entry boxes on the form
self.wtrfall_ent, self.um_sink_inst_ent, self.um_sink_inst_ent, self.farm_sink_co_ent,
self.std_sink_ent, self.upgrd_sink_ent, self.van_sink_ent, self.cust_sink_temp_ent)
for entry in entries:
entry.delete(0,tk.END)
new_quote = QuoteForm(my_window)
It's all about yourvalidate_ent function. Only when it returns true then your entry text can change. While typing tkinter just sent single chars like '1','2','a'. Even when you remove with backspace, this function gets the character you are trying to remove. However when you try to clear it function gets as an input whole string like '123543123'. This is not takes place inside r'[0-9]' reguler expression and you return false so tkinter denies removing it.
There is two simple solution to fix this.
First one add another condition for longer input like:
def validate_ent(self,input):
if not input:
return True
elif re.fullmatch(r'[0-9]',input):
return True
if(len(input)>2):
return True
return False
However I do not recommend this one because if someone copy paste longer inputs then it can write letters inside entry boxes.
def validate_ent(self,input):
if not input:
return True
elif re.fullmatch(r'[0-9]*',input):
return True
return False
In here we added a asteriks to reguler expression. Now it's accepting numbers bigger then 9. Now people can also paste numbers that fits into this rule. Also removing works as expected!

How do you query an object inside of a text field, to do something with it?

I would like to know how to query a selection entered into a text field group, so I can do something with it. I have created a window to just translate an object that I loaded in the text field. The error is that cont is not defined.
import maya.cmds as cmds
import maya.mel as ml
def set_selected_name (text_field):
cont = cmds.ls (selection = True)
text_field = cmds.textFieldButtonGrp (text_field, edit = True,
text = ''.join (cont),
buttonLabel = '<<<<',
backgroundColor = [0.5098039215686274,
0.5098039215686274,
0.5098039215686274])
return text_field
def translate_x(cont):
cmds.setAttr( cont[0] + '.translateX', 10)
def translate_y():
cmds.setAttr( cont[0] + '.translateY', 10)
def translate_z(*Args):
cmds.setAttr( cont[0] + '.translateZ', 10)
if cmds.window ('window1', q = 1, ex = 1):
cmds.deleteUI ('window1')
cmds.window ('window1',
title = 'Translate Attr',
sizeable = 0,
resizeToFitChildren = True,
menuBar = 1)
cmds.rowLayout (numberOfColumns = 3)
cmds.separator (style = 'double',
height = 6)
cmds.rowLayout (parent = 'window1',
numberOfColumns = 4)
ddd = cmds.textFieldButtonGrp (editable = False,
text = 'Obj',
backgroundColor = [0.029495689326314183,
0.5488975356679637,
0.5488975356679637],
buttonLabel = '<<<<')
cmds.textFieldButtonGrp (ddd, edit = True,
buttonCommand = 'set_selected_name (ddd)')
cmds.separator (style = 'double',
height = 6)
cmds.rowLayout (parent = 'window1',
numberOfColumns = 6)
cmds.separator (style = 'double',
height = 6)
cmds.button (command = 'translate_y()',
backgroundColor = [1.0,
0.7300068665598535,
0.7300068665598535],
label = 'Translate Y')
cmds.separator (style = 'double',
height = 6)
cmds.button (command = 'translate_x(cont)',
backgroundColor = [1.0,
0.9733272297245746,
0.7333333333333333],
label = 'Translate X')
cmds.separator (style = 'double',
height = 6)
cmds.button (command = 'translate_z()',
backgroundColor = [0.7333333333333333,
1.0,
0.7600061036087586],
label = 'Translate Z')
cmds.columnLayout (parent = 'window1')
cmds.separator (style = 'double',
height = 3)
cmds.showWindow ('window1')
# ~ BABLAAM ~
Create any object you like, loaded into the text field and then try to translate with buttons.
You have several problems in your code.
In the translate commands you always use cont[0]. cont is only used in the function set_selected_name() and is a local variable what means it is deleted as soon as the function is completed.
You can use a string as command in the button command, but this only works with static values. You should use lambdas to use functions with arguments.
The cont Problem can be solved by using a global variable, but it shouldn't since global variables are the source of all evil. A much more elegant way would be to enclose you UI in one python class and use instance variables to get the selection.
I have adjusted the code to add the class as you recommended but still having the same issue. By not using the quotes in the button command I get this error when I try to run the script, instead of getting it when I press the button.
Error: NameError: file line 28: name 'translate_x' is not defined
Can you please write a workable version, or place a link from the internet that shows a method using the class and calling methods using buttons? Nothing I have found from my internet search has anything like this and I'm just guessing where thigs should go.
import maya.cmds as cmds
import maya.mel as ml
class move_obj(object):
def __int__(self, *args):
self.cont = cont
self.trans = trans
def set_selected_name(self, *args):
cont = cmds.ls (selection = True)
return cont
def translate_x(self, *args):
trans = cmds.setAttr( cont[0] + '.translateX', 10)
print trans
if cmds.window ('window1', q = 1, ex = 1):
cmds.deleteUI ('window1')
cmds.window ('window1',
title = 'Translate Attr',
sizeable = 0,
resizeToFitChildren = True,
menuBar = 1)
cmds.rowLayout (numberOfColumns = 3)
cmds.button (command = translate_x,
backgroundColor = [1.0,
0.7300068665598535,
0.7300068665598535],
label = 'Translate X')
cmds.showWindow ('window1')

Why wont my code return a value for the data input into a tkinter text box

I have written this code and for some reason it refuses to return any sort of value or input for slef.REV when used in the function post(self) however it will return a value when I try and return a value in the getlen() function which is used to reurn the number of characters in the review.I dont have this problem for any other variables that I retrieve data from within this class. Below is the relevant code, any help would be appreciated. the lines where this problem occures is the first functio calld post(lines 1-5) and 4 lines up from the bottom
def post(self):
MovieID = self.MovID
REV = self.REV
AddReview(conn,cursor,Add_Review,MovieID,REV)
print(REV)
def shrek_film(self):
self.title = "Shrek"
self.MovID = 1
self.root4 = tk.Toplevel()
self.root4.title("Watch Shreck")
self.root4.geometry("1400x800")
frame_4 = tk.Frame(self.root4, bg = "black")
frame_4.pack(fill = tk.BOTH, expand = True, padx=0 , pady=0)
frame_4.grid_columnconfigure(1,weight=1)
self.Create_canvas = tk.Canvas(frame_4, width=2000, height=1080)
self.Create_canvas.place(x=-50, y=-50)
self.Create_img = PhotoImage(file="shrek-landscape.gif")
self.Create_canvas.create_image(20, 20, anchor = NW, image=self.Create_img)
play_button= tk.Button(frame_4,bg="orange",text="play", command = self.addHistory)
play_button.place(x=700,y=400)
play_button.config(font=("Ariel","30"))
def gtelen():
Review = reviewbox.get('1.0',END)
REVLEN = len(Review)
REVLENLEFT = (231-len(Review))
if REVLEN >=230:
lenbox = tk.Label(frame_4 ,text="No words left",bg="orange")
lenbox.place(x=360,y=460)
lenbox.config(font=("Ariel","15"))
else:
lenbox = tk.Label(frame_4 ,text=REVLENLEFT,bg="orange")
lenbox.place(x=360,y=460)
lenbox.config(font=("Ariel","15"))
print(Review)
Words_button = tk.Button(frame_4, bg="orange",text="check number of words remaining", command=gtelen)
Words_button.place(x=150,y=460)
Words_button.config(font=("Ariel","10"))
reviewlable=tk.Label(frame_4,text="Write a review",bg="orange")
reviewlable.place(x=10,y=460)
reviewlable.config(font=("ariel","15"))
Review_button= tk.Button(frame_4,bg="orange",text="See Reviews")#, command = self.ViewReviews)
Review_button.place(x=490,y=450)
Review_button.config(font=("Ariel","15"))
reviewbox= Text(frame_4,width=100,height=12)
reviewbox.place(x=10,y=500)
self.REV = reviewbox.get('1.0',END)
post_button = tk.Button(frame_4,bg="orange",text="Post Review", command = self.post)
post_button.place(x=830,y=650)
post_button.config(font=("Ariel","15"))
You can use Entry instead and use a StringVar
v = StringVar() # Create StringVar
reviewbox = Entry(frame_4, width = 100, height = 12, textvariable = v) # Create Entry widget
reviewbox.place(x = 10, y = 500) # Place Entry widget
self.REV = v.get() # Get contents of StringVar
The line self.REV = reviewbox.get('1.0',END) is being called about a millisecond after creating the text widget. The user will not even have seen the widget yet, much less have had time to type in it.
You can't call the get() method until after the user has had a chance to enter data, such as inside the post method.
def post(self):
MovieID = self.MovID
REV = reviewbox.get("1.0", "end")
AddReview(conn,cursor,Add_Review,MovieID,REV)
print(REV)

How to compare the inputs of two entries using single tkinter function for a bunch of such pairs of entries?

I want to validate two tkinter entries. One called minimum and the other called maximum. Of course, I want to make sure that minimum does not exceed maximum. And there is a third entry called increment which has to be lesser than maximum. There are a set of 15 such entries which I am trying to validate.
I have tried using for loop and tracing the textvariable of each entry. But inside the for loop, I am able to validate only a single entry box. Also, when I skip the validation for that specific one entry called the txtCab, it throws the following exception: If I do it for all the widgets, it does work, but fails some times.
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\beejb\AppData\Local\Programs\Python\Python37-32\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
File "C:\PROSAIL_5B_Fortran\PROSAIL_5B_FORTRAN\PROSAIL.py", line 191, in min_max
minVar = eval("self.txtVar_"+ str(wid)+ "_min.get()")
File "<string>", line 1, in <module>
NameError: name 'self' is not defined
The validation function I have used is:
def min_max(*args):
alltextFields = ["N","Cab","Car","Cw","Cm","Cbrown", "rsoil0","LIDFa","LIDFb","TypeLIDF","LAI","hspot","tts","tto","psi" ]
for wid in alltextFields:
if eval("self." + wid + "_variable.get()"):
minVar = eval("self.txtVar_"+ str(wid)+ "_min.get()")
maxVar = eval("self.txtVar_"+ str(wid) + "_max.get()")
rangeVar = eval("self.txtVar_"+ str(wid) + "_range.get()")
##
## print((minVar))
## print((maxVar))
## print((rangeVar))
if len(minVar) > 0 and len(maxVar):
if (minVar) > (maxVar):
messagebox.showinfo("Input Error", "Minimum should not be greater than maximum")
if len(rangeVar) > 0 and len(maxVar) > 0:
if (rangeVar) > (maxVar) :
messagebox.showinfo("Input Error", "Increment cannot exceed maximum limit")
## print(self.txtVar_Cab_min.get()); print(self.txtVar_Cab_max.get());
## print(self.txtVar_N_min.get()); print(self.txtVar_N_max.get());
if len(self.txtVar_Cab_min.get()) > 0 and len(self.txtVar_Cab_max.get()) > 0 and len(self.txtVar_Cab_range.get()) > 0:
if (self.txtVar_Cab_min.get()) > (self.txtVar_Cab_max.get()):
messagebox.showinfo("Input Data Error", "Minimum should not be greater than maximum!!")
if (self.txtVar_Cab_range.get()) > (self.txtVar_Cab_max.get()):
messagebox.showinfo("Error", "Increment cannot exceed maximum!!")
Another validation function I have tried is:
def validateMRM(self,value, text,W):
vMin,vMax,vRange;
entry = self.controller.nametowidget(W)
print(entry)
if entry == self.txt_N_min:
print(entry.get())
print(self.txtVar_N_max.get())
print(self.txtVar_N_range.get())
alltextFields = ["txt_N","txt_Cab","txt_Car","txt_Cab","txt_Cw","txt_Cw","txt_Cm","txt_Cbrown","txt_Cm", "txt_rsoil0",
"txt_LIDFa","txt_LIDFb","txt_TypeLIDF","txt_LAI","txt_hspot","txt_hspot","txt_tts","txt_tto","txt_psi"
]
for wid in alltextFields:
typeOfVar = wid.split("_")
if entry == eval("self.txt_" + str(typeOfVar[1])+ "_min"):
vMin = eval("self.txtVar_" + str(typeOfVar[1])+ "_min.get()")
print(eval("self.txtVar_" + str(typeOfVar[1])+ "_min.get()"))
vMax = eval("self.txtVar_" + str(typeOfVar[1])+ "_max.get()")
print(eval("self.txtVar_" + str(typeOfVar[1])+ "_max.get()"))
vRange = eval("self.txtVar_" + str(typeOfVar[1])+ "_range.get()")
print(eval("self.txtVar_" + str(typeOfVar[1])+ "_range.get()"))
print(vMin); print(vMax); print(vRange)
if len(vMin) > 0 and len(vMax) > 0 and len(vRange) > 0:
if (vMin) > (vMax):
messagebox.showinfo("Error", "Minimum cannot be greater than maximum")
if (vRange) > (vMax) :
messagebox.showinfo("Error", "Increment cannot exceed the maximum limit")
print(len(entry.get()))
if len(entry.get())>2:
And here is how all the entries are created:
self.lbl_N = tk.Label(self,text="Structure Coefficient(N)",anchor="w",width=40,bg='white'); self.lbl_N.grid(row=3,column=4,padx=4,pady=4);
self.N_variable = tk.BooleanVar()
self.chk_N = tk.Checkbutton(self,variable=self.N_variable, command=lambda:self.show_hide()); self.chk_N.grid(row=3,column=6,padx=4,pady=4);
self.txt_N = tk.Entry(self,width=10,validate = 'key', validatecommand = vcmd); self.txt_N.grid(row=3,column=7,padx=4,pady=4);
self.txtVar_N_min = tk.StringVar(); self.txtVar_N_max = tk.StringVar(); self.txtVar_N_range = tk.StringVar();
self.txtVar_N_min.trace("w", min_max); self.txtVar_N_max.trace("w", min_max); self.txtVar_N_range.trace("w", min_max);
self.txt_N_min = tk.Entry(self,width=5,validate = 'key',textvariable=self.txtVar_N_min, validatecommand = vcmd_min_max);
self.txt_N_max = tk.Entry(self,width=5,validate = 'key', textvariable=self.txtVar_N_max,validatecommand = vcmd_min_max);
self.txt_N_range = tk.Entry(self,width=5,validate = 'key', textvariable=self.txtVar_N_range,validatecommand = vcmd_min_max);
There are a set of fourteen such entries and I need to validate each of them.
But none of this gives the actual output I want. It works some time and fails some other times.
I am not sure why is that happening and I have spent a hell of time with this validation.
I'm not sure whether this answers your question but it should point you in the right direction.
I couldn't make much sense of your code. I've produced a 15 row x 4 column grid.
The 4th column is a message that the 3 fields next to it are 'OK' or if not indicate the problem. The validation is run on the whole grid for each keypress. If this is too slow a validate button could launch the validation instead.
import tkinter as tk
from tkinter import ttk
def rec(): return {'lo': 0, 'hi': 0, 'step': 0, 'ok': '' }
root = tk.Tk()
root.title('SO Question')
def entry(id, ent_dict, var_dict, v=0):
""" Add an Entry Widget to the root, with associated StringVar."""
var_dict[id] = tk.StringVar()
var_dict[id].set(str(v))
ent_dict[id] = ttk.Entry(root, textvariable= var_dict[id], width = 10 )
return ent_dict[id]
def do_validate(lo, hi, step):
""" Return OK if lo, hi and step are consistent else an error string. """
if lo < hi and step < hi: return 'OK'
txt = ''
if lo >= hi:
txt = 'lo >= hi. '
if step >= hi:
txt += 'step >= hi.'
return txt
def conv(txt):
""" Convert text to float. Return 0.0 if not valid float e.g "" or 'a' """
try:
return float(txt)
except ValueError:
return 0.0
def oklabel(ent_dict, var_dict):
""" Add an OK Label to a row. """
lo = conv(var_dict['lo'].get())
hi = conv(var_dict['hi'].get())
step = conv(var_dict['step'].get())
var_dict['ok'] = tk.StringVar()
var_dict['ok'].set(do_validate(lo, hi, step))
ent_dict['ok'] = ttk.Label(root, textvariable = var_dict['ok'], width = -17)
return ent_dict['ok'] # Return the Label object for gridding.
def do_check(*args):
""" Loop through the rows setting the validation string in each one. """
for var_dict in stringvars:
lo = conv(var_dict['lo'].get())
hi = conv(var_dict['hi'].get())
step = conv(var_dict['step'].get())
var_dict['ok'].set(do_validate(lo, hi, step))
# Add column labels
ttk.Label(root, text='Minimums').grid(row=0, column=0)
ttk.Label(root, text =' Maximums').grid(row=0, column=1)
ttk.Label(root, text='Increment').grid(row=0, column=2)
ttk.Label(root, text='Valid').grid(row=0, column=3)
# Create containers for he Entries and Stringvars
entries =[]
stringvars = []
# Add 15 rows of Entries / Validation Labels to the UI.
for row in range(1, 16):
tempe=rec()
tempv=rec()
entry('lo', tempe, tempv, 0).grid(row = row, column=0)
entry('hi', tempe, tempv, 0).grid(row = row, column=1)
entry('step', tempe, tempv, 0).grid(row = row, column=2)
oklabel(tempe, tempv).grid(row = row, column = 3)
entries.append(tempe)
stringvars.append(tempv)
# Bind do_check to all Entry widgets.
root.bind_class('TEntry', '<KeyPress>', do_check, add='+')
root.bind_class('TEntry', '<BackSpace>', do_check, add='+')
root.bind_class('TEntry', '<Delete>', do_check, add='+')
root.mainloop()
In the past I've got stuck trying to validate multiple fields by not allowing inconsistent entries. It is difficult for users to follow what is required to correct fields. They have to work in the correct order. e.g. lo = 100, hi = 9, and step = 1. Should the UI allow the last zero in 100 to be deleted, leaving 10 which is gt 9?
This could be extended to activate a 'Next' button only if all rows are OK.
Edit 1 - Response to Comment
This has a function to create and activate each row of the display. Each row has it's own variables and checking function. They are triggered by the trace on the three Entry StringVars, there's no need to use validate.
import tkinter as tk
from tkinter import ttk
def to_float(txt):
""" Safely convert any string to a float. Invalid strings return 0.0 """
try:
return float(txt)
except ValueError:
return 0.0
def row_n( parent, n, init_show = 0 ):
""" Create one row of the display. """
# tk.Variables
v_show = tk.IntVar()
v_min = tk.StringVar()
v_max = tk.StringVar()
v_incr = tk.StringVar()
v_message = tk.StringVar()
# Initialise variables
v_min.set('0')
v_max.set('1')
v_incr.set('1') # Can the increment be zero?
v_show.set(init_show)
v_message.set("OK")
def do_trace(*args):
""" Runs every time any of the three Entries change value.
Sets the message to the appropriate text.
"""
lo = to_float(v_min.get())
hi = to_float(v_max.get())
inc = to_float(v_incr.get())
if lo < hi and inc <=hi:
v_message.set('OK')
else:
txt = ''
if lo >= hi:
txt += 'Min >= Max'
if inc > hi:
if len(txt): txt += ' & '
txt += 'Incr > Max'
v_message.set(txt)
# Set trace callback for changes to the three StringVars
v_min.trace('w', do_trace)
v_max.trace('w', do_trace)
v_incr.trace('w', do_trace)
def activation(*args):
""" Runs when the tickbox changes state """
if v_show.get():
e_min.grid(row = n, column = 1)
e_max.grid(row = n, column = 2)
e_inc.grid(row = n, column = 3)
message.grid(row = n, column = 4)
else:
e_min.grid_remove()
e_max.grid_remove()
e_inc.grid_remove()
message.grid_remove()
tk.Checkbutton(parent,
text = 'Structure Coefficient {} :'.format(n),
variable = v_show, command = activation ).grid(row = n, column = 0)
e_min = tk.Entry(parent, width=5, textvariable = v_min)
e_max =tk.Entry(parent, width=5, textvariable = v_max)
e_inc = tk.Entry(parent, width=5, textvariable = v_incr)
message = tk.Label(parent, width=-15, textvariable = v_message)
activation()
return { 'Min': v_min, 'Max': v_max, 'Inc': v_incr }
def show_results():
print('Min Max Inc')
for row in rows:
res = '{} {} {}'.format(row['Min'].get(), row['Max'].get(), row['Inc'].get())
print( res )
root = tk.Tk()
root.title('SO Question')
ttk.Label(root, text='Minimums').grid(row=0, column=1)
ttk.Label(root, text =' Maximums').grid(row=0, column=2)
ttk.Label(root, text='Step', width = 5 ).grid(row=0, column=3)
ttk.Label(root, text='Valid', width = 15 ).grid(row=0, column=4)
rows = []
for r in range(1,16):
rows.append(row_n(root, r, init_show=r%3 == 0 ))
tk.Button(root, command=show_results, text = ' Show Results ').grid(column=1, pady = 5)
root.mainloop()
This is another approach. Does this help.
Here's another suggestion. Incorporate the Label and Entry in the row-n function. Include activating / disabling the Entry in the activate function. The row_n function is executed in a loop through a list of the descriptions you want.
import tkinter as tk
row_names = [ "Structure Coefficient(N)", "Chlorophyll Content(Cab) (µg.cm-2)",
"Carotenoid content(Car) (µg.cm-2)", "Brown pigment content(Cbrown)(arbitrary units)"]
def row_n(parent, desc, n, init_show = 0 ):
""" Create one row of the display. """
# tk.Variables
v_show = tk.IntVar()
v_min = tk.StringVar()
v_max = tk.StringVar()
v_incr = tk.StringVar()
v_fixed = tk.StringVar() # New StringVar
v_message = tk.StringVar()
v_show.set(init_show)
v_message.set("OK")
def do_trace(*args):
""" Runs every time any of the three Entries change value.
Sets the message to the appropriate text.
"""
lo = to_float(v_min.get())
hi = to_float(v_max.get())
inc = to_float(v_incr.get())
if lo < hi and inc <=hi:
v_message.set('OK')
else:
txt = ''
if lo >= hi:
txt += 'Min >= Max'
if inc > hi:
if len(txt): txt += ' & '
txt += 'Incr > Max'
v_message.set(txt)
# Set trace callback for changes to the three StringVars
v_min.trace('w', do_trace)
v_max.trace('w', do_trace)
v_incr.trace('w', do_trace)
def activation(*args):
""" Runs when the tickbox changes state """
if v_show.get():
e_min.grid(row = n, column = 8)
e_max.grid(row = n, column = 9)
e_inc.grid(row = n, column = 10)
message.grid(row = n, column = 11)
e_fixed.config(state = 'disabled') # Disable the base Entry
else:
e_min.grid_remove()
e_max.grid_remove()
e_inc.grid_remove()
message.grid_remove()
e_fixed.config(state = 'normal') # Enable the base Entry Widget
tk.Label(parent, text = desc ).grid(row = r+1, column = 4 ) # Add the desc. Label
e_fixed = tk.Entry(parent, textvariable = v_fixed) # Add the new Entry widget
e_fixed.grid(row = r+1, column = 5)
tk.Checkbutton(parent,
text = ' '.format(n),
variable = v_show, command = activation ).grid(row = n, column = 6)
e_min = tk.Entry(parent, width=5, textvariable = v_min)
e_min.config(font=('Candara', 15))
e_max =tk.Entry(parent, width=5, textvariable = v_max)
e_max.config(font=('Candara', 15))
e_inc = tk.Entry(parent, width=5, textvariable = v_incr)
e_inc.config(font=('Candara', 15))
message = tk.Label(parent, width=-15, textvariable = v_message)
message.config(font=('Candara', 15))
activation()
return { 'Min': v_min, 'Max': v_max, 'Inc': v_incr, 'Fixed': v_fixed }
# The 'Fixed' field added to the dictionary to return
def print_row(row):
fmt = 'Min: {}, Max: {}, Inc: {}, Fixed: {}'
print(fmt.format(
row['Min'].get(), row['Max'].get(), row['Inc'].get(), row['Fixed'].get()
))
def to_float(txt):
""" Safely convert any string to a float. Invalid strings return 0.0 """
try:
return float(txt)
except ValueError:
return 0.0
# GUI Start
root = tk.Tk()
root.title('Validation wth Trace')
# Header Labels
tk.Label(root,text="Min").grid(row=0,column=8,padx=4,pady=4)
tk.Label(root,text="Max").grid(row=0,column=9,padx=4,pady=4)
tk.Label(root,text="Inc").grid(row=0,column=10,padx=4,pady=4)
# Body of rows
rows = []
for r, r_text in enumerate(row_names):
rows.append(row_n( root, r_text, r+1))
root.mainloop()
print("Strings in the Entry fields")
for r, row in enumerate(rows):
print('Row: ', r, 'Data:', end=' ')
print_row(row)
HTH. Seeing your code in the inked question you may prefer to make row_n a class.

Tkinter: get data from a Entry widget

I am workin with Python 2.7. This is the initial part of a longer programme. What I want to do is to add a new username, together with is height and weight. I use a .txt file to store user data,
example userlist3.txt:
add_new_user 1 1
unknown_user 170 70
monthy 185 83
[empty line]
This is the code:
from Tkinter import *
user_list = Tk()
user_list.title('Users')
def add_new_user():
global select
global height
global weight
select = name.get()
height = h.get()
weight = w.get()
f = ' '
us=open("userlist3.txt","a")
print name, height, weight
us.write(select + f + str(height) + f + str(weight) + "\n")
us.close()
# add_user.destroy() # it doesn't work
user_list.destroy()
def onSelect(ev): # (10)
global select
select=listb.get(listb.curselection()) # (12)
lab.configure(text=select) # (14)
global name
global h
global w
if select == 'add_new_user':
add_user = Tk()
add_user.title('New user')
a=Label(add_user,text="Your username").pack()
name = StringVar()
NAME = Entry(add_user,textvariable = name).pack()
b=Label(add_user,text="Your height (in cm)").pack()
h = IntVar()
H = Entry(add_user,textvariable = h).pack()
c=Label(add_user,text="Your weight (in kg)").pack()
w = IntVar()
W = Entry(add_user,textvariable = w).pack()
Add_New_User=Button(add_user,text="Add new user data",command=add_new_user).pack()
add_user.mainloop()
else:
user_list.destroy()
a=open("userlist3.txt","r")
b =[]
for linea in a:
b.append(linea)
a.close()
e = []
for i in range(len(b)):
e.append(b[i].split())
userlist = []
heightlist = []
weightlist = []
for i in range(len(e)):
userlist.append(e[i][0])
heightlist.append(e[i][1])
weightlist.append(e[i][2])
sbar = Scrollbar(user_list, orient=VERTICAL) # (20)
listb = Listbox(user_list, width=30, height=4) # (22)
sbar.config(command=listb.yview) # (30)
listb.config(yscrollcommand=sbar.set) # (32)
sbar.pack(side=RIGHT, fill=Y) # (40)
listb.pack() # (42)
lab=Label(user_list,text="Double Click on User") # (50)
lab.pack()
for c in userlist: listb.insert(END,c)
listb.bind('<Double-1>',onSelect) # (70)
user_list.mainloop()
for d in range(1,len(userlist)):
if userlist[d] == select:
height = int(heightlist[d])
weight = int(weightlist[d])
print "Selected user is: ",select
print height
print weight
It works with user already present in the txt file, but not if I want to add a new one. When I try, I got 'PY_VAR0 0 0' printed on the shell and '' 0 0 added in a new line in the txt file. Obviously these data are of no use in the following steps of my software.I'm probably missing a .get() somewhere.
When you see something like PY_VAR0, that means you are printing out the instance of a StringVar (or IntVar or whatever), rather than printing out the value of the variable. If you are using one of the special vars, you must call the get() method to get the value.
In your specific case, change this:
print name, width, height
To this:
print name.get(), width, height
Thanks Fiver for your advice!
metaphy's solution works, I solved the problem modifying line 28 in
add_user = Toplevel(user_list)

Categories