Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Tkinter text overlap
#1
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
Reply
#2
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.
Reply
#3
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.
Reply
#4
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.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  make all text in Right to left alignment in tkinter widgets jalal0034 1 1,340 Sep-27-2022, 06:42 PM
Last Post: Larz60+
  Exercise List Overlap Damian 7 3,174 Apr-02-2021, 07:39 AM
Last Post: Damian
  Rotated Rectangle overlap using Shapely pyNew 0 1,717 Feb-25-2021, 04:54 AM
Last Post: pyNew
  how to create a tool with tkinter to convert img to text rachidel07 3 2,591 Feb-05-2021, 12:21 PM
Last Post: deanhystad
  How to order the Cronjobs to avoid overlap or conflict sadhaonnisa 1 1,840 Oct-10-2020, 10:26 AM
Last Post: DeaD_EyE
  Can OpenGL object be overlap? hsunteik 4 5,110 Jan-19-2017, 02:43 PM
Last Post: Windspar

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020