# Number system converter GUI

This program converts from number system A to number system B as soon as you input something in it, as a rookie it took a lot of thinking to make it work a seamless and fool-proof way.

It converts the number from base A to base 10 and then to base B because the formulas are always the same.

It is based on this website's version: https://codebeautify.org/all-number-converter

That said i still wanted to post the code here to receive feedback on the way it's structured so i don't pick up some bad habits:

import tkinter as tk
from tkinter import ttk

class MainApplication:
numtoletter = {
0: "0",
1: "1",
2: "2",
3: "3",
4: "4",
5: "5",
6: "6",
7: "7",
8: "8",
9: "9",
10: "A",
11: "B",
12: "C",
13: "D",
14: "E",
15: "F",
16: "G",
17: "H",
18: "I",
19: "J",
20: "K",
21: "L",
22: "M",
23: "N",
24: "O",
25: "P",
26: "Q",
27: "R",
28: "S",
29: "T",
30: "U",
31: "V",
32: "W",
33: "X",
34: "Y",
35: "Z",
}

lettertonum = {
"0": 0,
"1": 1,
"2": 2,
"3": 3,
"4": 4,
"5": 5,
"6": 6,
"7": 7,
"8": 8,
"9": 9,
"A": 10,
"B": 11,
"C": 12,
"D": 13,
"E": 14,
"F": 15,
"G": 16,
"H": 17,
"I": 18,
"J": 19,
"K": 20,
"L": 21,
"M": 22,
"N": 23,
"O": 24,
"P": 25,
"Q": 26,
"R": 27,
"S": 28,
"T": 29,
"U": 30,
"V": 31,
"W": 32,
"X": 33,
"Y": 34,
"Z": 35,
}

numbercheck = {
2: "01",
3: "012",
4: "0123",
5: "01234",
6: "012345",
7: "0123456",
8: "01234567",
9: "012345678",
10: "0123456789",
11: "0123456789A",
12: "0123456789AB",
13: "0123456789ABC",
14: "0123456789ABCD",
15: "0123456789ABCDE",
16: "0123456789ABCDEF",
17: "0123456789ABCDEFG",
18: "0123456789ABCDEFGH",
19: "0123456789ABCDEFGHI",
20: "0123456789ABCDEFGHIJ",
21: "0123456789ABCDEFGHIJK",
22: "0123456789ABCDEFGHIJKL",
23: "0123456789ABCDEFGHIJKLM",
24: "0123456789ABCDEFGHIJKLMN",
25: "0123456789ABCDEFGHIJKLMNO",
26: "0123456789ABCDEFGHIJKLMNOP",
27: "0123456789ABCDEFGHIJKLMNOPQ",
28: "0123456789ABCDEFGHIJKLMNOPQR",
29: "0123456789ABCDEFGHIJKLMNOPQRS",
30: "0123456789ABCDEFGHIJKLMNOPQRST",
31: "0123456789ABCDEFGHIJKLMNOPQRSTU",
32: "0123456789ABCDEFGHIJKLMNOPQRSTUV",
33: "0123456789ABCDEFGHIJKLMNOPQRSTUVW",
34: "0123456789ABCDEFGHIJKLMNOPQRSTUVWX",
35: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXY",
36: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",
}

base = ["Base-2 (Binary)", "Base-3", "Base-4", "Base-5", "Base-6", "Base-7", "Base-8 (Octal)", "Base-9", "Base-10 (Decimal)", "Base-11", "Base-12", "Base-13", "Base-14", "Base-15", "Base-16 (Hexadecimal)", "Base-17",
"Base-18", "Base-19", "Base-20", "Base-21", "Base-22", "Base-23", "Base-24", "Base-25", "Base-26", "Base-27", "Base-28", "Base-29", "Base-30", "Base-31", "Base-32", "Base-33", "Base-34", "Base-35", "Base-36", ]

inputnum = "" # number to be converted
outputnum = "" # result of the conversion
inputbase = "" # base of the inputnum
outputbase = "" # base of the outputnum
outputnumto = 0 # where the outputnum goes
varx = 0 # to make it so the function "convert" doesn't run when the value of stringvar is changed not through user input
vary = 0 # to make it so the "convert" function doesn't run infinitely at line 233

def __init__(self, master): # setting up the window and calling functions for gui creation
self.master = master
self.master.title("Number system converter")
self.master.geometry("600x200")
self.master.resizable(False, False)

self.label_title, self.label_equals = self.create_text()
self.stringvar_number1, self.entry_number1, self.stringvar_number2, self.entry_number2 = self.create_number_input()
self.combobox_base1, self.combobox_base2 = self.create_base_selection()

def create_text(self):
label_title = tk.Label(self.master, text="NUMBER SYSTEM CONVERTER", font=(
"Calibri", 20, "bold"), anchor=tk.CENTER,)
label_title.place(x=100, y=0, width=400, height=50)

