Posts: 201
Threads: 37
Joined: Dec 2021
Hello,
I cant figure out why my text in upper left corner is overlaping when i switch the conversion from F to C and P to CM.
from tkinter import *
class Temperature:
def __init__(self):
self.valeur = 0
def conversion(self):
if choix.get() == 'Celcius à Fahrenheit':
celcius = float(entree.get())
fahrenheit = (celcius * 1.8) + 32
resultat.configure(text=str(fahrenheit))
elif choix.get() == 'Fahrenheit à Celcius':
fahrenheit = float(entree.get())
celcius = (fahrenheit - 32) / 1.8
resultat.configure(text=str(celcius))
elif choix.get() == 'Pouce en Cm':
pouce = float(entree.get())
cm = pouce * 2.54
resultat.configure(text=str(cm))
else:
cm = float(entree.get())
pouce = cm / 2.54
resultat.configure(text=str(pouce))
def changement(event):
if choix.get() == 'Celcius à Fahrenheit':
txt1.configure(text='Température en Celcius')
txt2.configure(text='Température en Fahrenheit')
elif choix.get() == 'Fahrenheit à Celcius':
txt2.configure(text='Température en Celcius')
txt1.configure(text='Température en Fahrenheit')
elif choix.get() == 'Pouce en Cm':
txt1.configure(text='Pouce en Cm')
txt2.configure(text='Cm en Pouce')
else:
txt2.configure(text='Pouce en Cm')
txt1.configure(text='Cm en Pouce')
t = Temperature()
fen1 = Tk()
fen1.title('Conversion')
txt1 = Label(fen1, text='Température en Fahrenheit')
txt1.grid(row=0, column=0, sticky=E)
entree = Entry(fen1)
entree.grid(row=0, column=1)
txt2 = Label(fen1, text='Température en Celcius')
txt2.grid(row=1, column=0, sticky=E)
resultat = Label(fen1, text='')
resultat.grid(row=1, column=1)
txt3 = Label(fen1, text='Pouce en Cm')
txt3.grid(row=0, column=0, sticky=E)
entree = Entry(fen1)
entree.grid(row=0, column=1)
txt4 = Label(fen1, text='Cm en Pouce')
txt4.grid(row=1, column=0, sticky=E)
resultat = Label(fen1, text='')
resultat.grid(row=1, column=1)
bouton = Button(fen1, text='Conversion', command=t.conversion)
bouton.grid(row=2, column=0)
choix = StringVar(fen1)
choix.set('Celcius à Fahrenheit')
liste = OptionMenu(fen1, choix, 'Celcius à Fahrenheit', 'Fahrenheit à Celcius', 'Pouce en Cm', 'Cm en Pouce' ,command=changement)
liste.grid(row=2, column=1)
fen1.mainloop() Thank you
Posts: 6,809
Threads: 20
Joined: Feb 2020
Mar-31-2022, 03:58 PM
(This post was last modified: Mar-31-2022, 05:28 PM by deanhystad.)
You need to do a little cleanup. You make two entry objects, both assigned to entree. You have multiple widgets in grid(0, 0) and grid(1, 0). This is the reason for your label overlap.
You are still using classes for the purpose of satisfying a requirement for some assignment while not really making useable classes. If Temperature was a well designed class I could copy it into one of my projects and use it without having to change a bunch of code. Temperature suffers from using widgets defined outside of the class. It should not know about choix or resultat.
A good way to find a class in code is look for duplication or repetition. Things you do over and over are good candidates for functions or classes. Your code does this over and over:
if choix.get() == 'Celcius à Fahrenheit':
celcius = float(entree.get())
fahrenheit = (celcius * 1.8) + 32
resultat.configure(text=str(fahrenheit))
if choix.get() == 'Celcius à Fahrenheit':
txt1.configure(text='Température en Celcius')
txt2.configure(text='Température en Fahrenheit') This could be a class. You need an equation that does the conversion. You need a name for the conversion. You need to know the name of the input units and the output units.
class Converter:
"""Convert values from one unit to another"""
def __init__(self, dimension, inp, out, equation):
self.dimension = dimension # What kind of thing does this convert? Temperature, length, weight?
self.inp = inp # The input units
self.out = out # The output units
self.equation = equation # The equation to do the conversion
def convert(self, value):
"""Convert value from inp units to out units"""
return self.equation(value)
def __str__(self):
"""Return a pretty string that I can used to represent the conversion"""
return f"{self.dimension}: Convert {self.inp} to {self.out}" Now you can define and use conversions like this:
c2f = Converter("Temperature", "Celsius", "Fahrenheit", lambda x: 1.8 * x + 32)
print(0, c2f.inp, "=", c2f.convert(0.0), c2f.out) Output: 0 Celsius = 32.0 Fahrenheit
With a class that encapsulates the idea of unit conversion it becomes easy to write a unit converter program.
import tkinter as tk
from tkinter import ttk
from unitconverter import Converter
class ConversionWindow(tk.Tk):
"""A program for doing unit conversion"""
def __init__(self):
super().__init__()
self.converters = {}
self.title("Converter")
tk.Label(self, text="Convert") \
.grid(row=0, column=0, padx=5, pady=5, sticky="E")
self.converter = tk.StringVar(self, '')
self.converter.trace("w", self.converter_changed)
self.converter_selector = ttk.Combobox(self, textvariable=self.converter)
self.converter_selector.grid(row=0, column=1, columnspan=4, padx=5, pady=5, sticky="E")
self.inp_label = tk.Label(self)
self.inp_label.grid(row=1, column=0, padx=5, pady=5, sticky="E")
self.inp_value = tk.DoubleVar(self, 0.0)
self.inp_value.trace("w", self.input_changed)
tk.Entry(self, textvariable=self.inp_value, width=8, justify=tk.RIGHT) \
.grid(row=1, column=1, padx=5, pady=5, sticky="W")
self.out_label = tk.Label(self)
self.out_label.grid(row=1, column=2, padx=5, pady=5, sticky="E")
self.out_value = tk.StringVar()
tk.Label(self, textvariable=self.out_value, width=8) \
.grid(row=1, column=3, padx=5, pady=5, sticky="W")
def add(self, dimension, inp, out, equation):
"""Add a unit converter to the list"""
converter = Converter(dimension, inp, out, equation)
self.converters[str(converter)] = converter
self.converter_selector["values"] = list(self.converters.keys())
self.converter_selector["width"] = max(len(str(converter)) for converter in self.converters)
if len(self.converters) == 1:
# Select first converter as default
self.converter.set(str(converter))
return self
def input_changed(self, *_):
"""Called when input value changes"""
converter = self.converters[self.converter.get()]
try:
self.out_value.set(f"{converter.convert(self.inp_value.get()):.4}")
except tk.TclError:
self.out_value.set("Input Error")
def converter_changed(self, *_):
"""Called when conversion is changed"""
converter = self.converters[self.converter.get()]
self.inp_label["text"] = f"Enter {converter.inp}"
self.out_label["text"] = converter.out
self.input_changed()
ConversionWindow() \
.add("Temperature", "Celsius", "Fahrenheit", lambda x: 1.8 * x + 32) \
.add("Temperature", "Fahrenheit", "Celsius", lambda x: (x - 32) / 1.8) \
.add("Length", "Centimeter", "Inch", lambda x: x / 2.54) \
.add("Length", "Inch", "Centimeter", lambda x: x * 2.54) \
.mainloop()
Looking at the equations for the unit converter it is obvious there is a pattern.
c2f = Converter("Temperature", "Celsius", "Fahrenheit", x * 1.8 + 32)
f2c = Converter("Temperature", "Fahrenheit", "Celsius", x / 1.8 - 32/1.8)
If I know the inp->out conversion I can deduce the out->inp conversion. All I need to do is change how I represent the conversion equation. Instead of using a lambda expression I can used a fixed equation of the form: out = inp * scale + offset.
from dataclasses import dataclass
import tkinter as tk
from tkinter import ttk
@dataclass
class Converter:
"""Convert values from one unit to another"""
dimension: str
inp: str
out: str
scale: float = 1.0
offset: float = 0.0
def convert(self, value):
"""Convert value from inp units to out units"""
return value * self.scale + self.offset
def __str__(self):
"""Return a pretty string that I can used to represent the conversion"""
return f"{self.dimension}: {self.inp} to {self.out}"
class ConversionWindow(tk.Tk):
"""A program for doing unit conversion"""
def __init__(self):
super().__init__()
self.converters = {}
self.title("Converter")
# Control for selecting unit conversion
frame = tk.Frame(self)
frame.pack(padx=5, pady=5, side=tk.TOP, fill=tk.X)
tk.Label(frame, text="Convert ").pack(side=tk.LEFT)
self.converter = tk.StringVar(self, '')
self.converter.trace("w", self.converter_changed)
self.converter_selector = ttk.Combobox(frame, textvariable=self.converter)
self.converter_selector.pack(side=tk.LEFT)
# Control for entering the input value and displaying result
frame = tk.Frame(self)
frame.pack(padx=5, pady=5, side=tk.TOP, fill=tk.X)
self.inp_label = tk.Label(frame)
self.inp_label.pack(side=tk.LEFT)
self.inp_value = tk.DoubleVar(self, 0.0)
self.inp_value.trace("w", self.input_changed)
tk.Entry(frame, textvariable=self.inp_value, width=8, justify=tk.RIGHT).pack(side=tk.LEFT)
self.out_value = tk.StringVar()
tk.Label(frame, textvariable=self.out_value, width=8, anchor="e", bg='white').pack(side=tk.RIGHT)
self.out_label = tk.Label(frame)
self.out_label.pack(side=tk.RIGHT)
def add(self, dimension, inp, out, scale=1.0, offset=0.0):
"""Add a unit conversion. Add converters for inp->out and out->inp"""
self.converters.update(
Converter(dimension, inp, out, scale, offset),
Converter(dimension, out, inp, 1.0/scale, -offset/scale))
self.converter_selector["values"] = list(self.converters.keys())
self.converter_selector["width"] = max(len(str(converter)) for converter in self.converters)
self.converter.set(str(self.converters[0]))
return self
def input_changed(self, *_):
"""Called when input value changes"""
converter = self.converters[self.converter.get()]
try:
self.out_value.set(f"{converter.convert(self.inp_value.get()):.4}")
except tk.TclError:
self.out_value.set("Input Error")
def converter_changed(self, *_):
"""Called when conversion is changed"""
converter = self.converters[self.converter.get()]
self.inp_label["text"] = f"Enter {converter.inp}"
self.out_label["text"] = converter.out
self.input_changed()
ConversionWindow() \
.add("Temperature", "Celsius", "Fahrenheit", 1.8, 32) \
.add("Length", "Inch", "Centimeter", 2.54) \
.add("Length", "Foot", "Inch", 12) \
.add("Force", "Pound Force", "Newton", 4.448) \
.mainloop() I made Converter a dataclass just to start working with dataclasses. It isn't a particularly great example of dataclass, but I don't have to write an __init__() method and I get get comparison operators (lt, gt, eq) that can be used for sorting. If we had a lot of converters, sorting would organize them by dimension, inp unit, out unit..
I changed the layout management from grid to pack. This is not really a good window for using the grid layout.
Posts: 1,838
Threads: 2
Joined: Apr 2017
Mar-31-2022, 04:24 PM
(This post was last modified: Mar-31-2022, 04:33 PM by ndc85430.)
Your Temperature class would do well as a simple value object to represent the concept of temperature and nothing else. The GUI stuff is a separate concern and so should be elsewhere.
Posts: 6,809
Threads: 20
Joined: Feb 2020
Not Temperature, Quantity, a generic dimension with an associate set of units. Temperature would be an instance of Quantity and it would have a base unit of Celsius. You could add units to Temperature like Fahrenheit, Kelvin, Rankine, Newton. Each of these units would know how to convert to and from Celsius. A converter program could take this information and automatically support converting between any of these units. From 1 Quantity and 4 additional units (5 total) you get 20 unit converters.
The converter panel would have three combo boxes. One for dimension, one for input units and one for output units. There would be an entry for the input value, and some sort of display for the output value. When you enter a value it is converted from input units to base units, and that value converted to output units and displayed in the window.
Like this:
import tkinter as tk
from tkinter import ttk
font = ("", 20)
class Unit:
"""Measurement unit for a Quantity"""
def __init__(self, name, scale, offset=0):
"""
Scale and offset are values used in
y = x * scale + offset
where x is the value in base units and
y the value in my units
"""
self.name = name
self.scale = scale
self.offset = offset
def from_base(self, value):
"""Convert value from my units to base units"""
return value * self.scale + self.offset
def to_base(self, value):
"""Convert value from my units to base units"""
return (value - self.offset) / self.scale
class Quantity:
"""Things like Mass, Force, Distance, Time"""
def __init__(self, name, base_unit, units):
self.name = name
self.base_unit = base_unit
self.units = {unit.name:unit for unit in units}
def convert(self, value, src, dst):
"""Convert value from src units to dst units"""
if src != self.base_unit:
value = self.units[src].to_base(value)
if dst != self.base_unit:
value = self.units[dst].from_base(value)
return value
class ComboBox(ttk.Combobox):
"""Combobox with built in variable and set_values method."""
def __init__(self, *args, **kwargs):
self.variable = tk.StringVar()
super().__init__(*args, textvariable=self.variable, **kwargs)
def set_values(self, values):
self["values"]=values
width = max(map(len, values))
if width > self["width"]:
self["width"] = width
self.variable.set(values[0])
def value(self):
return self.variable.get()
class ConversionWindow(tk.Tk):
"""A program for doing unit conversion"""
def __init__(self):
super().__init__()
self.quantities = {}
self.title("Converter")
# Make a combobox for selecting the quantity
x = tk.Label(self, text="Convert Units")
x.pack(side=tk.TOP, padx=5, pady=5)
self.quantity_selector = ComboBox(self)
self.quantity_selector.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5)
self.quantity_selector.variable.trace("w", self.quantity_changed)
# Some frames for making a nice layout
frame = tk.Frame(self)
frame.pack(side=tk.TOP, fill=tk.BOTH)
inp_frame = tk.Frame(self)
inp_frame.pack(padx=5, pady=5, side=tk.LEFT, fill=tk.BOTH)
out_frame = tk.Frame(self)
out_frame.pack(padx=5, pady=5, side=tk.LEFT, fill=tk.BOTH)
# Make an entry for typing in the input value and a combobox for selecing
# the input units
self.inp_value = tk.DoubleVar(self, 0.0)
self.inp_value.trace("w", self.convert)
tk.Entry(inp_frame, textvariable=self.inp_value, width=8, font=font, justify=tk.RIGHT) \
.pack(side=tk.TOP, fill=tk.X)
self.inp_units = ComboBox(inp_frame)
self.inp_units.pack(side=tk.TOP, fill=tk.X)
self.inp_units.variable.trace("w", self.convert)
# Make a label for displaying the output value and a combobox for selecting
# the output units
self.out_value = tk.StringVar()
tk.Label(out_frame, textvariable=self.out_value, width=8, font=font, anchor="e", bg='white') \
.pack(side=tk.TOP, fill=tk.X)
self.out_units = ComboBox(out_frame)
self.out_units.pack(side=tk.TOP, fill=tk.X)
self.out_units.variable.trace("w", self.convert)
def add_quantity(self, quantity):
"""Add a quantity to the converter"""
self.quantities[quantity.name] = quantity
self.quantity_selector.set_values(list(self.quantities.keys()))
return self
def quantity_changed(self, *_):
"""Called when the quantity selector value changes"""
self.quantity = self.quantities[self.quantity_selector.value()]
units = [self.quantity.base_unit] + list(self.quantity.units.keys())
self.inp_units.set_values(units)
self.out_units.set_values(units)
self.inp_value.set(1.0)
def convert(self, *_):
"""Called when input value or unit selection changes"""
inp_units = self.inp_units.value()
out_units = self.out_units.value()
try:
value = self.quantity.convert(self.inp_value.get(), inp_units, out_units)
self.out_value.set(f"{value:.4f}")
except (tk.TclError, KeyError):
self.out_value.set("Input Error")
def main():
app = ConversionWindow()
app.add_quantity(Quantity("Temperature", "Celsius", [
Unit("Fahrenheit", 1.8, 32),
Unit("Kelvin", 1, 273.15),
Unit("Rankine", 1.8, 273.15/1.8),
Unit("Newton", 0.33),
Unit("Remer", 21/40, 7.5)]))
app.add_quantity(Quantity("Distance", "Meter", [
Unit("Centimeter", 100),
Unit("Millimeter", 1000),
Unit("Kilometer", 1/1000),
Unit("Inch", 39.3700787),
Unit("Foot", 3.2808399),
Unit("Yard", 1.0936133),
Unit("Mile", 0.00062137)]))
app.add_quantity(Quantity("Volume", "Liter", [
Unit("Millileter", 1000),
Unit("Cubic Centimeter", 1000),
Unit("Hogshead", 0.00419321),
Unit("Gallon", 0.26417205),
Unit("Quart", 1.05668821),
Unit("Pint", 2.11337642),
Unit("Cup", 4.22675284),
Unit("Ounce", 33.8140227)]))
app.mainloop()
if __name__ == "__main__":
main() Not positive about my math, but I think this supports 154 different unit conversions and it's not even 154 lines long! Excellent value for the keystroke.
|