I'm trying to make a simple calculator that has buttons of numbers 0-9, plus, minus, clear and equals. I have the gui and the functionality of the buttons, but my calculator calculates further than 999. Any ideas?
-I have attempted to stop it calculating further than 999, if you look at line 45-53.
here is my code:
from tkinter import *
class Calculator(Frame):
def frame(this, side):
w = Frame(this)
w.pack(side=side, expand=YES, fill=BOTH)
return w
def button(this, root, side, text, command=None):
w = Button(root, text=text, command=command)
w.pack(side=side, expand=YES, fill=BOTH)
return w
need_clr = False
def digit(self, digit):
if self.need_clr:
self.display.set('')
self.need_clr = False
self.display.set(self.display.get() + digit)
def sign(self):
need_clr = False
cont = self.display.get()
if len(cont) > 0 and cont[0] == '-':
self.display.set(cont[1:])
else:
self.display.set('-' + cont)
def oper(self, op):
self.display.set(self.display.get() + ' ' + op + ' ')
self.need_clr = False
def calc(self):
try:
self.display.set(eval(self.display.get()))
self.need_clr = True
except:
showerror('Operation Error', 'Illegal Operation')
self.display.set('')
self.need_clr = False
def equals(self):
try:
result = eval(self.display.get())
if result >= 1000:
result (calc)
except:
results("ERROR")
display.delete(0, END)
display.insert(0, display)
def __init__(self):
Frame.__init__(self)
self.option_add('*Font', 'Dotum 15')
self.pack(expand=YES, fill=BOTH)
self.master.title('Simple Calculator')
self.display = StringVar()
e = Entry(self, relief=SUNKEN, textvariable=self.display)
e.pack(side=TOP, expand=YES, fill=BOTH)
for key in ("123", "456", "789"):
keyF = self.frame(TOP)
for char in key:
self.button(keyF, LEFT, char,
lambda c=char: self.digit(c))
keyF = self.frame(TOP)
self.button(keyF, LEFT, '0', lambda ch='0': self.digit(ch))
opsF = self.frame(TOP)
for char in "+-=":
if char == '=':
btn = self.button(opsF, LEFT, char, self.calc)
else:
btn = self.button(opsF, LEFT, char,
lambda w=self, s=char: w.oper(s))
clearF = self.frame(BOTTOM)
self.button(clearF, LEFT, 'Clr', lambda w=self.display: w.set(''))
if __name__ == '__main__':
Calculator().mainloop()
Your big problem is that you've tried to fix this in a method called equals that you never call anywhere in your code. So, obviously this won't do anything.
If you look at where your = button is defined, it does this:
opsF = self.frame(TOP)
for char in "+-=":
if char == '=':
btn = self.button(opsF, LEFT, char, self.calc)
So, it calls the calc method. You have to change calc to influence what it does; adding some completely separate method that never gets called anywhere won't help.
If you change this to call self.equals instead of self.calc, that solves this first problem… but of course you'll have a whole new set of problems, because most of the code in equals makes no sense, as Joran Beasley explains. Look at the working code in calc as a model for how to do things in equals.
However, a better design than copying/pasting/editing calc would be to modify it to call some new validate_result method, like this:
def calc(self):
try:
self.display.set(self.validate_result(eval(self.display.get())))
self.need_clr = True
except:
showerror('Operation Error', 'Illegal Operation')
self.display.set('')
self.need_clr = False
Now, you just need to write validate_result as a function that works on numbers and either returns the number (or returns a modified number, if you want) or raises an exception. It doesn't need to duplicate all the work that calc does, it can just let calc do all that stuff. For example:
def validate_result(self, result):
if result >= 1000:
raise ValueError('result too big!')
else:
return result
I assume you are talking about
def equals(self):
try:
result = eval(self.display.get()) # <-- this is some risky business
if result >= 1000: #I think you want to check less than but its not clear, this is greater than or equal
result (calc) #result is a number you cannot do 5(some_argument)
#^^^^^^^^^^^ this line should probably end up as an error message ...
#you probably need an else here to handle if the number is too big
else:
raise ValueError("Value Too Large!!!")
except:
results("ERROR") #not sure what this line is doing ...
display.delete(0, END)
display.insert(0, display)
there are several problems I commented ... perhaps one will solve your issue
but I think really abarnert nailed your problem so I would go with that ...
Related
I have self in all functions, but I still get the error in the title. I don't know what to try, any help?
Here is the code:
class layy(App):
global t
t = TextInput(hint_text='insert text')
global c
global s
s = TextInput(hint_text='insert time till begin')
c = TextInput(hint_text='insert amount')
def bott(self):
am = c.text
if (am == ""):
am = '0'
l = int(am)
pm = c.text
if (pm == ""):
pm = '0'
o = int(pm)
base = 0
time.sleep(o)
while (base < l):
pyautogui.typewrite(t)
pyautogui.press('enter')
base = base + 1
def build(self):
b = Button(text = 'Start Spam')
b.bind(on_press = self.bott)
layout = BoxLayout(orientation='vertical')
sublay1 = BoxLayout(orientation='horizontal')
sublay2 = BoxLayout(orientation='horizontal')
sublay3 = BoxLayout(orientation='horizontal')
layout.add_widget(sublay1)
sublay1.add_widget(t)
layout.add_widget(sublay2)
sublay2.add_widget(s)
sublay2.add_widget(c)
layout.add_widget(sublay3)
sublay3.add_widget(b)
return layout
if __name__ == '__main__':
layy().run()
If any more information is needed, this is supposed to repeat sending the desired text as many times as I need. If anyone knows what to do, please tell me.
Kivy event handlers can take any number of parameters. I haven't found a good source for what parameters are given for each callback type, but the Button has an example that uses on_press and it receives the button instance being pressed. That's the second mystery parameter the error references. Just add it to your method
def bott(self, instance):
do the things...
I recently started doing python. The course I was on ended with an introduction to testing with doctest. I have written a program that uses Tkinter to display widgets and it works :-) . I am using version 3.7. However, testing it is another matter. I can test simple functions and methods, but I hit difficulties when I have a function inside a method. I am pasting below a stripped-down version of what I am trying to achieve. I tried first with doctest and it threw up an error:
"AttributeError: 'function' object has no attribute 'c_square'".
# counter.py
from tkinter import *
import doctest
count = 0
delay = 1000
class MyClass:
def __init__(self, master):
master.geometry("1000x500")
master.resizable(0, 0)
master.title("Display a counter")
frame1 = Frame(master)
frame1.pack()
label1 = Label(frame1, font = ('Courier', 15 , 'bold'))
label1.grid(row = 0, column = 0)
self.my_counter(label1)
label2 = Label(frame1, font = ('Courier', 15 , 'bold'))
label2.grid(row = 0, column = 1)
self.square_of_count(label2)
# This method recursively increments a counter and displays the count.
def my_counter(self, lbl):
def increment_count():
global count
global delay
count += 1
string = str(count)
lbl.config(text = string)
lbl.after(delay, increment_count)
increment_count()
# This method takes the square of the counter and displays the result.
def square_of_count(self, lbl):
def c_square():
global count
squ = count * count
string = str(squ)
lbl.config(text=string)
lbl.after(delay, c_square)
return squ
c_square()
def test_c_square(number):
"""
>>> test_c_square(2)
4
"""
global count
count = number
master = Tk()
frame1 = Frame(master)
label = Label(frame1, font = ('Courier', 15 , 'bold'))
return MyClass.square_of_count.c_square(MyClass.square_of_count.c_square)
def main():
""" # main body commented out for test purposes.
root = Tk()
a = MyClass(root)
root.mainloop()
"""
doctest.testmod(verbose=True)
if __name__ == "__main__":
main()
I am using a separate test function, so that I can initialise my counter.
Then someone suggested that I try unittest, so I wrote this :
import unittest
import counter
class TestCounter(unittest.TestCase):
counter.count = 2
print("count = ", counter.count)
def square_of_count(self):
result = counter.c_square()
self.assertEqual(result, 4)
result = counter.c_square()
self.assertNotEqual(result, 3)
if __name__ == '__main__':
unittest.main()
This runs without throwing up any errors, the purpose of it is to set a value to the variable 'count' and read back the result. But I get the same response whatever value I test for, so I do not believe it is working right. I also tried variations on a theme, but I just got error messages.
Can someone please point out what I am doing wrong, I have looked about various forums and tutorials but have not seen this question asked before.
I would appreciate an answer that is easy to follow, I am asperger / dyslexic and find it difficult to learn new material. A correction with explanation would be most helpful. Thank you.
First of all, avoid this kind of nesting the functions. In your particular case I would highly suggest refactoring of a code in manner of creating some help private methods which you will call from the main ones, or even create whole new utility class:
class Util:
def _init_(self):
self.name = "Utility"
def add_two_numbers(self, first, second):
if(isinstance(first, int) and isinstance(second, int)):
return first+second
class SomeFancyClass:
def __init__(self):
self.util = Util()
self.constant = 4
# This method recursively increments a counter and displays the count.
def my_fancy_math(self, first, second):
return self.constant * self.util.add_two_numbers(first, second)
FancyVar = SomeFancyClass()
print(FancyVar.my_fancy_math(5, 6))
In case you dont want to change your code (for some reason), there is extremely dirty way to access your inner function. Again, a bit stupidly modified example made from your code:
#!/usr/bin/python
# -*- coding: utf-8 -*-
# counter.py
from tkinter import *
import doctest
import types
count = 0
delay = 1000
class MyClass:
def __init__(self, smth1):
self.something = smth1
# This method recursively increments a counter and displays the count.
def my_counter(self, lbl):
def increment_count():
global count
global delay
count += 1
string = str(count)
lbl.config(text=string)
lbl.after(delay, increment_count)
increment_count()
# This method takes the square of the counter and displays the result.
def square_of_count(self, lbl):
def test_function1(self, first, second):
return first+second
def c_square():
global count
squ = count * count
string = str(squ)
lbl.config(text=string)
lbl.after(delay, c_square)
return squ
c_square()
def test_function(self, st1):
print(st1)
def test_c_square(number):
global count
count = number
master = Tk()
frame1 = Frame(master)
label = Label(frame1, font=('Courier', 15, 'bold'))
return MyClass.square_of_count.c_square(MyClass.square_of_count.c_square)
def main():
doctest.testmod(verbose=True)
if __name__ == '__main__':
# main()
print('done')
test_function = types.FunctionType(MyClass.square_of_count.__code__.co_consts[1],
{}, None, (), ())
obj = MyClass("Hi")
sum1 = test_function("", 1, 2)
print(sum1)
Two separate issues have come up with my code
First, I can't get the fourth row of my grid to appear, although the fifth appears to be displaying just fine.
Secondly, my passVal function keeps giving me the error:
passVal() takes 1 positional argument but 2 were given
I've tried rearranging things and converting it to a string and nothing seems to work.
I figure there's a chance it's one thing causing the same issue since they're both centered around the same button but I'm not sure.
import tkinter
class AccountCreation:
def __init__(self):
self.main = tkinter.Tk()
self.main.title("Account Creation")
self.topleftLabel = tkinter.Label(text=" ")
self.topleftLabel.grid(row=1,column=1)
self.botrightLabel = tkinter.Label(text=" ")
self.botrightLabel.grid(row=5,column=5)
self.promptLabel = tkinter.Label(text="Create a password with at least nine (9)\n characters that contains at least one digit, \n one uppercase, and one lowercase letter.\n\n")
self.promptLabel.grid(row=2,column=2,columnspan=2)
self.passLabel = tkinter.Label(text="Password:")
self.passLabel.grid(row=3,column=2)
self.passEntry = tkinter.Entry(width = 18, justify='right')
self.passEntry.grid(row=3,column=3)
self.enterButton = tkinter.Button(text="Enter", \
command=self.passVal(self.passEntry.get()))
self.enterButton.grid(row=4,column=2)
self.cancelButton = tkinter.Button(text="Cancel", \
command=self.cancel)
self.cancelButton.grid(row=4,column=3)
tkinter.mainloop()
def passVal(pw):
if len(pw) < 9:
print ("no")
def cancel(self):
self.main.destroy()
my_gui = AccountCreation()
Aside from the indention issues you are having, all methods in a class you need to pass self as the first argument unless you are using special tags that can make it a stand alone function.
Change:
def passVal(pw):
To:
def passVal(self, pw):
You will also need to change the command on you Enter button to use lambda in order to prevent python from calling the passVal method on start up.
Change:
command=self.passVal(self.passEntry.get())
To:
command=lambda: self.passVal(self.passEntry.get())
You don't really need to use a lambda here or even pass the argument of self.passEntry.get(). You can get the value of the entry field in the passVal() method by use self.passEntry.get() instead of pw.
If you change this:
command=lambda: self.passVal(self.passEntry.get())
To this:
command=self.passVal
And this:
def passVal(self, pw):
if len(pw) < 9:
print ("no")
To this:
def passVal(self):
if len(self.passEntry.get()) < 9:
print ("no")
You program will work fine and you can avoid using a lambda in your command.
Note: You do not need to use labels as spacers. You can simple use padx and pady in your grid placement.
Take a look at the below code:
import tkinter
class AccountCreation:
def __init__(self):
self.main = tkinter.Tk()
self.main.title("Account Creation")
self.promptLabel = tkinter.Label(text="Create a password with at least nine (9)\n characters that contains at least one digit, \n one uppercase, and one lowercase letter.\n\n")
self.promptLabel.grid(row=2,column=2,columnspan=2,pady=(10,10))
self.passLabel = tkinter.Label(text="Password:")
self.passLabel.grid(row=3,column=2)
self.passEntry = tkinter.Entry(width = 18, justify='right')
self.passEntry.grid(row=3,column=3)
self.enterButton = tkinter.Button(text="Enter", \
command=self.passVal(self.passEntry.get()))
self.enterButton.grid(row=4,column=2)
self.cancelButton = tkinter.Button(text="Cancel", \
command=self.cancel)
self.cancelButton.grid(row=4,column=3,pady=(10,10))
tkinter.mainloop()
def passVal(self, pw):
if len(pw) < 9:
print ("no")
def cancel(self):
self.main.destroy()
my_gui = AccountCreation()
Notice that simple using pady=(10,10) we have put space at the top and bottom of the widget.
I'm trying to make use of this excellent answer by Bryan Oakley, but to no avail (https://stackoverflow.com/a/4140988/5060127)...
I would like to use the same method to verify Spinbox values. I have defined from_ and to values for spinboxes, but user can still type most anything in them... it should be validated that only values within the from_-to range are possible to be inputted by the user, and only integers at that.
Here's the code that shows how far I've got...
try:
from Tkinter import *
except ImportError:
from tkinter import *
class GUI:
def __init__(self):
# root window of the whole program
self.root = Tk()
self.root.title('ImageSound')
# registering validation command
vldt_ifnum_cmd = (self.root.register(self.ValidateIfNum),'%s', '%S')
# creating a spinbox
harm_count = Spinbox(self.root, from_=1, to=128, width=5, justify='right', validate='all', validatecommand=vldt_ifnum_cmd)
harm_count.delete(0,'end')
harm_count.insert(0,8)
harm_count.pack(padx=10, pady=10)
def ValidateIfNum(self, s, S):
# disallow anything but numbers
valid = S.isdigit()
if not valid:
self.root.bell()
return valid
if __name__ == '__main__':
mainwindow = GUI()
mainloop()
I think I found the problem. Validator function is called initially with S='' and your condition S.isdigit() returns False and function is not called anymore. But after I updated condition to valid = S == '' or S.isdigit() it started to work as expected.
Of course you'll probably want some more sophisticated condition (e.g. checking if value is within range), but it looks like empty string has to pass (at least initial) validation.
I have done it! Both integer-only input and range-checking that takes widget's from_ and to values into account is working! It perhaps looks a bit hacky, but it's working! Here's the code for anyone interested:
try:
from Tkinter import *
except ImportError:
from tkinter import *
class GUI:
def __init__(self):
# root window of the whole program
self.root = Tk()
self.root.title('ImageSound')
# registering validation command
vldt_ifnum_cmd = (self.root.register(self.ValidateIfNum),'%P', '%S', '%W')
# creating a spinbox
harm_count = Spinbox(self.root, from_=1, to=128, width=5, justify='right', validate='all', validatecommand=vldt_ifnum_cmd)
harm_count.insert(0,8)
harm_count.delete(1,'end')
harm_count.pack(padx=10, pady=10)
def ValidateIfNum(self, user_input, new_value, widget_name):
# disallow anything but numbers in the input
valid = new_value == '' or new_value.isdigit()
# now that we've ensured the input is only integers, range checking!
if valid:
# get minimum and maximum values of the widget to be validated
minval = int(self.root.nametowidget(widget_name).config('from')[4])
maxval = int(self.root.nametowidget(widget_name).config('to')[4])
# check if it's in range
if int(user_input) not in range (minval, maxval):
valid = False
if not valid:
self.root.bell()
return valid
if __name__ == '__main__':
mainwindow = GUI()
mainloop()
One thing that I noticed isn't quite working is if you select the whole text in the spinbox, and paste something wrong, like text. That breaks validation completely. Ugh.
I've came up with a solution that works for any Entry widget and thus SpinBox's as well. It uses validatecommand to ensure only the correct values are entered. A blank entry is temporarily validate but on FocusOut it changes back to the last valid value.
intvalidate.py
import tkinter as tk
def int_validate(entry_widget, limits=(None, None)):
"""
Validates an entry_widget so that only integers within a specified range may be entered
:param entry_widget: The tkinter.Entry widget to validate
:param limits: The limits of the integer. It is given as a (min, max) tuple
:return: None
"""
num_str = entry_widget.get()
current = None if (not _is_int(num_str)) else int(num_str)
check = _NumberCheck(entry_widget, limits[0], limits[1], current=current)
entry_widget.config(validate='all')
entry_widget.config(validatecommand=check.vcmd)
entry_widget.bind('<FocusOut>', lambda event: _validate(entry_widget, check))
_validate(entry_widget, check)
def _is_int(num_str):
"""
Returns whether or not a given string is an integer
:param num_str: The string to test
:return: Whether or not the string is an integer
"""
try:
int(num_str)
return True
except ValueError:
return False
def _validate(entry, num_check):
"""
Validates an entry so if there is invalid text in it it will be replaced by the last valid text
:param entry: The entry widget
:param num_check: The _NumberCheck instance that keeps track of the last valid number
:return: None
"""
if not _is_int(entry.get()):
entry.delete(0, tk.END)
entry.insert(0, str(num_check.last_valid))
class _NumberCheck:
"""
Class used for validating entry widgets, self.vcmd is provided as the validatecommand
"""
def __init__(self, parent, min_, max_, current):
self.parent = parent
self.low = min_
self.high = max_
self.vcmd = parent.register(self.in_integer_range), '%d', '%P'
if _NumberCheck.in_range(0, min_, max_):
self.last_valid = 0
else:
self.last_valid = min_
if current is not None:
self.last_valid = current
def in_integer_range(self, type_, after_text):
"""
Validates an entry to make sure the correct text is being inputted
:param type_: 0 for deletion, 1 for insertion, -1 for focus in
:param after_text: The text that the entry will display if validated
:return:
"""
if type_ == '-1':
if _is_int(after_text):
self.last_valid = int(after_text)
# Delete Action, always okay, if valid number save it
elif type_ == '0':
try:
num = int(after_text)
self.last_valid = num
except ValueError:
pass
return True
# Insert Action, okay based on ranges, if valid save num
elif type_ == '1':
try:
num = int(after_text)
except ValueError:
if self.can_be_negative() and after_text == '-':
return True
return False
if self.is_valid_range(num):
self.last_valid = num
return True
return False
return False
def can_be_negative(self):
"""
Tests whether this given entry widget can have a negative number
:return: Whether or not the entry can have a negative number
"""
return (self.low is None) or (self.low < 0)
def is_valid_range(self, num):
"""
Tests whether the given number is valid for this entry widgets range
:param num: The number to range test
:return: Whether or not the number is in range
"""
return _NumberCheck.in_range(num, self.low, self.high)
#staticmethod
def in_range(num, low, high):
"""
Tests whether or not a number is within a specified range inclusive
:param num: The number to test if its in the range
:param low: The minimum of the range
:param high: The maximum of the range
:return: Whether or not the number is in the range
"""
if (low is not None) and (num < low):
return False
if (high is not None) and (num > high):
return False
return True
It's used as such
import tkinter as tk
from tkinter import ttk
from intvalidate import int_validate
if __name__ == '__main__':
root = tk.Tk()
var = tk.DoubleVar()
widget = ttk.Spinbox(root, textvariable=var, justify=tk.CENTER, from_=0, to_=10)
widget.pack(padx=10, pady=10)
int_validate(widget, limits=(0, 10))
root.mainloop()
Working out a few kinks in this code and for some reason my method validation check is not quite working. All I want it to do is to validate that the input from the user ONLY contains letters G, C, A, T before moving onto the method at_calculate which performs the maths on the input sequence. Any help/tips would be appreciated.
import re
from tkinter import *
class AT_content_calculator:
def __init__(self, master):
#initialising various widgets
frame_1 = Frame(master)
frame_1.pack()
self.varoutput_1 = StringVar()
self.label_1 = Label(frame_1, text="Please enter a DNA sequence:")
self.label_1.pack()
self.entry_1 = Entry(frame_1, textvariable=self.dna_sequence)
self.entry_1.pack()
self.output_1 = Label(frame_1, textvariable=self.varoutput_1)
self.output_1.pack()
self.button_1 = Button(frame_1, text="Calculate", command=self.validation_check)
self.button_1.pack()
def dna_sequence(self):
self.dna_sequence = ()
def validation_check(self):
#used to validate that self.dna_sequence only contains letters G, C, A, T
if re.match(r"GCAT", self.dna_sequence):
self.at_calculate()
else:
self.varoutput_1.append = "Invalid DNA sequence. Please enter again."
self.validation_check()
def at_calculate(self):
#used to calculate AT content of string stored in self.dna_sequence
self.dna_sequence = self.entry_1.get()
self.total_bases = len(self.dna_sequence)
self.a_bases = self.dna_sequence.count("A")
self.b_bases = self.dna_sequence.count("T")
self.at_content = "%.2f" % ((self.a_bases + self.b_bases) / self.total_bases)
self.varoutput_1.set("AT content percentage: " + self.at_content)
root = Tk()
root.title("AT content calculator")
root.geometry("320x320")
b = AT_content_calculator(root)
root.mainloop()
If you want to validate the input from the user ONLY contains letters G, C, A, T you need to put the characters within a character class that will match any combinations of this characters :
Note :self.dna_sequence is a function and you can't pass it to match function although its incorrect.you need to return the input value within that function :
def dna_sequence(self):
dna_sequence = self.entry_1.get()
return dna_sequence
and then do :
if re.match(r"[GCAT]+", self.dna_sequence()):
[GCAT]+ will match any combinations of that characters with length 1 or more. if you want that be in length 4 you can use [GCAT]+{4}.
But this also will match duplicated characters. like GGCC.If you don't want such thing you can use set.intersection :
if len(self.dna_sequence())==4 and len(set(GCAT).intersection(self.dna_sequence()))==4:
#do stuff
Or as a better way :
if sorted(self.dna_sequence)==ACGT:
#do stuff