I'm working on a small project and I'm having issues retrieving the values stored in combo boxes. The program has a "plus" button that creates additional boxes beneath the existing ones. They are created by calling a "create" function that makes a new instance of the ComboBox class, where the box is created and put onto the screen. A separate "submit" function is then supposed to loop through and retrieve all of the box values and store them in a list. My main flaw is that I used data in the variable names, but I have no clue how else to do this in this scenario. Does anyone have an alternative solution?
(there are some off screen variables that are show used here as parameters, but there are definitely not the source of the issue)
class ComboBox:
def __init__(self, master, counter, fields):
self.master = master
self.counter = counter
self.fields = fields
self.field_box = ttk.Combobox(width=20)
self.field_box["values"] = fields
self.field_box.grid(row=counter + 1, column=0, pady=5)
def get_value(self):
value = self.field_box.get()
return value
def create():
global entry_counter
name = "loop"+str(entry_counter-1)
name = ComboBox(window, entry_counter, fields)
values.append(name.get_value())
entry_counter += 1
def submit():
for i in range(1, entry_counter):
name = "loop" + str(entry_counter-1)
values.append(name.get_value())
For example, if I created 2 boxes and selected the options "test1" and "test2" I would want the my values list to contain ["test1, "test2"]
Not sure I understand the question right, but I guess you are asking about how to loop throw all instances of ComboBox. You can just create an global array, append new instance into it in create() method:
comboboxes = []
def create():
...
comboboxes.append(new_instance)
def submit():
for combobox in comboboxes:
...
You're on the right track with .get(). I believe your solution is that your get_value function also needs an event parameter:
def get_value(self, event):
value = self.field_box.get()
return value
See the following:
Getting the selected value from combobox in Tkinter
Retrieving and using a tkinter combobox selection
Related
I'm working on an application in tkinter. I have many Entry widgets in UI, and a few classes in app engine. I need to bind tkinter variables of those entries to instances attributes.
i.e.:
class Pipe(Variable):
"""class for pipes"""
def __init__(self):
self.diameter = 0
self.variables = {}
pipe1 = Pipe(self)
pipe2 = Pipe(self)
I want to bind value from one entry to pipe1.diameter, and value from another entry to pipe2.diameter. I'm doing it by a trace function, where is lambda statement, pointing to a function, which identifies entry, and, using a dictionary proper for each instance, pass a value from entry to dictionary value. Dictionaries are produced like here, and then passed as instance attribute:
def pipe1_vars(object_):
variables = {
'ui_variable_name_for_pipe1_diameter': [object_.diameter]
}
return variables
def pipe2_vars(object_):
variables = {
'ui_variable_name_for_pipe2_diameter': [object_.diameter]
}
return variables
pipe1.variables = pipe1_vars(pipe1)
pipe2.variables = pipe2_vars(pipe2)
Unfortunately, Variable class method, assigning value, isn't working properly.
class Variable():
def set_var_value(variable_name, value):
ui_variable = tkinterbuilder.get_variable(variable_name)
self.variables[variable_name][0] = value
if ui_variable.get() != value:
ui_variable.set(value)
Obviously self.variables[variable_name][0] is something different than self.diameter. The dictionary value is changing, but instance.diameter stays the same.
How can I pass a real instance attribute to this method, instead of a copy in a dictionary value?
I'm assuming it is important to my app, to build something working as those dictionaries, because i need to bind similar attributes of different pipes to different entries - so it's have to be defined outside of a Pipe() class. I don't know if I should change dictionary to something else, or maybe should I rebuild those functions, building dictionary. I've run out of ideas, what to ask google.
Code is much complex, I've posted only most important elements, but if any other details are important, please note in comment.
If the number of Pipe attributes is small, make them properties, and when you create a Pipe object, pass it the corresponding tk binded variable:
import tkinter as tk
from tkinter import Tk, ttk
root = Tk()
var_e1 = tk.StringVar()
def print_e1():
print(var_e1.get())
def inc_e1():
var_e1.set(int(var_e1.get())+1)
class Pipe():
def __init__(self, tkvar):
self.tkvar = tkvar
tkvar.set('')
#property
def diameter(self):
return self.tkvar.get()
#diameter.setter
def diameter(self, value):
self.tkvar.set(value)
e1 = tk.Entry(root, textvariable=var_e1)
b1 = tk.Button(root, text='Print e1', command=print_e1)
b2 = tk.Button(root, text='Increment e1', command=inc_e1)
e1.pack(side=tk.LEFT)
b1.pack()
b2.pack()
p1 = Pipe(var_e1)
p1.diameter = 200
root.mainloop()
from tkinter import *
import pandas as pd
df = pd.DataFrame({'item': list('abcde'), 'default_vals': [2,6,4,5,1]})
def input_data(df):
box = Tk()
height = str(int(25*(df.shape[0]+2)))
box.geometry("320x" + height)
box.title("my box")
#initialise
params, checkButtons, intVars = [], [], []
default_vals = list(df.default_vals)
itemList = list(df.item)
for i,label in enumerate(itemList):
Label(box, text = label).grid(row = i, sticky = W)
params.append(Entry(box))
params[-1].grid(row = i, column = 1)
params[-1].insert(i, default_vals[i])
intVars.append(IntVar())
checkButtons.append(Checkbutton(variable = intVars[-1]))
checkButtons[-1].grid(row = i, column = 3)
def sumbit(event=None):
global fields, checked
fields = [params[i].get() for i in range(len(params))]
checked = [intVars[i].get() for i in range(len(intVars))]
box.destroy()
#add submit button
box.bind('<Return>', sumbit)
Button(box, text = "submit",
command = sumbit).grid(row = df.shape[0]+3, sticky = W)
box.focus_force()
mainloop()
return fields, checked
I am new to tkinter and not sure what I a trying to do is possible.
At present, my script (simplified here to a function rather than a class) builds a box with all the default values entered in the fields:
Instead, I want to start with empty fields which, once the corresponding checkButton is clicked will get the default value (should still be able to manually change it through the field as happens now), and also, once any value is entered in a given field, the corresponding checkButton is selected.
Are these possible?
It is possible, but let me preface my solution with a few cautions on your current code:
It's rarely advisable to do a star import (from tkinter import *) as you don't have any control over what gets imported into your namespace. It's more advisable to explicitly import what you need as a reference:
import tkinter as tk
tk.Label() # same as if you wrote Label()
tk.IntVar() # same as if you called IntVar()
The behaviour you wanted, while possible, might not be necessarily user friendly. What happens when a user has already entered something, and unchecks the checkbox? Or what happens if the checkbox was selected and then the user deleted the information? These might be things you want to think about.
Having said that, the solution is to use add a trace callback function over your variable(s). You'll also need to add a StringVar() for the Entry boxes as you wanted a two way connection:
# add strVars as a list of StringVar() for your Entry box
params, checkButtons, intVars, strVars = [], [], [], []
During your iteration of enumerate(itemList), add these:
# Create new StringVar()
strVars.append(StringVar())
# add a trace callback for tracking changes over the StringVar()
strVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_strVar(idx))
# update your Entry to set textvariable to the new strVar
params.append(Entry(box, textvariable=strVars[-1]))
# similarly, add a trace for your IntVar
intVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_intVar(idx))
You'll need to define the two trace callback functions before you iterate through the widget creations:
def trace_intVar(idx):
# if Checkbox is checked and Entry is empty...
if intVars[idx].get() and not params[idx].get():
# prefill Entry with default value
params[idx].insert(0, df.default_vals[idx])
def trace_strVar(idx):
# if Entry has something...
if strVars[idx].get():
# and Checkbox is not checked...
if not intVars[idx].get():
# Set the checkbox to checked.
intVars[idx].set(True)
# but if Entry is empty...
else:
# Set the Checkbox to uncheck.
intVars[idx].set(False)
Remember I mentioned the behaviour - I took a little liberty to clear the Checkbox if Entry is empty. If you however don't wish to do that, you'll need to modify the handling a little.
Note on the way the trace_add is written. The callback function is always passed with three default arguments, namely the Variable Name, The Variable Index (if any) and Operation (see this great answer from Bryan Oakley). Since we don't need any in this case (we can't reverse reference the variable name to the linked index between the variable lists), we'll have to manually wrap the callback with another lambda and ignore the three arguments:
lambda var, # reserve first pos for variable name
var_idx, # reserve second pos for variable index
oper, # reserve third pos for operation
idx=i: # pass in i by reference for indexing point
trace_intVar(idx) # only pass in the idx
You cannot just pass lambda...: trace_intVar(i) as i will be passed by value instead of reference in that case. Trust me, I've made this error before. Therefore we pass another argument idx with its default set to i, which will now be passed by reference.
If trace_add doesn't work, use trace('w', ...) instead.
For prosperity, here's the complete implemented solution to your question:
from tkinter import *
import pandas as pd
df = pd.DataFrame({'item': list('abcde'), 'default_vals': [2,6,4,5,1]})
def input_data(df):
box = Tk()
height = str(int(25*(df.shape[0]+2)))
box.geometry("320x" + height)
box.title("my box")
#initialise
params, checkButtons, intVars, strVars = [], [], [], []
default_vals = list(df.default_vals)
itemList = list(df.item)
def trace_intVar(idx):
if intVars[idx].get() and not params[idx].get():
params[idx].insert(0, df.default_vals[idx])
def trace_strVar(idx):
if strVars[idx].get():
if not intVars[idx].get():
intVars[idx].set(True)
else:
intVars[idx].set(False)
for i,label in enumerate(itemList):
Label(box, text = label).grid(row = i, sticky = W)
strVars.append(StringVar())
strVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_strVar(idx))
params.append(Entry(box, textvariable=strVars[-1]))
params[-1].grid(row = i, column = 1)
#params[-1].insert(i, default_vals[i]) # <-- You don't need this any more
intVars.append(IntVar())
intVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_intVar(idx))
checkButtons.append(Checkbutton(variable = intVars[-1]))
checkButtons[-1].grid(row = i, column = 3)
def sumbit(event=None):
global fields, checked
fields = [params[i].get() for i in range(len(params))]
checked = [intVars[i].get() for i in range(len(intVars))]
box.destroy()
#add submit button
box.bind('<Return>', sumbit)
Button(box, text = "submit",
command = sumbit).grid(row = df.shape[0]+3, sticky = W)
box.focus_force()
mainloop()
return fields, checked
I have an odd question about tkinter entry boxes:
I know that I can write
add_charge = tk.Button(self, text='Add Data', command=function)
and that if the function is dependent on parameters I can write it as
add_charge = tk.Button(self, text='Add Data', command=lambda: function(parameters)
but my issue is this, I have a function defined as this
def add_charge(x,y,i, data):
""" x,y are locations if the charge, i is the value of charge, data is existing charges"""
temp = data.append(Charge(x,y,i)) #Charge is a predefined class which creates the charge object
return temp
What I need to do is save temp to a variable and replace the pre-existing data, but I can't figure out how to save the value that the function returns once the button has been pressed. Any help is appreciated!
Thanks to the commenters, I have established a workaround, instead of
def add_charge(x,y,i,data):
temp = data.append(Charge(x,y,i))
return temp
I can write
global charge_data
charge_data = []
Then later, I can write
def add_charge(x,y,i):
charge_data.append(Charge(x,y,i))
Which allows me to effectively eliminate the need to store the result of the function.
I have a MainUI, that has a few buttons, line-edits and checkboxes. Most of the widgets I have set with a given state - e.g. all checkboxes are checked by default, and all line-edit fields have a default value of "1.0".
Currently I have a pushbutton called "reset everything". As its name implies, any changes made in these checkboxes or line-edit fields will be reverted to be checked and have a value of "1.0".
One way I can think of resetting the values, is by creating a function where I have to re-type the variable names of the affected widgets, along with their default state - which is similar to what I did when I created them. But I don't think this is a practical method.
My question here is: what is the best way for me to store the default values and then revert them?
So far I have created a class that contains the creation of the main ui:
class MainUI(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.setWindowTitle('UI MANAGER')
self.setModal(False)
self.init_main_ui()
self.resize(QtCore.QSize(600, 350))
# UI-Configurations
def init_main_ui(self):
self.check1 = QtGui.QCheckBox("chk_box1")
self.check2 = QtGui.QCheckBox("chk_box2")
self.check3 = QtGui.QCheckBox("chk_box3")
self.check1.setChecked(True)
self.check2.setChecked(True)
self.check3.setChecked(True)
self.max_label = QtGui.QLabel("MIN Val")
self.max_input = QtGui.QLineEdit("0.0")
self.min_label = QtGui.QLabel("MAX Val")
self.min_input = QtGui.QLineEdit("1.0")
...
The simplest approach would be to refactor your existing code so that the initial values are set by a separate method:
class MainUI(QtGui.QDialog):
...
def init_main_ui(self):
self.check1 = QtGui.QCheckBox("chk_box1")
self.check2 = QtGui.QCheckBox("chk_box2")
self.check3 = QtGui.QCheckBox("chk_box3")
self.max_label = QtGui.QLabel("MIN Val")
self.max_input = QtGui.QLineEdit()
self.min_label = QtGui.QLabel("MAX Val")
self.min_input = QtGui.QLineEdit()
...
self.reset_main_ui()
def reset_main_ui(self):
self.check1.setChecked(True)
self.check2.setChecked(True)
self.check3.setChecked(True)
self.max_input.setText("0.0")
self.min_input.setText("1.0")
...
I am new to PySide. In my program, I encountered a problem that when I click one button, it triggers other button later added. Thanks!
self.addContentButton = QtGui.QPushButton('Add')
self.addContentButton.clicked.connect(self.addContent)
def addContent(self):
'''
slot to add a row that include a lineedit, combobox, two buttons
'''
self.contentTabHBoxWdgt = QtGui.QWidget()
self.contentName = QtGui.QLineEdit('line edit')
self.conetentTypeBox = QtGui.QComboBox()
self.conetentTypeBox.addItem('elem1')
self.conetentTypeBox.addItem('elem2')
self.contentSave = QtGui.QPushButton('save',parent = self.contentTabHBoxWdgt)
self.contentSave.clicked.connect(self.contntSaveAct)
self.contentDelete = QtGui.QPushButton('delete',parent=self.contentTabHBoxWdgt)
self.contentDelete.clicked.connect(self.contntDel)
self.contentTabHBox = QtGui.QHBoxLayout()
self.contentTabHBox.addWidget(self.contentName)
self.contentTabHBox.addWidget(self.conetentTypeBox)
self.contentTabHBox.addWidget(self.contentSave)
self.contentTabHBox.addWidget(self.contentDelete)
self.contentTabHBoxWdgt.setLayout(self.contentTabHBox)
self.contentTabVBox.addWidget(self.contentTabHBoxWdgt)
def contntDel(self):
'''
slot to delete a row
'''
msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Warning, '', 'Be sure to delete')
okBttn = msgBox.addButton('Yes', QtGui.QMessageBox.AcceptRole)
noBttn = msgBox.addButton('Cancel', QtGui.QMessageBox.RejectRole)
ret = msgBox.exec_()
if msgBox.clickedButton() == okBttn:
self.contentTabVBox.removeWidget(self.contentDelete.parentWidget());
When I Add one row and click its delete button, it does not work as expected.While I add two or three row , I click one delete button , it remove one row that is not the clicked delete button belong to. How could I achieve this function. Ths!
Your problem is because you aren't really taking advantage of object oriented programming properly.
All rows in your example call the same instance of the method contntDel. This method uses self.contentDelete which always contains a reference to the last row added.
What you need to do is separate out everything related to a row to a new class. When you add a row, create a new instance of this class and pass in the contentTabVBox. That way each row (or instance of the new class you will write) will have it's own delete method.
Without a complete code example, I can't provide a complete solution, but this should give you a rough idea:
class MyRow(object):
def __init__(self,contentTabVBox, rows):
self.contentTabVBox = contentTabVBox
self.my_list_of_rows = rows
self.addContent()
def addContent(self):
# The code for your existing addContent method here
def contntDel(self):
# code from your existing contntDel function here
# also add (if Ok button clicked):
self.my_list_of_rows.remove(self)
class MyExistingClass(??whatever you have here normally??):
def __init__(....):
self.addContentButton = QtGui.QPushButton('Add')
self.addContentButton.clicked.connect(self.addContent)
self.my_list_of_rows = []
def addContent(self):
my_new_row = MyRow(self.contentTabVBox,self.my_list_of_rows)
# You mustsave a reference to my_new_row in a list or else it will get garbage collected.
self.my_list_of_rows.append(my_new_row)
Hope that helps!