label_equals = tk.Label(self.master, text="=", font=(
"Calibri", 20), anchor=tk.CENTER, justify=tk.CENTER)
label_equals.place(x=275, y=65, width=50, height=30)

return label_title, label_equals

def create_number_input(self): # entry(s) for input and stringvars
stringvar_number1 = tk.StringVar()
stringvar_number1.set("1")
stringvar_number1.trace("w", lambda x, y, z: self.convert(2))

entry_number1 = tk.Entry(self.master, textvariable=stringvar_number1, font=(
"Calibri", 16), borderwidth=0, bg="white",)
entry_number1.place(x=50, y=65, width=225, height=30)

stringvar_number2 = tk.StringVar()
stringvar_number2.set("1")
stringvar_number2.trace("w", lambda x, y, z: self.convert(1))

entry_number2 = tk.Entry(self.master, textvariable=stringvar_number2, font=(
"Calibri", 16), borderwidth=0, bg="white",)
entry_number2.place(x=325, y=65, width=225, height=30)

return stringvar_number1, entry_number1, stringvar_number2, entry_number2

def create_base_selection(self): # combobox(s) for selecting the base
combobox_base1 = ttk.Combobox(
combobox_base1.place(x=50, y=100, width=225, height=30)

popdown_base1 = self.master.tk.call(
"ttk::combobox::PopdownWindow", combobox_base1)
self.master.tk.call(f"{popdown_base1}.f.l",
"configure", "-font", "Calibri 14")

combobox_base1.current(0)

combobox_base1.bind("<<ComboboxSelected>>",
lambda x: self.inputbase_or_outputbase())

combobox_base2 = ttk.Combobox(
combobox_base2.place(x=325, y=100, width=225, height=30)

popdown_base2 = self.master.tk.call(
"ttk::combobox::PopdownWindow", combobox_base2)
self.master.tk.call(f"{popdown_base2}.f.l",
"configure", "-font", "Calibri 14")

combobox_base2.current(0)

combobox_base2.bind("<<ComboboxSelected>>",
lambda x: self.inputbase_or_outputbase())

return combobox_base1, combobox_base2

def inputbase_or_outputbase(self): # determining if a base belongs to the input or output number
if self.outputnumto == 2:
self.inputbase = self.combobox_base1.get()[5:7]
self.outputbase = self.combobox_base2.get()[5:7]
elif self.outputnumto == 1:
self.inputbase = self.combobox_base2.get()[5:7]
self.outputbase = self.combobox_base1.get()[5:7]

if self.vary == 0:
self.convert(self.outputnumto)

def convert(self, n): # determining which entry modified last (through stringvar tracing) + the process of converting input number in output base (outputbase determined through the previous function)
if self.varx == 1:
self.varx = 0
return

self.outputnumto = n

if self.outputnumto == 2:
self.inputnum = self.entry_number1.get().upper()

elif self.outputnumto == 1:
self.inputnum = self.entry_number2.get().upper()

self.vary = 1
self.inputbase_or_outputbase()
self.vary = 0

if not self.inputbase or not self.outputbase:
return

for x in self.inputnum:
if not x in self.numbercheck[int(self.inputbase)]:
if self.outputnumto == 1:
self.varx = 1
self.stringvar_number1.set("Invalid input")
elif self.outputnumto == 2:
self.varx = 1
self.stringvar_number2.set("Invalid input")
return

base10num = self.convert_to_base10(self.inputbase, self.inputnum)
outputnum = self.convert_to_baseo(self.outputbase, base10num)

if self.outputnumto == 1:
self.varx = 1
self.stringvar_number1.set(outputnum)
elif self.outputnumto == 2:
self.varx = 1
self.stringvar_number2.set(outputnum)

def convert_to_base10(self, inputbase, inputnum): # converting inputnum to base10
inputbase = int(inputbase)
inputnum = inputnum[::-1]
multiply = 1
base10num = 0

for x in inputnum:
x = self.lettertonum[x]
base10num = (x * multiply) + base10num
multiply = multiply * inputbase

return base10num

def convert_to_baseo(self, outputbase, base10num): # converting inputnum to outputbase
outputbase = int(outputbase)
base10num = int(base10num)
outputnum = ""

while base10num > 0:
outputnum = outputnum + self.numtoletter[base10num % outputbase]
base10num = base10num // outputbase

outputnum = outputnum[::-1]

return outputnum

if __name__ == "__main__":
root = tk.Tk()
app = MainApplication(root)
root.mainloop()


In no particular order:

• numtoletter and similar variables and method names should be num_to_letter (lower_snake_case)
• numtoletter and lettertonum should not be written out like that, and should be formed via loops - which computers are good at
• numbercheck is not necessary if you listen for ValueErrors from int()
• base should also be formed via a loop
• you have a bunch of symbols initialized in static scope of your class. Moving your constants between there and global scope is debatable, but inputnum through varx should not exist at all.
• Don't call things master.
• You've accepted a parent widget parameter to your constructor - this is good. In theory it means that you could populate any widget with your converter. However, it also means that your class instance does not own the parent, and so it should not be responsible for setting title and geometry.
• Your title label is unnecessary - you've already set a title on the parent window decorator.
• You should generally avoid place and instead favour grid-like placement. This is less fragile and manual. You can still add padding, etc. if you want to.
• Some of your visual modifications such as giant font aren't all that useful. Generic UI scaling in tkinter is sort of a mess, and would still require that you modify your fonts; but my opinion is: the user has control of their window manager, and all self-respecting window managers have some kind of utility to apply zoom.
• You do not need to save references to any of your widgets - just your variables.
• You should always pass parent, name and value parameters to your tk variables on construction. This will obviate your first call to set.
• Avoid lambdas. You can just make a bound method on your class for your traces. Do not fake the names x, y and z; those are actually name, index and mode.
• When tk sets the background colour of a combo box to grey, it's trying communicate something: that the input half of the combo box is read-only, and can only be changed via dropdown. My opinion is that it's confusing to set this to white, and the default should be left.
• You used StringVar on your inputs - good! You should do the same with your combo boxes! This will obviate your call to current().
• Don't call bind at all. This whole machinery of input-or-output-base and last-known-edited-input is too complicated. You can figure out which input was just edited by which trace was called; and assuming that a change in base of the left-hand side auto-converts the right-hand value is intuitive enough that I don't find it useful to do anything more complex.
• The n parameter to convert is only ever one of two values, so I recommend changing it to a forward: bool. Or, as I've shown, just accept a source and destination group object that holds references to your number and base variables.
• trace() is deprecated and should be trace_add().

## Suggested

from string import ascii_uppercase
import tkinter as tk
from tkinter import ttk
from typing import Tuple, Iterator, Dict, List, Callable

def base_pairs() -> Iterator[Tuple[str, int]]:
for base in range(2, 37):
desc = f'Base-{base}'
name = BASE_NAMES.get(base)
if name is not None:
desc += f' ({name})'
yield desc, base

BASE_NAMES = {
2: 'Binary',
8: 'Octal',
10: 'Decimal',
}
BASES: Dict[str, int] = dict(base_pairs())
DEFAULT_BASE = next(iter(BASES.keys()))
NUM_TO_LETTER: List[str] = [
*(str(x) for x in range(10)),
*ascii_uppercase,
]

def convert_to_base(output_base: int, base10_num: int) -> str:
"""converting inputnum to outputbase"""
output_num = ''

while base10_num > 0:
base10_num, remainder = divmod(base10_num, output_base)
output_num = NUM_TO_LETTER[remainder] + output_num

return output_num

class InputGroup:
def __init__(
self, parent: tk.Tk, index: int,
convert: Callable[[], None],
) -> None:
self.convert = convert

self.number_var = tk.StringVar(
parent, name=f'number{index}', value='1',
)
self.set_trace()
tk.Entry(
parent, textvariable=self.number_var,
).grid(row=0, column=2*index)

self.base_var = tk.StringVar(
parent, name=f'base{index}', value=DEFAULT_BASE,
)

names = tuple(BASES.keys())
ttk.Combobox(
textvariable=self.base_var,
).grid(row=1, column=2*index)

def set_trace(self) -> None:

def trace(self, name: str, index: str, mode: str) -> None:
self.convert()

@property
def base(self) -> int:
return BASES[self.base_var.get()]

@property
def value(self) -> int:
value = self.number_var.get().upper()
return int(value, self.base)

@value.setter
def value(self, value: int) -> None:
as_base = convert_to_base(self.base, value)
self.set_output(as_base)

def set_output(self, value: str) -> None:
self.number_var.trace_remove(mode='write', cbname=self.trace_id)
try:
self.number_var.set(value)
finally:
self.set_trace()

class MainApplication:
def __init__(self, parent: tk.Tk) -> None:
"""setting up the window and calling functions for gui creation"""
self.parent = parent

tk.Label(self.parent, text='=').grid(row=0, column=1)

self.groups = (
InputGroup(parent, index=0, convert=self.convert_forward),
InputGroup(parent, index=1, convert=self.convert_backward),
)

def convert_forward(self) -> None:
self.convert(*self.groups)

def convert_backward(self) -> None:
self.convert(*self.groups[::-1])

def convert(self, source: InputGroup, dest: InputGroup) -> None:
try:
dest.value = source.value
except ValueError:
dest.set_output('Invalid input')

def main() -> None:
root = tk.Tk()
root.title('Number system converter')
root.resizable(width=False, height=False)
MainApplication(root)
root.mainloop()

if __name__ == '__main__':
main()