I am creating buttons, then putting them through as arguments to a function I created called (placement), but when I run them in as arguments and I try to print the type of this input, they become Event Objects, I don't understand why this occurs, I need them to remain as Button objects, because i want to change the text of a button after i click it
for column in range(self.column + 1):
new_button = Button(new_frame, text = ' ', height = 10, width = 20)
new_button.grid(row = r, column = c)
new_button.bind('<Button-1>', func = lambda x=new_button: self.placement(x))
def placement(self, button):
print(type(button))
if self.current == 1:
button.config(text = 1)
self.current = 2
else:
button.config(text = 2)
self.current = 1
A bind function requires a event argument, so you have to change your code to:
new_button.bind('<Button-1>', func = lambda event, x=new_button: self.placement(x))
Related
I want to make a financial calculator by using GUI, which can calculate some basic calculation functions.
Currently my program has main two functions future_value and present_value.
But when I calculate, it will call my previous function and print out the result on the terminal. For example, my previous function is future_value, and now there is a new function called present_value. When I press the calculation button, It will also call future_value in the terminal and print it out,but I only want present_value to be called.
I want to set the cauculation button as universal button, so whenever there is a blank in a block, then computer know I need to calculte that specific number(function).
One of my idea is that create a function that like a title(heading) function, and that function is connected to this button, and within this function, there is a list of different functions that do something like calculte interest rate, or present_value, etc, but my main problem is how should I do it, how should I use if statement or other statement to work on it.
from PySide2.QtWidgets import QApplication
from PySide2.QtUiTools import QUiLoader
class COMPOUND_INTEREST:
def __init__(self):
self.ui = QUiLoader().load("bb\IA compound designer.ui")
self.ui.setWindowTitle("Compound interest")
self.ui.pushButton.clicked.connect(self.future_value)
self.ui.pushButton.clicked.connect(self.present_value)
def future_value(self):
N = int(self.ui.lineEdit.text())
I = float(self.ui.lineEdit_2.text())
PV = float(self.ui.lineEdit_3.text())
C_Y = int(self.ui.lineEdit_7.text())
jb = (1 + (I / (100 * C_Y))) ** N
res = abs(jb * PV)
print(res)
self.ui.lineEdit_6.setText(str(round(res, 2)))
def present_value(self):
F_V = float(self.ui.lineEdit_6.text())
N = int(self.ui.lineEdit.text())
I = float(self.ui.lineEdit_2.text())
C_Y = int(self.ui.lineEdit_7.text())
x = (1 + (I / (100 * C_Y))) ** N
res = -abs(F_V / x)
print(res)
self.ui.lineEdit_3.setText(str(round(res, 2)))
app = QApplication([])
COMPOUND_INTEREST = COMPOUND_INTEREST()
COMPOUND_INTEREST.ui.show()
app.exec_()
Based on what I understood on your post, you want a single button to change its functionality based on wether the Line Edits are filled with numbers or empty, right?
Just be careful because it gets more complex than that. For example: what if both of the Line Edits are filled with numbers? Or both of them are empty?
In the example below, I teach you how to check those 4 conditions, and ask the user to choose which method that should be executed it happens that both Edits are filled.
If both are empty, an error is displayed on the screen telling the user to fill at least one of them.
from PySide2.QtWidgets import QApplication, QMainWindow, QWidget, QMessageBox
from PySide2.QtWidgets import QLabel, QLineEdit, QPushButton, QDialog
from PySide2.QtWidgets import QDialogButtonBox, QGridLayout, QVBoxLayout
class OurCondition(QDialog):
def __init__(self):
QDialog.__init__(self)
layout = QVBoxLayout()
self.setLayout(layout)
text = "Both <b>Present</b> and <b>Future</b> fields are filled. Which function "
text += "do you want to execute?"
layout.addWidget(QLabel(text))
bbox = QDialogButtonBox()
bbox.addButton('Future Value', QDialogButtonBox.AcceptRole)
bbox.addButton('Present Value', QDialogButtonBox.AcceptRole)
bbox.addButton('Cancel', QDialogButtonBox.RejectRole)
bbox.accepted.connect(self.accept)
bbox.rejected.connect(self.reject)
bbox.clicked.connect(self.clickedBeforeAccept)
layout.addWidget(bbox)
# Set Default Option
self.conditionOption = 0
# As we are using bbox on clickedBeforeAccept below, we must reference
# bbox for later usage:
self.bbox = bbox
# Custom function to be called when a QDialogButtonBox button
# is clicked.
def clickedBeforeAccept(self, button):
if (self.bbox.buttonRole(button) == QDialogButtonBox.AcceptRole):
if (button.text() == 'Future Value'):
self.conditionOption = 1
else:
self.conditionOption = 2
def exec(self):
self.exec_()
# Return which option the user selected. Default is 0 (None).
if (self.result() == QDialog.Accepted):
return self.conditionOption
class Scene(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout()
self.setLayout(layout)
editN = QLineEdit()
editI = QLineEdit()
editPV = QLineEdit()
editFV = QLineEdit()
editCY = QLineEdit()
grid = QGridLayout()
grid.addWidget(QLabel("N"), 0, 0)
grid.addWidget(editN, 0, 1)
grid.addWidget(QLabel("I"), 1, 0)
grid.addWidget(editI, 1, 1)
grid.addWidget(QLabel("Present:"), 2, 0)
grid.addWidget(editPV, 2, 1)
grid.addWidget(QLabel("Future:"), 3, 0)
grid.addWidget(editFV, 3, 1)
grid.addWidget(QLabel("C/Y"), 4, 0)
grid.addWidget(editCY, 4, 1)
layout.addLayout(grid)
layout.addSpacing(12)
button = QPushButton("Calculate")
layout.addWidget(button)
# Connecting methods to each widget:
button.clicked.connect(self.calculate)
# As we may reference these variables within the methods
# of our class, we bind them to the scene class
self.editI = editI
self.editN = editN
self.editPV = editPV
self.editFV = editFV
self.editCY = editCY
# Note I'm not using QUiLoader. So self.ui is not available
# here.
def future_value(self):
N = int(self.editN.text())
I = float(self.editI.text())
PV = float(self.editPV.text())
C_Y = int(self.editCY.text())
jb = (1 + (I / (100 * C_Y))) ** N
res = abs(jb * PV)
print(res)
self.editFV.setText(str(round(res, 2)))
def present_value(self):
F_V = float(self.editFV.text())
N = int(self.editN.text())
I = float(self.editI.text())
C_Y = int(self.editCY.text())
x = (1 + (I / (100 * C_Y))) ** N
res = -abs(F_V / x)
print(res)
self.editPV.setText(str(round(res, 2)))
def calculate(self):
# As each element in the scene is being referenced from
# this class, we can access them using the self variable.
#
# To choose which operation, we must inform the application
# which widget is empty before calling a calculation method:
fv_empty = (self.editFV.text() == "")
pv_empty = (self.editPV.text() == "")
# If both inputs are empty, show an error dialog. That cannot
# happen based on the current rules.
if (fv_empty and pv_empty):
text = 'Both Present and Future Values Fields are blank. '
text += 'Fill at least one of them.'
dialog = QMessageBox()
dialog.setWindowTitle('Error')
dialog.setText(text)
dialog.setIcon(QMessageBox.Critical)
dialog.exec_()
# If both inputs are filled, create a rule and ask the user if
# it wants to continue, otherwise, do nothing:
elif (not fv_empty and not pv_empty):
dialog = OurCondition()
choice = dialog.exec()
# Inside OutCondition class we defined if the user
# clicked on Future Value button, choice is set to 1.
#
# If Present Value button is clicked, choice is set to 2.
#
# If it clicks on Cancel, or x button, choice is set to 0.
if (choice == 1):
self.future_value()
elif (choice == 2):
self.present_value()
# At least one field is empty:
else:
# If Future Field is empty, then call future_value to fill it
if (fv_empty):
self.future_value()
# If Present Field is empty, then call present_value to fill it
elif (pv_empty):
self.present_value()
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setWindowTitle('Compound Test')
scene = Scene()
self.setCentralWidget(scene)
if __name__ == '__main__':
app = QApplication()
win = MainWindow()
win.show()
app.exec_()
If you want, you can also use QComboBoxes to let the user choose from multiple options, if you need 3 or more methods to execute from the single button. As the number of options grows, it gets harder to create a logic using the conditions of wether each input is empty or filled.
With only 2 of them, that's ok. But with 3 or more, I strongly recomend you to rethink your User Interface and add a QComboBox.
If that's still not what you wanted, please edit your question to state what is that you're struggling at.
What you want is to learn how to parse an input. Basically you need to make an algorithm that checks if the user has input the interest rate.
Then, you make a conditional statement that says
if inputHasInterestRate() == false:
presentValue()
else:
futureValue()
I'm trying to make a memory game and I'm trying to figure out how to call the action hide_text so that the parameter will be the button I clicked.
from tkinter import *
from random import shuffle
root = Tk()
a = list(range(1, 19))
a = list(a) + list(a)
shuffle(a)
def game():
count = 0
for i in a:
if count == 0:
posisonx = 0
posisony = 0
if count == 6:
count = 0
posisonx = 0
posisony += 1
button = Button(root,text=f' {i} ',
command=lambda: hide_text())
button.grid(row=posisony, column=posisonx)
def hide_text(s):
s.config(text='')
posisonx += 1
count += 1
game()
root.mainloop()
There are two problems to solve here. One is that you can't reference a button until it has been created. The other is correctly capturing the reference once it has.
If you want to pass a command involving a reference to the button, you have to split the creation into two lines:
button = Button(root,text=f' {i} ')
button.configure(command=...)
You need to bind button to the lambda that you create. There are a couple of ways to do this. If you just do command=lambda: hide_text(button), all the commands will point to the last button you create in the loop.
One trick is to use the fact that default arguments are bound when the function is created:
button.configure(command=lamda s=button: hide_text(s))
You should take def hide_text outside the loop in this case.
Another way is to capture a closure using a nested function. Put this outside the loop:
def get_hide_text(button):
def hide_text():
button.config(text='')
return hide_text
Then, in the loop, do
button.configure(command=get_hide_text(button))
I used a list as a parameter for the GUI window, and each individual item in the list is supposed to be created as a radio button in the GUI window. Currently, I used a for loop to generate different values for each radio button and another for loop to generate different columns. However, I am only able to create a radio button for the last item in the list.
This is what I currently have:
from tkinter import *
from tkinter.scrolledtext import *
class ExpenditureGUI:
def __init__(self, listOfCategories):
self._listOfCategories = listOfCategories
self._tk = Tk()
self._tk.title('Expenditure Tracker')
self._tk.geometry('500x200')
self._tk.resizable(False,False)
self.initWidgits()
self._tk.mainloop()
#property
def listOfCategories(self):
return self._listOfCategories
#listOfCategories.setter
def listOfCategories(self, newValue):
self._listOfCategories = newValue
def initWidgits(self):
#flow layout
topF = Frame(self._tk)
self._lblAmount = Label(topF, text = 'Amount: ')
self._txtAmount = Entry(topF, width=30)
rbtnF = Frame(topF)
self._rbtnVar = IntVar()
self._rbtnVar.set(0)
self._lblExpenditure = Label(topF, text = 'Type of Expenditure: ')
n = 0
for type in self._listOfCategories:
self._rbtntype = Radiobutton(rbtnF, text = f'{type}', value = n, variable = self._rbtnVar)
self._lblExpenditure.grid(row = 0, column = 0, sticky = E)
n = 0
for type in self._listOfCategories:
self._rbtntype.grid(row = 0, column = n)
I’m not an expert in the GUI library, but this looks pretty suspect:
for type in self._listOfCategories:
self._rbtntype = Radiobutton(rbtnF, text = f'{type}', value = n, variable = self._rbtnVar)
^^^^^^^^^^^^^^^^
After this loop ends, the self._rbtntype variable will contain text referring to only the last element in the list of categories (i.e. the loop variable type).
You may want to construct a new Radiobutton in the final loop in your code sample. Maybe something like this would work. It probably needs to update n in each loop iteration.
for type in self._listOfCategories:
rbtntype = Radiobutton(rbtnF, text = f'{type}', value = n, variable = self._rbtnVar)
rbtntype.grid(row = 0, column = n)
I'm making a revision quiz program using Tkinter. The program will automatically get questions from an array and then output the questions using a Label. When the submit button is pressed, the program will check the answer and then should update the Label to the next question in the array but it doesn't, there is no error message. The score is updated but the Label just doesn't update.
def entryQuestion():
entryOpen = open('./files/entry.csv','r')
entryFile = csv.reader(entryOpen, delimiter = ',')
global entryQuestionArray
entryQuestionArray = []
for topic, question, answer in entryFile:
for i in range(0,1):
entryQuestionArray.append(question)
entryQuestionArray = random.sample(entryQuestionArray, len(entryQuestionArray))
arrayLength = len(entryQuestionArray)
for x in entryQuestionArray:
global qOut
qOut = x
global entryQuestion
entryQuestion = Label(entryQ, text = x)
entryQuestion.grid(row = 1, column = 0, columnspan = 3)
global answerEntry
answerEntry = StringVar()
global answerEntryBox
answerEntryBox = Entry(entryQ, textvariable = answerEntry)
answerEntryBox.grid(row = 2, column = 0, columnspan = 3)
submitEntryAnswer = Button(entryQ, text = 'Submit Answer', command = entryQuestionCheck)
submitEntryAnswer.grid(row = 3, column = 2)
def entryQuestionCheck():
entryOpen = open('./files/entry.csv','r')
entryFile = csv.reader(entryOpen, delimiter = ',')
tempScore = score
for topic, question, answer in entryFile:
if qOut == question:
if answerEntry.get() == answer:
tempScore = tempScore + 1
else:
tempScore = tempScore + 0
return
Can anyone help?
Each Widget only needs 1 call, on the top or in a main class, you need to call him only once:
class MainPage(#values):
self.label1 = tk.Label(self, #values)
and in the function, if you need to change your values, for example the text, you only need to pass self values to function, and change him, like this:
def changeValues(self):
self.label["text"] = "the text that u want to put in"
or:
def changeValues(label):
label["text"] = "the text that u want to put in"
and in the main class, or in the scope, you need to call the function with the values, if is in the scope (not in a class) you need to pass the widget in the values:
class MainPage(#values):
self.label1 = tk.Label(self, #values)
changeValues(self)
or:
label1 = tk.Label(self, #values)
changeValues(label1)
I created my own version of a Dialog class that is being used as a parent class for my different dialog windows that have custom fields. At the bottom is the code for the Dialog class. In one of the Dialog type windows there is a Combobox (drop down box) that allows the user to also enter their own value. If they enter their own value (their name) and it isn't contained in the list of customers (from a file) it asks the user if they want to add a new customer. If they select yes I want it to pass the name they entered up to the parent window (the root of the application), close the dialog box where they entered the name, and open a different dialog to enter the new customer's info (containing the name they entered before). Every part of this works except passing the value up. At first I attempted to pass the value up using a custom exception, which doesn't work because of how Tkinter handles exceptions.
How should I pass this value up? My initial thought would be through a custom Tkinter Event. I can't find information on how to do that though.
Root Method creating first child dialog:
def check_in(self):
log_diag = LoggerDialog(self,self.customers)
try:
log_diag.show()
except NewCustomerException, (instance):
self.new_customer(str(instance))
except:
print instance
Inside Child Dialog Box:
def new_customer_error(self):
#parse name and preset values in the new customer dialog
name = self.name.get()
if askquestion(title="New Customer?",
message="Add new customer: " + name,
parent = self.root) == 'yes':
raise NewCustomerException(name)
Dialog class:
class Dialog:
def __init__(self, master, title, class_=None, relx=0.5, rely=0.3):
self.master = master
self.title = title
self.class_ = class_
self.relx = relx
self.rely = rely
def setup(self):
if self.class_:
self.root = Toplevel(self.master, class_=self.class_)
else:
self.root = Toplevel(self.master)
self.root.title(self.title)
self.root.iconname(self.title)
def enable(self):
### enable
self.root.protocol('WM_DELETE_WINDOW', self.wm_delete_window)
self._set_transient(self.relx, self.rely)
self.root.wait_visibility()
self.root.grab_set()
self.root.mainloop()
self.root.destroy()
def _set_transient(self, relx=0.5, rely=0.3):
widget = self.root
widget.withdraw() # Remain invisible while we figure out the geometry
widget.transient(self.master)
widget.update_idletasks() # Actualize geometry information
if self.master.winfo_ismapped():
m_width = self.master.winfo_width()
m_height = self.master.winfo_height()
m_x = self.master.winfo_rootx()
m_y = self.master.winfo_rooty()
else:
m_width = self.master.winfo_screenwidth()
m_height = self.master.winfo_screenheight()
m_x = m_y = 0
w_width = widget.winfo_reqwidth()
w_height = widget.winfo_reqheight()
x = m_x + (m_width - w_width) * relx
y = m_y + (m_height - w_height) * rely
if x+w_width > self.master.winfo_screenwidth():
x = self.master.winfo_screenwidth() - w_width
elif x < 0:
x = 0
if y+w_height > self.master.winfo_screenheight():
y = self.master.winfo_screenheight() - w_height
elif y < 0:
y = 0
widget.geometry("+%d+%d" % (x, y))
widget.deiconify() # Become visible at the desired location
def wm_delete_window(self):
self.root.quit()
I tried using generate_event() and passing a custom parameter (name) and I get this error:
TclError: bad option "-name": must be -when, -above, -borderwidth,
-button, -count, -data, -delta, -detail, -focus, -height, -keycode,
-keysym, -mode, -override, -place, -root, -rootx, -rooty, -sendevent,
-serial, -state, -subwindow, -time, -warp, -width, -window, -x, or -y
I've tried overriding the value of a few of these and I keep getting errors back telling me that I've passed the wrong type. (mode is an int, detail has to be an instance of a specific list of objects) This does not seem to be a very elegant solution.
Here is the solution I settled on:
Code in the child object (for handling a name not in database):
def new_customer_error(self):
if askquestion(title="New Customer?",
message="Add new customer: " + self.name.get(),
parent = self.root) == 'yes':
self.master.event_generate('<<NewCustomer>>')
In __init__ of root window:
self.bind('<<NewCustomer>>',self.new_customer)
Later on in root object:
def new_customer(self, event=None):
#if evert this came as a new customer entry through check in
if event:
temp = self.logger_diag.name.get().split(' ')
self.newc_diag.fname.set(temp[0])
if len(temp) == 2:
self.newc_diag.lname.set(temp[1])
elif len(temp) == 3:
self.newc_diag.mname.set(temp[1])
self.newc_diag.lname.set(temp[2])
elif len(temp) > 3:
self.newc_diag.mname.set(temp[1])
self.newc_diag.lname.set(' '.join(temp[2:4]))