- Avoid putting variables in the global namespace
- Use a more meaningful variable name than
Var1
- With your current usage,
Varx
variables should be boolean variables, not integer variables, due to them connecting to checkboxes
- Use radio buttons rather than checkboxes, because you really want exclusive selection
- No reason not to accept floats instead of ints for your entry boxes
- Generalize a series of conversion factors so that your division and multiplication only need to be expressed once
- No need to set your controls as members on
master
- Avoid calling things
master
; it's 2021
- Centralize the expression of your padding quantities, since they're all the same
- Separate your business logic from your presentation
Suggested
from enum import Enum
from functools import partial
from tkinter import Button, DoubleVar, Entry, Grid, IntVar, Radiobutton, Tk
from typing import Callable, Dict, Iterable
FACTORS = (1, 60, 60 * 60)
class TimeComponent(Enum):
SECOND = 0
MINUTE = 1
HOUR = 2
def get_factor(self) -> int:
return FACTORS[self.value]
class GUIVariable:
def __init__(
self,
root: Tk,
component: TimeComponent,
selected_component: IntVar,
):
self.check_button = Radiobutton(
root,
text=component.name.title(),
variable=selected_component,
value=component.value,
)
value = DoubleVar()
self.get_value: Callable[[], float] = value.get
self.set_value: Callable[[float], None] = value.set
self.entry = Entry(root, textvariable=value)
class TimeController:
def __init__(self, component: TimeComponent, gui: GUIVariable):
self.get_factor: Callable[[], int] = component.get_factor
self.gui = gui
@property
def normalized(self) -> float:
return self.gui.get_value() * self.get_factor()
@normalized.setter
def normalized(self, x: float) -> None:
self.gui.set_value(x / self.get_factor())
def adjust_others(self, others: Iterable['TimeController']):
value = self.normalized
for other in others:
if other is not self:
other.normalized = value
class GUI:
def __init__(self):
root = Tk()
self.run: Callable[[], None] = root.mainloop
root.title('Time Converter')
root.geometry('+600+300')
padded_grid = partial(Grid.grid, pady=5, padx=20)
convert_button = Button(root, text='Convert', command=self.convert_time)
padded_grid(convert_button, column=1, row=3)
self.selected_var = IntVar()
self.controllers: Dict[TimeComponent, TimeController] = {}
for component in TimeComponent:
var = GUIVariable(root, component, self.selected_var)
padded_grid(var.check_button, column=component.value, row=0)
padded_grid(var.entry, column=component.value, row=1)
controller = TimeController(component, var)
self.controllers[component] = controller
@property
def current_controller(self) -> TimeController:
return self.controllers[
TimeComponent(self.selected_var.get())
]
def convert_time(self) -> None:
self.current_controller.adjust_others(self.controllers.values())
if __name__ == '__main__':
GUI().run()
Alternative
An alternate GUI that is fairly simplified: don't have any selectors or buttons, and when one field changes, update the others.
from enum import Enum
from functools import partial
from tkinter import DoubleVar, Entry, Grid, Label, Tk, TclError
from typing import Callable, Dict, Iterable
FACTORS = (1, 60, 60 * 60)
class TimeComponent(Enum):
SECOND = 0
MINUTE = 1
HOUR = 2
def get_factor(self) -> int:
return FACTORS[self.value]
class GUIVariable:
def __init__(
self,
root: Tk,
component: TimeComponent,
trace: Callable[['GUIVariable'], None],
):
value = DoubleVar()
value.trace('w', self.trace_inner)
self.trace = trace
self.component = component
self.get_value: Callable[[], float] = value.get
self.set_value: Callable[[float], None] = value.set
self.label = Label(root, text=component.name.title())
self.entry = Entry(root, textvariable=value)
def trace_inner(self, name: str, _: str, mode: str) -> None:
self.trace(self)
class TimeController:
def __init__(self, component: TimeComponent, gui: GUIVariable):
self.get_factor: Callable[[], int] = component.get_factor
self.gui = gui
@property
def normalized(self) -> float:
return self.gui.get_value() * self.get_factor()
@normalized.setter
def normalized(self, x: float) -> None:
self.gui.set_value(x / self.get_factor())
def adjust_others(self, others: Iterable['TimeController']):
try:
value = self.normalized
except TclError:
return
for other in others:
if other is not self:
other.normalized = value
class GUI:
def __init__(self):
root = Tk()
self.run: Callable[[], None] = root.mainloop
root.title('Time Converter')
root.geometry('+600+300')
padded_grid = partial(Grid.grid, pady=5, padx=20)
self.controllers: Dict[TimeComponent, TimeController] = {}
for component in TimeComponent:
var = GUIVariable(root, component, self.on_change)
padded_grid(var.label, column=component.value, row=0)
padded_grid(var.entry, column=component.value, row=1)
controller = TimeController(component, var)
self.controllers[component] = controller
def on_change(self, var: GUIVariable) -> None:
controller = self.controllers[var.component]
controller.adjust_others(self.controllers.values())
if __name__ == '__main__':
GUI().run()
