Tkinker - When placing buttons inside a frame = AttributeError: object has no attribute 'tk'
Tkinker - When placing buttons inside a frame = AttributeError: object has no attribute 'tk'
我正在尝试为我的应用构建更复杂的 GUI。我正在尝试使用框架内的 .grid() 放置按钮。但是,每当我尝试创建一个以框架为根的按钮时,我都会得到 "AttributeError: object has no attribute 'tk'"
。我见过有人编写 GUI classes(即 class Frame(tk.Frame)),这给我的代码带来了更多问题。我应该如何创建按钮并将它们放置在框架内,而不必从头开始重写我的大部分 classes?
当我将一个按钮固定到“主”时,它工作正常。但是,如果它植根于“action_frame”,那就是我收到错误的时候。
calculator.py
# -*- coding: utf-8 -*-
import tkinter as tk
from math import *
from classes_GUI import ButtonBlock, LabelBlock, TextBlock, FrameBlock
from classes_calculator import ActionBlock
# Store input number
def storeInput(entry_text, result_text, action, array):
numb = 0.0
try:
numb = float(entry_text.retrieveTextInput())
except:
print('Please enter a valid number')
return
calc_action = ActionBlock(numb, action)
array.append(calc_action)
entry_text.clearText()
num = calc_action.returnNumber()
act = calc_action.returnAction()
input_texts = dict([
(1, ' + ' + str(num)),
(2, ' - ' + str(num)),
(3, ' * ' + str(num)),
(4, ' / ' + str(num)),
(5, ' + 1/' + str(num)),
(6, ' + ' + str(num) + '^2')
])
result_text.changeText(input_texts[act])
# Calculate result
def calcResult(entry_text, result_text, array):
result = 0.0
for calc in array:
action = calc.returnAction()
num = calc.returnNumber()
if action == 1:
result += num
elif action == 2:
result -= num
elif action == 3:
result *= num
elif action == 4:
result /= num
elif action == 5:
result += 1.0 / num
elif action == 6:
result += num ** 2
entry_text.clearText()
result_text.changeText(str(result), True)
# Create a new calculator instance
def exeCalc():
action_blocks = []
button_blocks = []
frame_blocks = []
label_blocks = []
text_blocks = []
# Create GUI
master = tk.Tk()
master.title('Calculator')
# Create frames
action_frame = FrameBlock(master, 30, 30, 1, 6)
frame_blocks.append(action_frame)
for f in frame_blocks:
f.createFrame()
# Create GUI labels
title_label = LabelBlock(master, 20, 2, 'n', 0, 0, 'center', 'Calculator')
label_blocks.append(title_label)
entry_label = LabelBlock(master, 20, 2, 'n', 1, 0, 'center', 'Enter:')
label_blocks.append(entry_label)
result_label = LabelBlock(master, 20, 2, 'n', 2, 0, 'center', 'Result:')
label_blocks.append(result_label)
for l in label_blocks:
l.createLabel()
# Create GUI text
entry_text = TextBlock(master, 20, 2, 1, 1, 'normal', '')
text_blocks.append(entry_text)
result_text = TextBlock(master, 20, 2, 2, 1, 'disabled', '0')
text_blocks.append(result_text)
for t in text_blocks:
t.createText()
# Create GUI buttons
close_button = ButtonBlock(master, 6, 2, 3, 0, 'Close',
lambda: master.destroy())
button_blocks.append(close_button)
add_button = ButtonBlock(frame_blocks[0], 4, 2, 0, 0, '+',
lambda: storeInput(text_blocks[0], text_blocks[1], 1, action_blocks))
button_blocks.append(add_button)
subtract_button = ButtonBlock(frame_blocks[0], 4, 2, 0, 1, '-',
lambda: storeInput(text_blocks[0], text_blocks[1], 2, action_blocks))
button_blocks.append(subtract_button)
multiply_button = ButtonBlock(frame_blocks[0], 4, 2, 0, 2, 'x',
lambda: storeInput(text_blocks[0], text_blocks[1], 3, action_blocks))
button_blocks.append(multiply_button)
divide_button = ButtonBlock(frame_blocks[0], 4, 2, 1, 0, '/',
lambda: storeInput(text_blocks[0], text_blocks[1], 4, action_blocks))
button_blocks.append(divide_button)
fraction_button = ButtonBlock(frame_blocks[0], 4, 2, 1, 1, '1/x',
lambda: storeInput(text_blocks[0], text_blocks[1], 5,
action_blocks))
button_blocks.append(fraction_button)
square_block = ButtonBlock(frame_blocks[0], 4, 2, 1, 2, 'x^2',
lambda: storeInput(text_blocks[0], text_blocks[1], 6,
action_blocks))
button_blocks.append(square_block)
equal_button = ButtonBlock(frame_blocks[0], 4, 2, 2, 0, '=',
lambda: calcResult(text_blocks[0], text_blocks[1], action_blocks))
button_blocks.append(equal_button)
for b in button_blocks:
b.createButton()
master.mainloop()
classes_GUI.py
# -*- coding: utf-8 -*-
import tkinter as tk
# Create a base data block
class BaseBlock():
def __init__(self, root, width, height, txt):
self.root = root
self.width = width
self.height = height
self.txt = txt
# Create a inner data block
class InnerBlock(BaseBlock):
def __init__(self, root, width, height, row, column, txt):
super().__init__(root, width, height, txt)
self.g_row = row
self.g_column = column
# Create a data block for a button
class ButtonBlock(InnerBlock):
def __init__(self, root, width, height, row, column, txt, command=None):
super().__init__(root, width, height, row, column, txt)
self.command = command
def createButton(self):
button = tk.Button(self.root, text=self.txt, width=self.width,
height=self.height, command=self.command)
button.grid(row=self.g_row, column=self.g_column)
return button
# Create a frame data block
class FrameBlock(InnerBlock):
def __init__(self, root, width, height, row, column, txt=None):
super().__init__(root, width, height, row, column, txt)
def createFrame(self):
frame = tk.Frame(self.root, width=self.width, height=self.height)
frame.grid(row=self.g_row, column=self.g_column)
return frame
# Create a data block for a window
class LabelBlock(InnerBlock):
def __init__(self, root, width, height, anchor, row, column, justify, txt):
super().__init__(root, width, height, row, column, txt)
self.anchor = anchor
self.justify = justify
def createLabel(self):
label = tk.Label(self.root, width=self.width, height=self.height,
anchor=self.anchor, justify=self.justify, text=self.txt)
label.grid(row=self.g_row, column=self.g_column)
return label
# Create a data block for text
class TextBlock(InnerBlock):
def __init__(self, root, width, height, row, column, state, txt):
super().__init__(root, width, height, row, column, txt)
self.state = state
self.text = None
def createText(self):
self.text = tk.Text(self.root, width=self.width, height=self.height)
self.text.insert(tk.END, self.txt)
self.text.grid(row=self.g_row, column=self.g_column)
self.text.config(state=self.state)
return self.text
# Clear text
def clearText(self):
self.text.delete('1.0', 'end')
# Change text
def changeText(self, new_txt, clear=False):
self.text.config(state='normal')
if clear:
self.clearText()
self.text.insert(tk.END, new_txt)
self.text.config(state='disabled')
# Retrieve input from text box
def retrieveTextInput(self):
text_input = self.text.get('1.0', 'end')
return text_input
如果你的基础 class 实际上扩展了一个 Frame
,它会有所帮助。 ;)
class BaseBlock(tk.Frame):
def __init__(self, master, width, height, txt):
tk.Frame.__init__(self, master)
但实际上,您创建了太多层并且管理一切都很奇怪。下面的会更好。所有这些每一步的继承和创建功能都太混乱了。
import tkinter as tk
from math import *
from dataclasses import asdict, dataclass
from typing import Callable
@dataclass
class Label_dc:
width: int = 20
height: int = 2
anchor: str = 'n'
justify: str = 'center'
text: str = ''
@dataclass
class Button_dc:
width: int = 4
height: int = 2
text: str = ''
command: Callable = None
@dataclass
class Text_dc:
width: int = 20
height: int = 2
state: str = 'normal'
#from classes_calculator import ActionBlock
class FrameBlock(tk.Frame):
def __init__(self, master, row, column, rowspan, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.grid(row=row, column=column, rowspan=rowspan)
class ButtonBlock(tk.Button):
def __init__(self, master, row, column, **kwargs):
tk.Button.__init__(self, master, **asdict(Button_dc(**kwargs)))
self.grid(row=row, column=column)
class LabelBlock(tk.Label):
def __init__(self, master, row, column, **kwargs):
tk.Label.__init__(self, master, **asdict(Label_dc(**kwargs)))
self.grid(row=row, column=column)
class TextBlock(tk.Text):
def __init__(self, master, row, column, text='', **kwargs):
tk.Text.__init__(self, master, **asdict(Text_dc(**kwargs)))
self.grid(row=row, column=column)
self.insert('1.end', text)
# Clear text
def clearText(self):
self.delete('1.0', 'end')
# Change text
def changeText(self, new_txt, clear=False):
self.config(state='normal')
if clear:
self.clearText()
self.insert(tk.END, new_txt)
self.config(state='disabled')
# Retrieve input from text box
def retrieveTextInput(self):
return self.get('1.0', 'end')
class App(tk.Tk):
WIDTH = 800
HEIGHT = 600
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# Create GUI labels
LabelBlock(self, 0, 0, text='Calculator')
LabelBlock(self, 1, 0, text='Enter:')
LabelBlock(self, 2, 0, text='Result:')
# Create GUI text
text_blocks = {
'entry' : TextBlock(self, 1, 1),
'result': TextBlock(self, 2, 1, state='disabled', text='0'),
}
#can't use ButtonBlock for this one ~ self.destroy wont pickle properly
tk.Button(self, text='Close', width=6, height=2, command=self.destroy).grid(row=3, column=0)
action = []
# Create frames
frame = FrameBlock(self, 1, 3, 2, width=30, height=30)
# Create GUI buttons
ButtonBlock(frame, 0, 0, text='+', command=lambda: self.store(*text_blocks, 1, action))
ButtonBlock(frame, 0, 1, text='-', command=lambda: self.store(*text_blocks, 2, action))
ButtonBlock(frame, 0, 2, text='*', command=lambda: self.store(*text_blocks, 2, action))
ButtonBlock(frame, 1, 0, text='/', command=lambda: self.store(*text_blocks, 4, action))
ButtonBlock(frame, 1, 1, text='1/x', command=lambda: self.store(*text_blocks, 5, action))
ButtonBlock(frame, 1, 2, text='x^2', command=lambda: self.store(*text_blocks, 6, action))
ButtonBlock(frame, 2, 0, text='=', command=lambda: self.calc(*text_blocks, action))
def store(self, entry, result, action, array):
pass #remove this line
numb = 0.0
try:
numb = float(entry.retrieveTextInput())
except:
print('Please enter a valid number')
return
calc_action = ActionBlock(numb, action)
array.append(calc_action)
entry.clearText()
num = calc_action.returnNumber()
act = calc_action.returnAction()
input_texts = dict([
(1, ' + ' + str(num)),
(2, ' - ' + str(num)),
(3, ' * ' + str(num)),
(4, ' / ' + str(num)),
(5, ' + 1/' + str(num)),
(6, ' + ' + str(num) + '^2')
])
result.changeText(input_texts[act])
# Calculate result
def calc(self, entry, result, array):
pass #remove this line
r = 0.0
for calc in array:
action = calc.returnAction()
num = calc.returnNumber()
if action == 1:
result += num
elif action == 2:
result -= num
elif action == 3:
result *= num
elif action == 4:
result /= num
elif action == 5:
result += 1.0 / num
elif action == 6:
result += num ** 2
entry.clearText()
result.changeText(str(r), True)
if __name__ == '__main__':
app = App()
app.title("Calculator")
app.geometry(f'{App.WIDTH}x{App.HEIGHT}')
app.mainloop()
改变你所拥有的一切都是值得的。一切都会变得更干净,更易于管理。另外,我刚刚为你做了很多,你的方法永远行不通。一旦您使用 Frame
作为 BaseBlock
的 super
,您的 Button
、Label
和 Text
将全部中断。经验教训:不要告诉一堆不同类型的小部件最终扩展相同的东西。
如果你绝对坚持按照自己的方式去做~你可以这样做
class FrameBlock(InnerBlock):
def __init__(self, root, width, height, row, column, txt=None):
super().__init__(root, width, height, row, column, txt)
self.frame = tk.Frame(self.root, width=self.width, height=self.height)
self.frame.grid(row=self.g_row, column=self.g_column)
然后当您想将它用作 Button
的 master
时,请使用 action_frame.frame
一边
你计算结果的方法根本行不通。您甚至没有考虑运算符优先级。使用 eval()
。向您展示您还有多远......这就是解析 python 支持的每个可以想象的数学表达式所需要的。即使您将其简化为您的计算器支持的内容,它仍然会比您当前的整个应用程序大。
class Expression:
# Clean
__WHITE: str = '\s'
__white: Pattern = re.compile(__WHITE)
__COMM: str = '#\s.*$'
__comm: Pattern = re.compile(__COMM)
# Symbolic
__PARENS: str = '[\)\(]'
__parens: Pattern = re.compile(__PARENS)
__INFIX: str = '[%&+-]|[*/]{1,2}|<<|>>|\||\^'
__infix: Pattern = re.compile(__INFIX)
__TOKEN: str = 'STK([0-9]+)'
__token: Pattern = re.compile(__TOKEN)
__SYMBOLIC: str = f'{__PARENS}|{__INFIX}'
# Prefix
__INV: str = '([~]+|~u)?'
# Numeric
__HEX: str = '([-]?0x[0-9a-f]+)'
__hex: Pattern = re.compile(__HEX)
__IHEX: str = f'{__INV}{__HEX}'
__ihex: Pattern = re.compile(__IHEX)
__OHEX: str = f'^{__HEX}$'
__ohex: Pattern = re.compile(__OHEX)
__NUM: str = '([-]?[0-9]+(\.[0-9]+)?)'
__num: Pattern = re.compile(__NUM)
__INUM: str = f'{__INV}{__NUM}'
__inum: Pattern = re.compile(__INUM)
__ONUM: str = f'^{__NUM}$'
__onum: Pattern = re.compile(__ONUM)
__NUMERIC: str = f'{__IHEX}|{__INUM}'
# Variable
__HYPER: str = 'acosh|asinh|atanh|cosh|sinh|tanh'
__TRIG: str = 'acos|asin|atan2|atan|cos|sin|tan|hypot|dist'
__THEORY: str = 'ceil|comb|fabs|factorial|floor|fmod|frexp|gcd|isqrt|ldexp|modf|perm|remainder|trunc'
__LOG: str = 'expm1|exp|log1p|log10|log2|log|pow|sqrt'
__ANGLE: str = 'degrees|radians'
__SPEC: str = 'erfc|erf|lgamma|gamma'
__FN: str = f'{__HYPER}|{__TRIG}|{__THEORY}|{__LOG}|{__ANGLE}|{__SPEC}'
__func: Pattern = re.compile(__FN)
__RAND: str = '(random|rand)'
__rand: Pattern = re.compile(__RAND)
__CONST: str = 'pi|e|tau|inf|' + __RAND
__const: Pattern = re.compile(__CONST)
__BITWISE: str = '<<|>>|\||\^|&'
__bitwise: Pattern = re.compile(__BITWISE)
__FN2: str = 'min|max|' + __RAND
__func2: Pattern = re.compile(__FN2)
__VARIABLE: str = f'{__FN}|{__FN2}|{__CONST}'
__SIMPLE: str = f'^({__INUM}+{__INFIX})+{__INUM}$'
__simple: Pattern = re.compile(__SIMPLE)
# Combo
__MATH: str = f'{__VARIABLE}|{__NUMERIC}|{__SYMBOLIC}|,|E|\s'
__math: Pattern = re.compile(__MATH)
# Priorities
__P1: str = '[*/]{1,2}|%'
__P2: str = '[+-]'
__P3: str = '<<|>>|&'
__P4: str = '\||\^'
__priority: List[Pattern] = [re.compile(__P1), re.compile(__P2), re.compile(__P3), re.compile(__P4)]
def __init__(self):
self.value = math.nan
def evaluate(self, expr: str) -> float:
self.value = Expression.eval(expr)
return self.value
@staticmethod
def __hexrepl(m: Match[Union[str, bytes]]):
return str(int(m.group(0), 16))
@staticmethod
def eval(expr: str, fast: bool = False) -> float:
# Remove Whitespace, Comments, Convert Hash To Hex and Case To Lower
expr = Expression.__comm.sub("", expr)
expr = Expression.__white.sub("", expr)
expr = expr.replace('#', '0x').lower()
# Check If This Is Actual Math By Deleting Everything Math Related And Seeing If Anything Is Left
if len(re.sub(Expression.__math, "", expr)) > 0:
return math.nan
if fast:
return Expression.__fast(expr)
# Parse All Inversions Now ... invert(~) is the only "left side only" operator
expr = Expression.__parse_inversions(expr)
expr = Expression.__hex.sub(Expression.__hexrepl, expr)
# Check If This Is Solely A Number ~ If So, Parse Int And Return
if Expression.__onum.match(expr):
n = float(expr)
return int(n) if n % 1 == 0 else n
# We Got This Far. It Must Be Math
n = Expression.__parse(expr)
return int(n) if n % 1 == 0 else n
# Private Static Interfaces
@staticmethod
def __parse_inversions(expr: str) -> str:
match: Iterator[Match[Union[str, bytes]]] = Expression.__ihex.finditer(expr)
m: Match[Union[str, bytes]]
for m in match:
expr = Expression.__invert_expr(expr, m, 16)
match = Expression.__inum.finditer(expr)
for m in match:
expr = Expression.__invert_expr(expr, m, 10)
return expr
@staticmethod
def __invert_expr(expr: str, m: Match[Union[str, bytes]], b: int) -> str:
t1: str = m.group(1)
t2: str = m.group(2)
if t1:
if t1 == '~u':
n: int = Expression.__uinvert_num(int(t2, b))
else:
f: int = len(t1) % 2 == 1
n: int = -(int(t2, b) + 1) if f else int(t2, b)
expr = expr.replace(m.group(0), str(n))
return expr
@staticmethod
def __uinvert_num(num: float) -> int:
if num > 0:
x: int = int(math.log(num, 2.0) + 1)
i: int = 0
for i in range(0, x):
num = (num ^ (1 << i))
return num
@staticmethod
def __parse(expr: str) -> float:
exp_stack: List[str] = []
ops_stack: List[str] = []
res_stack: List[float] = []
tokens = Expression.__tokenize(expr)
# everything that can come before an operator
b1: str = f'{Expression.__HEX}|{Expression.__NUM}|{Expression.__CONST}|\)'
c: Pattern = re.compile(b1)
# before an operator that is the rest of this expression
b2: str = f'{Expression.__NUM}E'
d: Pattern = re.compile(b2, re.I)
expr = tokens.expression[0::]
while len(expr):
m: Match[Union[str, bytes]] = Expression.__infix.search(expr)
if m:
op: str = m.group()
left: str = expr[0:m.span()[0]]
if re.search(c, left) and not re.search(d, left):
exp_stack.append(left)
ops_stack.append(op)
expr = expr.replace(f'{left}{op}', "")
else:
if len(left) == 0 or re.match(d, left):
right: str = expr[m.span()[1]::]
m = Expression.__infix.search(right)
if m:
left = f'{left}{op}'
op = m.group()
left = f'{left}{right[0:m.span()[0]]}'
exp_stack.append(left)
ops_stack.append(op)
expr = expr.replace(f'{left}{op}', "")
else:
exp_stack.append(expr)
expr = ""
else:
# Probably Not Even Possible In A Valid Math Expression
print("Expression.parse(expr:String): unexpected left side")
print("expression: ", expr)
print("left side: ", left)
print("operator: ", op)
print("exp_stack: ", exp_stack)
print("ops_stack: ", ops_stack)
else:
exp_stack.append(expr)
expr = ""
for r in range(len(exp_stack)):
m: Match[Union[str, bytes]] = Expression.__token.search(exp_stack[r])
inner: str = ""
if m:
i: int = int(m.group(1))
inner = tokens.stack[i]
res_stack.append(Expression.__parsetype(exp_stack[r], inner))
# Iterate Through Stacks Based On Priority and Do Assignments ~ ie... Calculate Everything
if len(ops_stack) > 0:
p: int = 0
for p in range(len(Expression.__priority)):
n: int = 0
while n < len(ops_stack) and len(ops_stack) > 0:
m: Match[Union[str, bytes]] = Expression.__priority[p].match(ops_stack[n])
if m is not None:
if not math.isnan(res_stack[n]) and not math.isnan(res_stack[n + 1]):
res_stack[n] = Expression.__value(res_stack[n], ops_stack[n], res_stack[n + 1])
res_stack.pop(n + 1)
ops_stack.pop(n)
else:
n += 1
return res_stack[0] if len(res_stack) == 1 else math.nan
@staticmethod
def __parsetype(expr: str, val: str = "") -> float:
fin: float = math.nan
if val != "":
tokens: Tokens_t = Expression.__tokenize(val)
csv: List[str] = tokens.expression.split(",")
a: float = 0
b: float = 0
f: str = ""
ln: int = len(csv)
if ln >= 1:
a = Expression.__parse(Expression.__detokenize(csv[0], tokens))
if ln == 2:
b = Expression.__parse(Expression.__detokenize(csv[1], tokens))
m: Match[Union[str, bytes]] = Expression.__func.match(expr)
m2: Match[Union[str, bytes]] = Expression.__func2.match(expr)
if m:
f = m.group()
fin = getattr(math, f)(a, b) if len(csv) == 2 else getattr(math, f)(a)
elif m2:
f = m2.group()
if ln == 2:
if f == 'min':
fin = min(a, b)
elif f == 'max':
fin = max(a, b)
elif ln == 1:
if Expression.__rand.match(f):
fin = random() * a
else:
fin = Expression.__parse(val)
else:
m: Match[Union[str, bytes]] = Expression.__const.match(expr)
c: Match[Union[str, bytes]] = Expression.__hex.match(expr)
if m:
cn: str = m.group()
fin = random() if Expression.__rand.match(cn) else getattr(math, cn)
elif c:
fin = int(c.group(), 16)
else:
fin = float(expr)
return fin
@staticmethod
def __tokenize(expr: str) -> Tokens_t:
c: int = 0
b: int = -1
e: int = -1
ex: str = expr[0::]
s: List[str] = []
m: Match[Union[str, bytes]]
p: Iterator[Match[Union[str, bytes]]] = Expression.__parens.finditer(ex)
for m in p:
if m.group() == "(":
c += 1
if b == -1:
b = m.span()[1]
elif m.group() == ")":
c -= 1
if c == 0 and b > -1:
e = m.span()[0]
if b != e:
s.append(expr[b:e])
ex = ex.replace(expr[b:e], f'STK{len(s) - 1}')
b = -1
return Tokens_t(ex, s) # Tokens_t ~ python equivalent to my c++ math parser
@staticmethod
def __detokenize(part: str, tokens: Tokens_t) -> str:
ex: str = part[0::]
m: Match[Union[str, bytes]]
p: Iterator[Match[Union[str, bytes]]] = Expression.__token.finditer(ex)
for m in p:
ex = ex.replace(m.group(0), tokens.stack[int(m.group(1))])
return ex
@staticmethod
def __fast(expr: str) -> float:
return eval(expr)
__ops: Dict[str, Callable] = {
'+': lambda x, y: x + y,
'-': lambda x, y: x - y,
'*': lambda x, y: x * y,
'/': lambda x, y: x / y,
'**': lambda x, y: x ** y,
'//': lambda x, y: x // y,
'>>': lambda x, y: x >> y,
'<<': lambda x, y: x << y,
'&': lambda x, y: x & y,
'|': lambda x, y: x | y,
'^': lambda x, y: x ^ y,
'%': lambda x, y: x % y,
}
@staticmethod
def __value(v1: float, oper: str, v2: float) -> float:
x: float = 0
try:
m: Match[Union[str, bytes]] = Expression.__bitwise.match(oper)
x = Expression.__ops[oper](v1, v2) if not m else Expression.__ops[oper](int(v1), int(v2))
except KeyError:
x = math.nan
return x
简单地说,您不能在 action_frame
中放置小部件,因为 action_frame
不是小部件。您只能将 tkinter 小部件用作另一个小部件的主人。
我正在尝试为我的应用构建更复杂的 GUI。我正在尝试使用框架内的 .grid() 放置按钮。但是,每当我尝试创建一个以框架为根的按钮时,我都会得到 "AttributeError: object has no attribute 'tk'"
。我见过有人编写 GUI classes(即 class Frame(tk.Frame)),这给我的代码带来了更多问题。我应该如何创建按钮并将它们放置在框架内,而不必从头开始重写我的大部分 classes?
当我将一个按钮固定到“主”时,它工作正常。但是,如果它植根于“action_frame”,那就是我收到错误的时候。
calculator.py
# -*- coding: utf-8 -*-
import tkinter as tk
from math import *
from classes_GUI import ButtonBlock, LabelBlock, TextBlock, FrameBlock
from classes_calculator import ActionBlock
# Store input number
def storeInput(entry_text, result_text, action, array):
numb = 0.0
try:
numb = float(entry_text.retrieveTextInput())
except:
print('Please enter a valid number')
return
calc_action = ActionBlock(numb, action)
array.append(calc_action)
entry_text.clearText()
num = calc_action.returnNumber()
act = calc_action.returnAction()
input_texts = dict([
(1, ' + ' + str(num)),
(2, ' - ' + str(num)),
(3, ' * ' + str(num)),
(4, ' / ' + str(num)),
(5, ' + 1/' + str(num)),
(6, ' + ' + str(num) + '^2')
])
result_text.changeText(input_texts[act])
# Calculate result
def calcResult(entry_text, result_text, array):
result = 0.0
for calc in array:
action = calc.returnAction()
num = calc.returnNumber()
if action == 1:
result += num
elif action == 2:
result -= num
elif action == 3:
result *= num
elif action == 4:
result /= num
elif action == 5:
result += 1.0 / num
elif action == 6:
result += num ** 2
entry_text.clearText()
result_text.changeText(str(result), True)
# Create a new calculator instance
def exeCalc():
action_blocks = []
button_blocks = []
frame_blocks = []
label_blocks = []
text_blocks = []
# Create GUI
master = tk.Tk()
master.title('Calculator')
# Create frames
action_frame = FrameBlock(master, 30, 30, 1, 6)
frame_blocks.append(action_frame)
for f in frame_blocks:
f.createFrame()
# Create GUI labels
title_label = LabelBlock(master, 20, 2, 'n', 0, 0, 'center', 'Calculator')
label_blocks.append(title_label)
entry_label = LabelBlock(master, 20, 2, 'n', 1, 0, 'center', 'Enter:')
label_blocks.append(entry_label)
result_label = LabelBlock(master, 20, 2, 'n', 2, 0, 'center', 'Result:')
label_blocks.append(result_label)
for l in label_blocks:
l.createLabel()
# Create GUI text
entry_text = TextBlock(master, 20, 2, 1, 1, 'normal', '')
text_blocks.append(entry_text)
result_text = TextBlock(master, 20, 2, 2, 1, 'disabled', '0')
text_blocks.append(result_text)
for t in text_blocks:
t.createText()
# Create GUI buttons
close_button = ButtonBlock(master, 6, 2, 3, 0, 'Close',
lambda: master.destroy())
button_blocks.append(close_button)
add_button = ButtonBlock(frame_blocks[0], 4, 2, 0, 0, '+',
lambda: storeInput(text_blocks[0], text_blocks[1], 1, action_blocks))
button_blocks.append(add_button)
subtract_button = ButtonBlock(frame_blocks[0], 4, 2, 0, 1, '-',
lambda: storeInput(text_blocks[0], text_blocks[1], 2, action_blocks))
button_blocks.append(subtract_button)
multiply_button = ButtonBlock(frame_blocks[0], 4, 2, 0, 2, 'x',
lambda: storeInput(text_blocks[0], text_blocks[1], 3, action_blocks))
button_blocks.append(multiply_button)
divide_button = ButtonBlock(frame_blocks[0], 4, 2, 1, 0, '/',
lambda: storeInput(text_blocks[0], text_blocks[1], 4, action_blocks))
button_blocks.append(divide_button)
fraction_button = ButtonBlock(frame_blocks[0], 4, 2, 1, 1, '1/x',
lambda: storeInput(text_blocks[0], text_blocks[1], 5,
action_blocks))
button_blocks.append(fraction_button)
square_block = ButtonBlock(frame_blocks[0], 4, 2, 1, 2, 'x^2',
lambda: storeInput(text_blocks[0], text_blocks[1], 6,
action_blocks))
button_blocks.append(square_block)
equal_button = ButtonBlock(frame_blocks[0], 4, 2, 2, 0, '=',
lambda: calcResult(text_blocks[0], text_blocks[1], action_blocks))
button_blocks.append(equal_button)
for b in button_blocks:
b.createButton()
master.mainloop()
classes_GUI.py
# -*- coding: utf-8 -*-
import tkinter as tk
# Create a base data block
class BaseBlock():
def __init__(self, root, width, height, txt):
self.root = root
self.width = width
self.height = height
self.txt = txt
# Create a inner data block
class InnerBlock(BaseBlock):
def __init__(self, root, width, height, row, column, txt):
super().__init__(root, width, height, txt)
self.g_row = row
self.g_column = column
# Create a data block for a button
class ButtonBlock(InnerBlock):
def __init__(self, root, width, height, row, column, txt, command=None):
super().__init__(root, width, height, row, column, txt)
self.command = command
def createButton(self):
button = tk.Button(self.root, text=self.txt, width=self.width,
height=self.height, command=self.command)
button.grid(row=self.g_row, column=self.g_column)
return button
# Create a frame data block
class FrameBlock(InnerBlock):
def __init__(self, root, width, height, row, column, txt=None):
super().__init__(root, width, height, row, column, txt)
def createFrame(self):
frame = tk.Frame(self.root, width=self.width, height=self.height)
frame.grid(row=self.g_row, column=self.g_column)
return frame
# Create a data block for a window
class LabelBlock(InnerBlock):
def __init__(self, root, width, height, anchor, row, column, justify, txt):
super().__init__(root, width, height, row, column, txt)
self.anchor = anchor
self.justify = justify
def createLabel(self):
label = tk.Label(self.root, width=self.width, height=self.height,
anchor=self.anchor, justify=self.justify, text=self.txt)
label.grid(row=self.g_row, column=self.g_column)
return label
# Create a data block for text
class TextBlock(InnerBlock):
def __init__(self, root, width, height, row, column, state, txt):
super().__init__(root, width, height, row, column, txt)
self.state = state
self.text = None
def createText(self):
self.text = tk.Text(self.root, width=self.width, height=self.height)
self.text.insert(tk.END, self.txt)
self.text.grid(row=self.g_row, column=self.g_column)
self.text.config(state=self.state)
return self.text
# Clear text
def clearText(self):
self.text.delete('1.0', 'end')
# Change text
def changeText(self, new_txt, clear=False):
self.text.config(state='normal')
if clear:
self.clearText()
self.text.insert(tk.END, new_txt)
self.text.config(state='disabled')
# Retrieve input from text box
def retrieveTextInput(self):
text_input = self.text.get('1.0', 'end')
return text_input
如果你的基础 class 实际上扩展了一个 Frame
,它会有所帮助。 ;)
class BaseBlock(tk.Frame):
def __init__(self, master, width, height, txt):
tk.Frame.__init__(self, master)
但实际上,您创建了太多层并且管理一切都很奇怪。下面的会更好。所有这些每一步的继承和创建功能都太混乱了。
import tkinter as tk
from math import *
from dataclasses import asdict, dataclass
from typing import Callable
@dataclass
class Label_dc:
width: int = 20
height: int = 2
anchor: str = 'n'
justify: str = 'center'
text: str = ''
@dataclass
class Button_dc:
width: int = 4
height: int = 2
text: str = ''
command: Callable = None
@dataclass
class Text_dc:
width: int = 20
height: int = 2
state: str = 'normal'
#from classes_calculator import ActionBlock
class FrameBlock(tk.Frame):
def __init__(self, master, row, column, rowspan, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.grid(row=row, column=column, rowspan=rowspan)
class ButtonBlock(tk.Button):
def __init__(self, master, row, column, **kwargs):
tk.Button.__init__(self, master, **asdict(Button_dc(**kwargs)))
self.grid(row=row, column=column)
class LabelBlock(tk.Label):
def __init__(self, master, row, column, **kwargs):
tk.Label.__init__(self, master, **asdict(Label_dc(**kwargs)))
self.grid(row=row, column=column)
class TextBlock(tk.Text):
def __init__(self, master, row, column, text='', **kwargs):
tk.Text.__init__(self, master, **asdict(Text_dc(**kwargs)))
self.grid(row=row, column=column)
self.insert('1.end', text)
# Clear text
def clearText(self):
self.delete('1.0', 'end')
# Change text
def changeText(self, new_txt, clear=False):
self.config(state='normal')
if clear:
self.clearText()
self.insert(tk.END, new_txt)
self.config(state='disabled')
# Retrieve input from text box
def retrieveTextInput(self):
return self.get('1.0', 'end')
class App(tk.Tk):
WIDTH = 800
HEIGHT = 600
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# Create GUI labels
LabelBlock(self, 0, 0, text='Calculator')
LabelBlock(self, 1, 0, text='Enter:')
LabelBlock(self, 2, 0, text='Result:')
# Create GUI text
text_blocks = {
'entry' : TextBlock(self, 1, 1),
'result': TextBlock(self, 2, 1, state='disabled', text='0'),
}
#can't use ButtonBlock for this one ~ self.destroy wont pickle properly
tk.Button(self, text='Close', width=6, height=2, command=self.destroy).grid(row=3, column=0)
action = []
# Create frames
frame = FrameBlock(self, 1, 3, 2, width=30, height=30)
# Create GUI buttons
ButtonBlock(frame, 0, 0, text='+', command=lambda: self.store(*text_blocks, 1, action))
ButtonBlock(frame, 0, 1, text='-', command=lambda: self.store(*text_blocks, 2, action))
ButtonBlock(frame, 0, 2, text='*', command=lambda: self.store(*text_blocks, 2, action))
ButtonBlock(frame, 1, 0, text='/', command=lambda: self.store(*text_blocks, 4, action))
ButtonBlock(frame, 1, 1, text='1/x', command=lambda: self.store(*text_blocks, 5, action))
ButtonBlock(frame, 1, 2, text='x^2', command=lambda: self.store(*text_blocks, 6, action))
ButtonBlock(frame, 2, 0, text='=', command=lambda: self.calc(*text_blocks, action))
def store(self, entry, result, action, array):
pass #remove this line
numb = 0.0
try:
numb = float(entry.retrieveTextInput())
except:
print('Please enter a valid number')
return
calc_action = ActionBlock(numb, action)
array.append(calc_action)
entry.clearText()
num = calc_action.returnNumber()
act = calc_action.returnAction()
input_texts = dict([
(1, ' + ' + str(num)),
(2, ' - ' + str(num)),
(3, ' * ' + str(num)),
(4, ' / ' + str(num)),
(5, ' + 1/' + str(num)),
(6, ' + ' + str(num) + '^2')
])
result.changeText(input_texts[act])
# Calculate result
def calc(self, entry, result, array):
pass #remove this line
r = 0.0
for calc in array:
action = calc.returnAction()
num = calc.returnNumber()
if action == 1:
result += num
elif action == 2:
result -= num
elif action == 3:
result *= num
elif action == 4:
result /= num
elif action == 5:
result += 1.0 / num
elif action == 6:
result += num ** 2
entry.clearText()
result.changeText(str(r), True)
if __name__ == '__main__':
app = App()
app.title("Calculator")
app.geometry(f'{App.WIDTH}x{App.HEIGHT}')
app.mainloop()
改变你所拥有的一切都是值得的。一切都会变得更干净,更易于管理。另外,我刚刚为你做了很多,你的方法永远行不通。一旦您使用 Frame
作为 BaseBlock
的 super
,您的 Button
、Label
和 Text
将全部中断。经验教训:不要告诉一堆不同类型的小部件最终扩展相同的东西。
如果你绝对坚持按照自己的方式去做~你可以这样做
class FrameBlock(InnerBlock):
def __init__(self, root, width, height, row, column, txt=None):
super().__init__(root, width, height, row, column, txt)
self.frame = tk.Frame(self.root, width=self.width, height=self.height)
self.frame.grid(row=self.g_row, column=self.g_column)
然后当您想将它用作 Button
的 master
时,请使用 action_frame.frame
一边
你计算结果的方法根本行不通。您甚至没有考虑运算符优先级。使用 eval()
。向您展示您还有多远......这就是解析 python 支持的每个可以想象的数学表达式所需要的。即使您将其简化为您的计算器支持的内容,它仍然会比您当前的整个应用程序大。
class Expression:
# Clean
__WHITE: str = '\s'
__white: Pattern = re.compile(__WHITE)
__COMM: str = '#\s.*$'
__comm: Pattern = re.compile(__COMM)
# Symbolic
__PARENS: str = '[\)\(]'
__parens: Pattern = re.compile(__PARENS)
__INFIX: str = '[%&+-]|[*/]{1,2}|<<|>>|\||\^'
__infix: Pattern = re.compile(__INFIX)
__TOKEN: str = 'STK([0-9]+)'
__token: Pattern = re.compile(__TOKEN)
__SYMBOLIC: str = f'{__PARENS}|{__INFIX}'
# Prefix
__INV: str = '([~]+|~u)?'
# Numeric
__HEX: str = '([-]?0x[0-9a-f]+)'
__hex: Pattern = re.compile(__HEX)
__IHEX: str = f'{__INV}{__HEX}'
__ihex: Pattern = re.compile(__IHEX)
__OHEX: str = f'^{__HEX}$'
__ohex: Pattern = re.compile(__OHEX)
__NUM: str = '([-]?[0-9]+(\.[0-9]+)?)'
__num: Pattern = re.compile(__NUM)
__INUM: str = f'{__INV}{__NUM}'
__inum: Pattern = re.compile(__INUM)
__ONUM: str = f'^{__NUM}$'
__onum: Pattern = re.compile(__ONUM)
__NUMERIC: str = f'{__IHEX}|{__INUM}'
# Variable
__HYPER: str = 'acosh|asinh|atanh|cosh|sinh|tanh'
__TRIG: str = 'acos|asin|atan2|atan|cos|sin|tan|hypot|dist'
__THEORY: str = 'ceil|comb|fabs|factorial|floor|fmod|frexp|gcd|isqrt|ldexp|modf|perm|remainder|trunc'
__LOG: str = 'expm1|exp|log1p|log10|log2|log|pow|sqrt'
__ANGLE: str = 'degrees|radians'
__SPEC: str = 'erfc|erf|lgamma|gamma'
__FN: str = f'{__HYPER}|{__TRIG}|{__THEORY}|{__LOG}|{__ANGLE}|{__SPEC}'
__func: Pattern = re.compile(__FN)
__RAND: str = '(random|rand)'
__rand: Pattern = re.compile(__RAND)
__CONST: str = 'pi|e|tau|inf|' + __RAND
__const: Pattern = re.compile(__CONST)
__BITWISE: str = '<<|>>|\||\^|&'
__bitwise: Pattern = re.compile(__BITWISE)
__FN2: str = 'min|max|' + __RAND
__func2: Pattern = re.compile(__FN2)
__VARIABLE: str = f'{__FN}|{__FN2}|{__CONST}'
__SIMPLE: str = f'^({__INUM}+{__INFIX})+{__INUM}$'
__simple: Pattern = re.compile(__SIMPLE)
# Combo
__MATH: str = f'{__VARIABLE}|{__NUMERIC}|{__SYMBOLIC}|,|E|\s'
__math: Pattern = re.compile(__MATH)
# Priorities
__P1: str = '[*/]{1,2}|%'
__P2: str = '[+-]'
__P3: str = '<<|>>|&'
__P4: str = '\||\^'
__priority: List[Pattern] = [re.compile(__P1), re.compile(__P2), re.compile(__P3), re.compile(__P4)]
def __init__(self):
self.value = math.nan
def evaluate(self, expr: str) -> float:
self.value = Expression.eval(expr)
return self.value
@staticmethod
def __hexrepl(m: Match[Union[str, bytes]]):
return str(int(m.group(0), 16))
@staticmethod
def eval(expr: str, fast: bool = False) -> float:
# Remove Whitespace, Comments, Convert Hash To Hex and Case To Lower
expr = Expression.__comm.sub("", expr)
expr = Expression.__white.sub("", expr)
expr = expr.replace('#', '0x').lower()
# Check If This Is Actual Math By Deleting Everything Math Related And Seeing If Anything Is Left
if len(re.sub(Expression.__math, "", expr)) > 0:
return math.nan
if fast:
return Expression.__fast(expr)
# Parse All Inversions Now ... invert(~) is the only "left side only" operator
expr = Expression.__parse_inversions(expr)
expr = Expression.__hex.sub(Expression.__hexrepl, expr)
# Check If This Is Solely A Number ~ If So, Parse Int And Return
if Expression.__onum.match(expr):
n = float(expr)
return int(n) if n % 1 == 0 else n
# We Got This Far. It Must Be Math
n = Expression.__parse(expr)
return int(n) if n % 1 == 0 else n
# Private Static Interfaces
@staticmethod
def __parse_inversions(expr: str) -> str:
match: Iterator[Match[Union[str, bytes]]] = Expression.__ihex.finditer(expr)
m: Match[Union[str, bytes]]
for m in match:
expr = Expression.__invert_expr(expr, m, 16)
match = Expression.__inum.finditer(expr)
for m in match:
expr = Expression.__invert_expr(expr, m, 10)
return expr
@staticmethod
def __invert_expr(expr: str, m: Match[Union[str, bytes]], b: int) -> str:
t1: str = m.group(1)
t2: str = m.group(2)
if t1:
if t1 == '~u':
n: int = Expression.__uinvert_num(int(t2, b))
else:
f: int = len(t1) % 2 == 1
n: int = -(int(t2, b) + 1) if f else int(t2, b)
expr = expr.replace(m.group(0), str(n))
return expr
@staticmethod
def __uinvert_num(num: float) -> int:
if num > 0:
x: int = int(math.log(num, 2.0) + 1)
i: int = 0
for i in range(0, x):
num = (num ^ (1 << i))
return num
@staticmethod
def __parse(expr: str) -> float:
exp_stack: List[str] = []
ops_stack: List[str] = []
res_stack: List[float] = []
tokens = Expression.__tokenize(expr)
# everything that can come before an operator
b1: str = f'{Expression.__HEX}|{Expression.__NUM}|{Expression.__CONST}|\)'
c: Pattern = re.compile(b1)
# before an operator that is the rest of this expression
b2: str = f'{Expression.__NUM}E'
d: Pattern = re.compile(b2, re.I)
expr = tokens.expression[0::]
while len(expr):
m: Match[Union[str, bytes]] = Expression.__infix.search(expr)
if m:
op: str = m.group()
left: str = expr[0:m.span()[0]]
if re.search(c, left) and not re.search(d, left):
exp_stack.append(left)
ops_stack.append(op)
expr = expr.replace(f'{left}{op}', "")
else:
if len(left) == 0 or re.match(d, left):
right: str = expr[m.span()[1]::]
m = Expression.__infix.search(right)
if m:
left = f'{left}{op}'
op = m.group()
left = f'{left}{right[0:m.span()[0]]}'
exp_stack.append(left)
ops_stack.append(op)
expr = expr.replace(f'{left}{op}', "")
else:
exp_stack.append(expr)
expr = ""
else:
# Probably Not Even Possible In A Valid Math Expression
print("Expression.parse(expr:String): unexpected left side")
print("expression: ", expr)
print("left side: ", left)
print("operator: ", op)
print("exp_stack: ", exp_stack)
print("ops_stack: ", ops_stack)
else:
exp_stack.append(expr)
expr = ""
for r in range(len(exp_stack)):
m: Match[Union[str, bytes]] = Expression.__token.search(exp_stack[r])
inner: str = ""
if m:
i: int = int(m.group(1))
inner = tokens.stack[i]
res_stack.append(Expression.__parsetype(exp_stack[r], inner))
# Iterate Through Stacks Based On Priority and Do Assignments ~ ie... Calculate Everything
if len(ops_stack) > 0:
p: int = 0
for p in range(len(Expression.__priority)):
n: int = 0
while n < len(ops_stack) and len(ops_stack) > 0:
m: Match[Union[str, bytes]] = Expression.__priority[p].match(ops_stack[n])
if m is not None:
if not math.isnan(res_stack[n]) and not math.isnan(res_stack[n + 1]):
res_stack[n] = Expression.__value(res_stack[n], ops_stack[n], res_stack[n + 1])
res_stack.pop(n + 1)
ops_stack.pop(n)
else:
n += 1
return res_stack[0] if len(res_stack) == 1 else math.nan
@staticmethod
def __parsetype(expr: str, val: str = "") -> float:
fin: float = math.nan
if val != "":
tokens: Tokens_t = Expression.__tokenize(val)
csv: List[str] = tokens.expression.split(",")
a: float = 0
b: float = 0
f: str = ""
ln: int = len(csv)
if ln >= 1:
a = Expression.__parse(Expression.__detokenize(csv[0], tokens))
if ln == 2:
b = Expression.__parse(Expression.__detokenize(csv[1], tokens))
m: Match[Union[str, bytes]] = Expression.__func.match(expr)
m2: Match[Union[str, bytes]] = Expression.__func2.match(expr)
if m:
f = m.group()
fin = getattr(math, f)(a, b) if len(csv) == 2 else getattr(math, f)(a)
elif m2:
f = m2.group()
if ln == 2:
if f == 'min':
fin = min(a, b)
elif f == 'max':
fin = max(a, b)
elif ln == 1:
if Expression.__rand.match(f):
fin = random() * a
else:
fin = Expression.__parse(val)
else:
m: Match[Union[str, bytes]] = Expression.__const.match(expr)
c: Match[Union[str, bytes]] = Expression.__hex.match(expr)
if m:
cn: str = m.group()
fin = random() if Expression.__rand.match(cn) else getattr(math, cn)
elif c:
fin = int(c.group(), 16)
else:
fin = float(expr)
return fin
@staticmethod
def __tokenize(expr: str) -> Tokens_t:
c: int = 0
b: int = -1
e: int = -1
ex: str = expr[0::]
s: List[str] = []
m: Match[Union[str, bytes]]
p: Iterator[Match[Union[str, bytes]]] = Expression.__parens.finditer(ex)
for m in p:
if m.group() == "(":
c += 1
if b == -1:
b = m.span()[1]
elif m.group() == ")":
c -= 1
if c == 0 and b > -1:
e = m.span()[0]
if b != e:
s.append(expr[b:e])
ex = ex.replace(expr[b:e], f'STK{len(s) - 1}')
b = -1
return Tokens_t(ex, s) # Tokens_t ~ python equivalent to my c++ math parser
@staticmethod
def __detokenize(part: str, tokens: Tokens_t) -> str:
ex: str = part[0::]
m: Match[Union[str, bytes]]
p: Iterator[Match[Union[str, bytes]]] = Expression.__token.finditer(ex)
for m in p:
ex = ex.replace(m.group(0), tokens.stack[int(m.group(1))])
return ex
@staticmethod
def __fast(expr: str) -> float:
return eval(expr)
__ops: Dict[str, Callable] = {
'+': lambda x, y: x + y,
'-': lambda x, y: x - y,
'*': lambda x, y: x * y,
'/': lambda x, y: x / y,
'**': lambda x, y: x ** y,
'//': lambda x, y: x // y,
'>>': lambda x, y: x >> y,
'<<': lambda x, y: x << y,
'&': lambda x, y: x & y,
'|': lambda x, y: x | y,
'^': lambda x, y: x ^ y,
'%': lambda x, y: x % y,
}
@staticmethod
def __value(v1: float, oper: str, v2: float) -> float:
x: float = 0
try:
m: Match[Union[str, bytes]] = Expression.__bitwise.match(oper)
x = Expression.__ops[oper](v1, v2) if not m else Expression.__ops[oper](int(v1), int(v2))
except KeyError:
x = math.nan
return x
简单地说,您不能在 action_frame
中放置小部件,因为 action_frame
不是小部件。您只能将 tkinter 小部件用作另一个小部件的主人。