How to save a tkinter canvas as an image - python

I would like to save my drawing on a tkinter canvas as an image so I can open it for later use. I currently use this save system from this post however this is not a good way for me. First I would need to add an offset and second if i set the application so only some part of the canvas is actually visible, the part where the canvas is not visible appears black when saving the image.
only part of the canvas is actually visible. If I open the saved image this is what it looks like only what was visible is actually there(the entire image was yellow before saving it).
The code of saving the image.
def save(widget(canvas), filelocation):
x=root.winfo_rootx()+widget.winfo_x() + 74
y=root.winfo_rooty()+widget.winfo_y() + 109
x1=x+widget.winfo_width()
y1=y+widget.winfo_height()
ImageGrab.grab().crop((x,y,x1,y1)).save(filelocation)
Idea
After reading from this post it explains i could recreate all the stuff i drew on the canvas. So my idea is to put all the stuff i drew on the canvas such as lines i created on an invisible layer and paste it on the image. However i dont know if this is possible(may be possible with PIL, numpy or cv2)
Code(Is minimal reproducable)
import tkinter as tk
from tkinter import colorchooser, Canvas, N
from tkinter.ttk import *
from PIL import Image, ImageTk, ImageGrab
import keyboard
def save(widget, filelocation):
x=root.winfo_rootx()+widget.winfo_x()
y=root.winfo_rooty()+widget.winfo_y()
x1=x+widget.winfo_width()
y1=y+widget.winfo_height()
ImageGrab.grab().crop((x,y,x1,y1)).save(filelocation)
def type_of(color):
type_pen = 'marker'
if type_pen == 'marker':
pencil_motion_marker(color = color)
#pixel pen
def pencil_motion_marker(color):
stage.bind('<Button-1>', get_pos_marker)
stage.bind('<B1-Motion>', lambda event, color = color: pencil_draw_marker(event, color))
def get_pos_marker(event):
global lastx, lasty
lastx, lasty = event.x, event.y
def pencil_draw_marker(event, color):
stage.create_line((lastx, lasty, event.x, event.y), width = width.get(), fill = color, capstyle = 'round')
get_pos_marker(event)
def choose_pen_color():
pencilcolor = colorchooser.askcolor(title = 'Pencil Color')
type_of(pencilcolor[1])
##
def pencil_click():
global width, opacity
Whitepencolb = Button(optionsframe, text = 'Whitepencolimg', style = 'COLBG.TButton', command = lambda m = 'White': type_of(m))
Whitepencolb.grid(row = 0, column = 0, padx = 10, pady = 1)
Redpencolb = Button(optionsframe, text = 'Redpencolimg', style = 'COLBG.TButton', command = lambda m = 'Red': type_of(m))
Redpencolb.grid(row = 1, column = 0, padx = 10, pady = 1)
Magentapencolb = Button(optionsframe, text = 'Magentapencolimg', style = 'COLBG.TButton', command = lambda m = 'Magenta': type_of(m))
Magentapencolb.grid(row = 0, column = 1, padx = 10, pady = 1)
Limegreenpencolb = Button(optionsframe, text = 'Limegreenpencolimg', style = 'COLBG.TButton', command = lambda m = 'Lime': type_of(m))
Limegreenpencolb.grid(row = 1, column = 1, padx = 10, pady = 1)
Greenpencolb = Button(optionsframe, text = 'Greenpencolimg', style = 'COLBG.TButton', command = lambda m = 'Green': type_of(m))
Greenpencolb.grid(row = 0, column = 2, padx = 10, pady = 1)
Bluepencolb = Button(optionsframe, text = 'Bluepencolimg', style = 'COLBG.TButton', command = lambda m = 'Blue': type_of(m))
Bluepencolb.grid(row = 1, column = 2, padx = 10, pady = 1)
Cyanpencolb = Button(optionsframe, text = 'Cyanpencolimg', style = 'COLBG.TButton', command = lambda m = 'Cyan': type_of(m))
Cyanpencolb.grid(row = 0, column = 3, padx = 10, pady = 1)
Yellowpencolb = Button(optionsframe, text = 'Yellowpencolimg', style = 'COLBG.TButton', command = lambda m = 'Yellow': type_of(m))
Yellowpencolb.grid(row = 1, column = 3, padx = 10, pady = 1)
Orangepencolb = Button(optionsframe, text = 'Orangepencolimg', style = 'COLBG.TButton', command = lambda m = 'Orange': type_of(m))
Orangepencolb.grid(row = 0, column = 4, padx = 10, pady = 1)
Graypencolb = Button(optionsframe, text = 'Graypencolimg', style = 'COLBG.TButton', command = lambda m = 'Gray': type_of(m))
Graypencolb.grid(row = 1, column = 4, padx = 10, pady = 1)
Blackpencolb = Button(optionsframe, text = 'Blackpencolimg', style = 'COLBG.TButton', command = lambda m = 'Black': type_of(m))
Blackpencolb.grid(row = 0, column = 5, padx = 10, pady = 1)
Createnewpencolb = Button(optionsframe, text = 'Createnewpencolimg', style = 'COLBG.TButton', command = choose_pen_color)
Createnewpencolb.grid(row = 1, column = 5, padx = 10, pady = 1)
widthlabel = Label(optionsframe, text = 'Width: ', style = 'LABELBG.TLabel')
width = Scale(optionsframe, from_ = 1, to = 20, style = 'SCALEBG.Horizontal.TScale')
widthlabel.grid(row = 0, column = 6)
width.grid(row = 0, column = 7)
width.set(20)
opacitylabel = Label(optionsframe, text = 'Opacity: ', style = 'LABELBG.TLabel')
opacity = Scale(optionsframe, from_ = 0, to = 1.0, style = 'SCALEBG.Horizontal.TScale')
opacitylabel.grid(row = 1, column = 6)
opacity.grid(row = 1, column = 7)
opacity.set(1.0)
def setup(filelocation):
global stage, img_id, optionsframe, draw
for widgets in root.winfo_children():
widgets.destroy()
root.config(bg = '#454545')
iconsframewidth = int(screen_width / 20)
frames = Style()
frames.configure('FRAMES.TFrame', background = '#2a2a2a')
sep = Style()
sep.configure('SEP.TFrame', background = '#1a1a1a')
style = Style()
style.configure('STAGE.TFrame', background = '#454545')
icon = Style()
icon.configure('ICON.TButton', background = '#2a2a2a', foreground = '#2a2a2a')
iconsframe = Frame(root, width = iconsframewidth, style = 'FRAMES.TFrame')
iconsframe.pack(side = 'left', expand = False, fill = 'y')
iconsframe.pack_propagate(0)
sep1frame = Frame(root, style = 'SEP.TFrame', width = 5)
sep1frame.pack(side = 'left', expand = False, fill = 'y')
optionsframe = Frame(root, style = 'FRAMES.TFrame', height = 100)
optionsframe.pack(side = 'top', expand = False, fill = 'x')
optionsframe.pack_propagate(0)
sep2frame = Frame(root, style = 'SEP.TFrame', height = 5)
sep2frame.pack(side = 'top', expand = False, fill = 'x')
propertyframe = Frame(root, style = 'FRAMES.TFrame', width = 150)
propertyframe.pack(side = 'right', expand = False, fill = 'y')
propertyframe.pack_propagate(0)
sep3frame = Frame(root, style = 'SEP.TFrame', width = 5)
sep3frame.pack(side = 'right', expand = False, fill = 'y')
stageframe = Frame(root, style = 'STAGE.TFrame')
stageframe.pack(side = 'top', expand = True, fill = 'both')
stageframe.pack_propagate(0)
image = Image.open(filelocation)
width, height = image.size
stage = Canvas(stageframe, width = width, height = height)
stage.pack(side="top", anchor = 'c', expand=True)
root.update()
keyboard.add_hotkey("ctrl+s", lambda widget = stage, filelocation = filelocation: save(widget, filelocation))
pencilbutton = Button(iconsframe, text = 'pencilimg', command = pencil_click, style = 'ICON.TButton')
pencilbutton.pack(anchor = N, pady = 10)
imgtk = ImageTk.PhotoImage(Image.open(filelocation))
img_id = stage.create_image(stage.winfo_width() / 2, stage.winfo_height() / 2, image = imgtk)
stage.image = imgtk
root = tk.Tk()
root.title('App')
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
w = 1150
h = 600
x = (screen_width / 2) - (w / 2)
y = (screen_height / 2) - (h / 2)
root.geometry('%dx%d+%d+%d' % (w, h, x, y))
root.minsize(1150, 600)
setup('Test.png')
root.mainloop()
Image
Small Problem
Replying to #Claudio : I am using the screenshot technique for saving Canvas as an image to a file right now. I noticed that the saved canvas image looks like this at the corner and after saving and reopening the image it looks like this ( the border of the canvas increases the size of the canvas image ).
Update 2. June 2022: Small Problem Solved by the updated code provided in the accepted answer.

How to save a tkinter Canvas graphics as an image?
It seems to be a fact that tkinter doesn't provide a direct method
allowing to get an image of Canvas graphics for saving it to an image
file. There are two ways around this problem requiring solely an import
of the Python PIL module (Pillow).
One of this ways is to perform a screenshot of painting on the
Canvas area which can be done using PIL.ImageGrab.grab() or any other
of the various methods for performing (cropped) screenshots and saving
them to an image file
( see e.g. Fast screenshot of a small part of the screen in Python
for a Python screenshot module fast enough to allow to make a video of
the progressing painting on the Canvas ).
Another way is to paint on a Python PIL image updating the tkinter Canvas
with the modified PIL image saving it then to a file using the .save()
method available for saving PIL image objects.
The code provided in the question works generally as expected if save()
uses both the Frame (stageframe) and the Canvas (stage) widgets
required for getting the right x,y values for cropping of the screenshot
in case the Canvas is placed within a Frame AND if the bounding box for cropping
the screenshot takes into account that tkinter Canvas widget size
includes a Canvas border and a Canvas highlight-border.
The code below is the in the question provided code with some added
comments and appropriate modifications. It doesn't require the keyboard
module and saves the by painting modified Canvas as image file by
clicking on the most-left upper pencilbutton handled by the
pencil_click() function.
It provides both methods for saving the graphics of the tkinter Canvas
to an image file. Select one of them by assigning appropriate value to
the global method variable ( method = 'screenshot' or
method = 'imagepaint' ):
# https://stackoverflow.com/questions/72459847/how-to-save-a-tkinter-canvas-as-an-image
from tkinter import Tk, colorchooser, Canvas, N, PhotoImage
from tkinter.ttk import Style, Frame, Button, Label, Scale
from PIL import Image, ImageTk # required to load images in tkinter
# method = 'screenshot' or 'imagepaint'
method = 'screenshot'
borderthickness_bd = 2
highlightthickness = 1
if method == 'imagepaint':
from PIL import ImageDraw # required to draw on the image
if method == 'screenshot':
from PIL import ImageGrab # required for the screenshot
filelocation = 'Test.png'
savelocation = 'Test_.png'
def save(stageframe, stage, savelocation):
if method == 'imagepaint':
global image
image.save(savelocation)
if method == 'screenshot':
global borderthickness_bd, highlightthickness
brdt = borderthickness_bd + highlightthickness
# +1 and -2 because of thicknesses of Canvas borders (bd-border and highlight-border):
x=root.winfo_rootx()+stageframe.winfo_x()+stage.winfo_x() +1*brdt
y=root.winfo_rooty()+stageframe.winfo_y()+stage.winfo_y() +1*brdt
x1=x+stage.winfo_width() -2*brdt
y1=y+stage.winfo_height()-2*brdt
ImageGrab.grab().crop((x,y,x1,y1)).save(savelocation)
def type_of(color):
type_pen = 'marker'
if type_pen == 'marker':
pencil_motion_marker(color = color)
#pixel pen
def pencil_motion_marker(color):
stage.bind('<Button-1>' , get_pos_marker)
stage.bind('<B1-Motion>', lambda event, color = color: pencil_draw_marker(event, color))
def get_pos_marker(event):
global lastx, lasty
lastx, lasty = event.x, event.y
def pencil_draw_marker(event, color):
global method, lastx, lasty, draw, image, img_id
# print( (lastx, lasty, event.x, event.y), color, int(width.get()) )
if method == 'screenshot':
stage.create_line((lastx, lasty, event.x, event.y), width = width.get(), fill = color, capstyle = 'round')
get_pos_marker(event)
if method == 'imagepaint':
w12 = int(width.get()/2)
draw.ellipse( (event.x-w12, event.y-w12, event.x+w12, event.y+w12), fill=color )
imgtk = ImageTk.PhotoImage(image)
stage.itemconfig(img_id, image=imgtk)
stage.image = imgtk
def choose_pen_color():
pencilcolor = colorchooser.askcolor(title = 'Pencil Color')
type_of(pencilcolor[1])
##
def pencil_click():
global width, opacity, stageframe, stage, savelocation
# imgToSave = stage.image # gives a PhotoImage object
# imgToSave._PhotoImage__photo.write("Test.gif", format='gif') # which can be saved, but ...
# ^--- ... with no painting done on Canvas - only the image.
save(stageframe, stage, savelocation)
Whitepencolb = Button(optionsframe, text = 'Whitepencolimg', style = 'COLBG.TButton', command = lambda m = 'White': type_of(m))
Whitepencolb.grid(row = 0, column = 0, padx = 10, pady = 1)
Redpencolb = Button(optionsframe, text = 'Redpencolimg', style = 'COLBG.TButton', command = lambda m = 'Red': type_of(m))
Redpencolb.grid(row = 1, column = 0, padx = 10, pady = 1)
Magentapencolb = Button(optionsframe, text = 'Magentapencolimg', style = 'COLBG.TButton', command = lambda m = 'Magenta': type_of(m))
Magentapencolb.grid(row = 0, column = 1, padx = 10, pady = 1)
Limegreenpencolb = Button(optionsframe, text = 'Limegreenpencolimg', style = 'COLBG.TButton', command = lambda m = 'Lime': type_of(m))
Limegreenpencolb.grid(row = 1, column = 1, padx = 10, pady = 1)
Greenpencolb = Button(optionsframe, text = 'Greenpencolimg', style = 'COLBG.TButton', command = lambda m = 'Green': type_of(m))
Greenpencolb.grid(row = 0, column = 2, padx = 10, pady = 1)
Bluepencolb = Button(optionsframe, text = 'Bluepencolimg', style = 'COLBG.TButton', command = lambda m = 'Blue': type_of(m))
Bluepencolb.grid(row = 1, column = 2, padx = 10, pady = 1)
Cyanpencolb = Button(optionsframe, text = 'Cyanpencolimg', style = 'COLBG.TButton', command = lambda m = 'Cyan': type_of(m))
Cyanpencolb.grid(row = 0, column = 3, padx = 10, pady = 1)
Yellowpencolb = Button(optionsframe, text = 'Yellowpencolimg', style = 'COLBG.TButton', command = lambda m = 'Yellow': type_of(m))
Yellowpencolb.grid(row = 1, column = 3, padx = 10, pady = 1)
Orangepencolb = Button(optionsframe, text = 'Orangepencolimg', style = 'COLBG.TButton', command = lambda m = 'Orange': type_of(m))
Orangepencolb.grid(row = 0, column = 4, padx = 10, pady = 1)
Graypencolb = Button(optionsframe, text = 'Graypencolimg', style = 'COLBG.TButton', command = lambda m = 'Gray': type_of(m))
Graypencolb.grid(row = 1, column = 4, padx = 10, pady = 1)
Blackpencolb = Button(optionsframe, text = 'Blackpencolimg', style = 'COLBG.TButton', command = lambda m = 'Black': type_of(m))
Blackpencolb.grid(row = 0, column = 5, padx = 10, pady = 1)
Createnewpencolb = Button(optionsframe, text = 'Createnewpencolimg', style = 'COLBG.TButton', command = choose_pen_color)
Createnewpencolb.grid(row = 1, column = 5, padx = 10, pady = 1)
widthlabel = Label(optionsframe, text = 'Width: ', style = 'LABELBG.TLabel')
width = Scale(optionsframe, from_ = 1, to = 100, style = 'SCALEBG.Horizontal.TScale')
widthlabel.grid(row = 0, column = 6)
width.grid(row = 0, column = 7)
width.set(20)
opacitylabel = Label(optionsframe, text = 'Opacity: ', style = 'LABELBG.TLabel')
opacity = Scale(optionsframe, from_ = 0, to = 1.0, style = 'SCALEBG.Horizontal.TScale')
opacitylabel.grid(row = 1, column = 6)
opacity.grid(row = 1, column = 7)
opacity.set(1.0)
def setup(filelocation):
global stage, stageframe, img_id, optionsframe, draw, image, img_id, method
global borderthickness_bd, highlightthickness
for widgets in root.winfo_children():
widgets.destroy()
root.config(bg = '#454545')
iconsframewidth = int(screen_width / 20)
frames = Style()
frames.configure('FRAMES.TFrame', background = '#2a2a2a')
sep = Style()
sep.configure('SEP.TFrame', background = '#1a1a1a')
style = Style()
style.configure('STAGE.TFrame', background = '#454545')
icon = Style()
icon.configure('ICON.TButton', background = '#2a2a2a', foreground = '#2a2a2a')
iconsframe = Frame(root, width = iconsframewidth, style = 'FRAMES.TFrame')
iconsframe.pack(side = 'left', expand = False, fill = 'y')
iconsframe.pack_propagate(0)
sep1frame = Frame(root, style = 'SEP.TFrame', width = 5)
sep1frame.pack(side = 'left', expand = False, fill = 'y')
optionsframe = Frame(root, style = 'FRAMES.TFrame', height = 100)
optionsframe.pack(side = 'top', expand = False, fill = 'x')
optionsframe.pack_propagate(0)
sep2frame = Frame(root, style = 'SEP.TFrame', height = 5)
sep2frame.pack(side = 'top', expand = False, fill = 'x')
propertyframe = Frame(root, style = 'FRAMES.TFrame', width = 150)
propertyframe.pack(side = 'right', expand = False, fill = 'y')
propertyframe.pack_propagate(0)
sep3frame = Frame(root, style = 'SEP.TFrame', width = 5)
sep3frame.pack(side = 'right', expand = False, fill = 'y')
stageframe = Frame(root, style = 'STAGE.TFrame')
stageframe.pack(side = 'top', expand = True, fill = 'both')
stageframe.pack_propagate(0)
image = Image.open(filelocation)
width, height = image.size
if method == 'imagepaint':
draw = ImageDraw.Draw(image)
imgtk = ImageTk.PhotoImage(image)
# width, height = imgtk._PhotoImage__size
# imgtk = PhotoImage(filelocation)
# ^--- no width, hight information ???
stage = Canvas(stageframe, width = width, height = height, bd=borderthickness_bd, highlightthickness=highlightthickness) # default: bd=2, highlightthickness=1
stage.pack(side="top", anchor = 'c', expand=True)
root.update()
# keyboard.add_hotkey("ctrl+s", lambda widget = stageframe, filelocation = filelocation: save(widget, filelocation))
pencilbutton = Button(iconsframe, text = 'pencilimg', command = pencil_click, style = 'ICON.TButton')
pencilbutton.pack(anchor = N, pady = 10)
img_id = stage.create_image(stage.winfo_width() / 2, stage.winfo_height() / 2, image = imgtk)
stage.image = imgtk
root = Tk()
root.title('App')
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
w = 1150
h = 600
x = (screen_width / 2) - (w / 2)
y = (screen_height / 2) - (h / 2)
root.geometry('%dx%d+%d+%d' % (w, h, x, y))
root.minsize(1150, 600)
setup(filelocation)
root.mainloop()
Cropping a screenshot as a way of saving the graphics of tkinter Canvas
is to be preferred over painting on a PIL image updating the tkinter
Canvas because the latter has the side effect of slowing graphics
down so painting smoothness suffer.
To see how to change the look of a button in tkinter (to change
after the first click the pencilbutton to a savebutton), check out
Python tkinter: error _tkinter.TclError: bad window path name ".!button2" for how it can be done.

Related

Tkinter - "Label" object is not callable ; NOT a case of naming an object after LABEL class [duplicate]

This question already has answers here:
I'm getting a TypeError. How do I fix it?
(2 answers)
TypeError: 'function' object is not subscriptable - Python
(4 answers)
Closed 6 months ago.
I'm working on a tkinter GUI API-based application.
I'm attempting to modularize my code in which each function has a specific task to do and some functions just call other functions.
In my case, I bind the search button to the function, search, whose job is to call other API-based functions, such as full_name.
When I did this, an error is thrown, saying that in search full_name() TypeError: 'Label' object is not callable
what's interesting is that
I see no mention of creating instances of Label classes in full_name()
when I bind the search button to function, full_name(), it works just fine. However, I want to bind it to a function that will call all API-based functions, and so far, it's not working
Here's the code below:
#MER for frames placement through .Grid()
#import necessary modules
from tkinter import *
from tkinter import ttk
from PIL import ImageTk, Image
from tkinter import messagebox
import requests
import json
root = Tk()
root.title("F1 Desktop Application")
root.geometry("500x600")
root.configure(bg="white")
#generate 2022 drivers-list [can scale to drivers-list by changing the]
drivers_list_request = requests.get("http://ergast.com/api/f1/2022/drivers.json")
#initialize empty-list
drivers_list = []
drivers_list_object = json.loads(drivers_list_request.content)
for elements in drivers_list_object["MRData"]["DriverTable"]["Drivers"]:
drivers_list.append(elements["givenName"] + " " + elements["familyName"])
#NEED TO UNDERSTAND PRECEDENCE BETWEEN WIDTH, HEIGHT SETTING, VS SPANS. STICKY MAKES PERFECT SENSE
top_frame = LabelFrame(root, width = 300, height = 125)
top_frame.grid(row = 0, column = 0, columnspan = 2)
top_frame.rowconfigure(0, minsize = 75)
top_frame.columnconfigure(0, minsize = 300)
# Update the Entry widget with the selected item in list
def check(e):
v= entry_box.get()
if v=='':
hide_button(menu)
else:
data=[]
for item in drivers_list:
if v.lower() in item.lower():
data.append(item)
update(data)
show_button(menu)
def update(data):
# Clear the Combobox
menu.delete(0, END)
# Add values to the combobox
for value in data:
menu.insert(END,value)
def fillout(event):
try:
entry_box.delete(0,END)
entry_box.insert(0,menu.get(menu.curselection()))
hide_button(menu)
#handle a complete deletion of entry-box via cursor double tap
except:
pass
def hide_button(widget):
widget.grid_remove()
def show_button(widget):
widget.grid()
def full_name():
user_input = entry_box.get()
lower_user_input = user_input.lower().split(" ")[1]
response = requests.get("http://ergast.com/api/f1/drivers/{}.json".format(lower_user_input))
print(response.status_code)
response_object = json.loads(response.content)
name = response_object["MRData"]["DriverTable"]["Drivers"][0]["givenName"] + " " + response_object["MRData"]["DriverTable"]["Drivers"][0]["familyName"]
print(name)
def search():
full_name()
#once works, then make API request
header_label = Label(top_frame, text = "F1 2022 Drivers App", font = ("Arial bold",14), bg = "white")
header_label.grid(row = 0, column = 0, padx = 30)
search_button = Button(top_frame, text = "search" , command = search)
search_button.grid(row = 1, column = 1)
entry_box= Entry(top_frame)
entry_box.grid(row = 1, column = 0)
entry_box.bind('<KeyRelease>',check)
menu= Listbox(top_frame, height = 4)
menu.grid(row = 2, column = 0)
menu.bind("<<ListboxSelect>>",fillout)
left_frame = LabelFrame(root, width = 250, height = 225, borderwidth = 0, highlightthickness = 2)
left_frame.grid(row = 1, column = 0, sticky = NW, padx = 10, pady = 15)
#left_frame.grid_propagate(False)
left_frame.rowconfigure(0, minsize = 100)
left_frame.columnconfigure(0, minsize = 200)
#left_frame_content = LabelFrame(, width = 125, height = 70)
#left_frame_content.grid(row = 1, column = 1, sticky = W)
basic_info = Label(left_frame, text = "Basic Info ", font = ("Arial bold",14))
basic_info.grid(row = 0, column = 0)
full_name = Label(left_frame, text = "Full Name :")
full_name.grid(row = 1, column = 0, sticky = W)
driver_code = Label(left_frame, text = "Driver Code : ")
driver_code.grid(row = 2, column = 0, sticky = W)
nationality = Label(left_frame, text = "Nationality : ")
nationality.grid(row = 3, column = 0, sticky = W)
bottom_left_frame = LabelFrame(root, width = 250, height = 225)
bottom_left_frame.grid(row = 2, column = 0, sticky = N, padx = 10)
bottom_left_frame.rowconfigure(0, minsize = 50)
bottom_left_frame.columnconfigure(0, minsize = 200)
F1_career = Label(bottom_left_frame, text = "F1 Career ", font = ("Arial bold",14))
F1_career.grid(row = 0, column = 0)
wins = Label(bottom_left_frame, text = "Wins :")
wins.grid(row = 1, column = 0, sticky = W)
wins.configure(text = "Wins :" + " 7")
poles = Label(bottom_left_frame, text = "Poles :")
poles.grid(row = 2, column = 0, sticky = W)
test = Label(bottom_left_frame, text = "test")
test.grid(row = 2, column = 1)
podiums = Label(bottom_left_frame, text = "Podiums :")
podiums.grid(row = 3, column = 0, sticky = W)
podiums.configure(text = "Podiums :" + "Lewis Hamilton")
drivers_championships = Label(bottom_left_frame, text = "Championships :")
drivers_championships.grid(row = 4, column = 0, sticky = W)
bottom_right_frame = LabelFrame(root, width = 250, height = 225)
bottom_right_frame.grid(row = 2, column = 1, sticky = W)
bottom_right_frame.rowconfigure(0, minsize = 50)
bottom_right_frame.columnconfigure(0, minsize = 150)
hide_button(menu)
root.mainloop()
Feel free to ask for more clarity. I would be grateful if anyone has a reason/solution
You have used the same name for a label:
full_name = Label(left_frame, text = "Full Name :")
So it will override the full_name().
Use another name for the label, for example full_name_label:
full_name_label = Label(left_frame, text = "Full Name :")
full_name_label.grid(row = 1, column = 0, sticky = W)

Cannot create 2 treeview in same window/ Tkinter

when I tried to create 2 treeview inside same window in tkinter , i got an error like this
File "D:\Anaconda Navigator\lib\tkinter\ttk.py", line 1240, in column
return _val_or_dict(self.tk, kw, self._w, "column", column) File
"D:\Anaconda Navigator\lib\tkinter\ttk.py", line 298, in _val_or_dict
res = tk.call(*(args + options)) _tkinter.TclError: Invalid column
index PID.
The reason i got this error is that because I have created another treeview above and the one that i am creating later has different column names and spyder cannot differentiate them.
Thanks for your help.
The error i got is this:
my code is
class swindow:
def __init__(self, window):
#Settings For Supplier Window
self.window = window
self.window.title('TEDARİKÇİLER')
self.window.state('zoomed')
self.window.protocol("WM_DELETE_WINDOW", on_closing)
#Frames for treeview and data entry
self.view = Frame(self.window)
self.viewr =Frame(self.window,bg = 'red')
self.entry= Frame(self.window)
self.rentry = Frame(self.window)
#Labels
self.sheading = Label(self.entry , text = 'Tedarikçi Girişi', font = ('Verdana',17,'bold'),fg = 'blue')
self.sname = Label(self.entry,text = 'Tedarikçi Adı: ', font=('Verdana',12),width =18)
self.sphone = Label(self.entry, text = 'Tedarikçi Tel: ', font=('Verdana',12),width = 18)
self.smail= Label(self.entry, text = 'Tedarikçi Mail Adresi: ', font=('Verdana',12),width=18)
self.sofficer = Label(self.entry, text = 'İlgili Adı: ',font = ('Verdana',12), width =18)
self.headingr = Label(self.rentry,text = 'Tedarik Edilmiş Parça Girişi',font = ('Verdana',17,'bold'),fg = 'red')
self.snamer = Label(self.rentry,text = 'Tedarikçi Adı: ',font = ('Verdana',12), width =18)
self.pnamer = Label(self.rentry,text = 'Parça Adı: ',font = ('Verdana',12), width =18)
self.pbrandr = Label(self.rentry,text = 'Parça Markası: ',font = ('Verdana',12), width =18)
self.quantity = Label(self.rentry,text = 'Parça Adet: ' ,font = ('Verdana',12), width =18)
self.pricer = Label(self.rentry,text = 'Parça Fiyat: ',font = ('Verdana',12), width =18)
#EntryBoxes
self.nameentry = Entry(self.entry , width=20 , font = ('Verdana',12))
self.phoneentry = Entry(self.entry , width=20 , font = ('Verdana',12))
self.mailentry = Entry(self.entry , width=20 , font = ('Verdana',12))
self.officerentry = Entry(self.entry , width=20 , font = ('Verdana',12))
#ComboBoxes and EntryBoxes for Part-Supplier Relationship
self.scombo = ttk.Combobox(self.rentry, width = 20, font = ('Verdana',12))
self.pcombo = ttk.Combobox(self.rentry, width = 20, font = ('Verdana',12))
self.bcombo = ttk.Combobox(self.rentry, width = 20, font = ('Verdana',12))
self.quantityentry = Entry(self.rentry,width = 22,font=('Verdana',12))
self.priceentry = Entry(self.rentry,width = 22,font=('Verdana',12))
#Buttons
self.backbutton = Button(self.window,text='Ana Pencere',font=('Verdana',14), command = self.goback)
self.add = Button(self.entry , text = 'Tedarikçi Ekle',font= ('Verdana',12), bg='chartreuse1',command = self.add_supplier)
self.update = Button(self.entry , text = 'Tedarikçi Güncelle', font= ('Verdana',12),bg='yellow',command = self.update_supplier)
self.delete = Button(self.entry ,text= 'Tedarikçi Sil', font= ('Verdana', 12),bg='red',command = self.delete_supplier)
self.addr = Button(self.rentry,text = 'Tedarikçi-Parça Ekle',font= ('Verdana',12), bg='chartreuse1',command = self.addr)
self.updater = Button(self.rentry,text = 'Tedarikçi-Parça Güncelle', font= ('Verdana',12),bg='yellow',command = self.updater)
#ScrollBar For Treeview
self.scroll = Scrollbar(self.view)
self.yscrollr = Scrollbar(self.viewr)
#Treeview for Supplier Table
self.tree = ttk.Treeview(self.view, yscrollcommand= self.scroll.set, selectmode = 'browse')
self.tree['columns'] = ('SID','SuppName', 'PartName','PartNo','Part Price')
self.headings=['sid','Tedarikçi Adı','Parça Adı','Parça No','Parça Fiyat']
self.tree.column('#0', width= 0, stretch= NO)
for i,j in zip(self.tree['columns'], self.headings):
self.tree.column(i,anchor= CENTER,width= 150, minwidth= 150)
self.tree.heading(i,text = j, anchor = CENTER)
self.tree['displaycolumns'] = ('SuppName', 'PartName','PartNo','Part Price')
#Treeview for Supplier-Part Relation
self.treer = ttk.Treeview(self.viewr , yscrollcommand = self.yscrollr.set, selectmode = 'browse')
self.treer['columns'] = ('SID','PID','Sname','Pname','Pcategory','Squantity','Sdate','Sprice','Pbrand','Ptype')
self.headingsr = ['sid','pid','Tedarikçi Adı','Parça Adı','Parça Kategorisi','Tedarik Edilen Adet','Tedarik Tarihi','Tedarik Fiyatı','Parça Markası','Parça Hammaddesi']
self.treer.column('#0', width = 0, stretch = NO)
for i,j in zip(self.treer['columns'],self.headingsr):
self.tree.column(i,anchor= CENTER,width= 150, minwidth= 150)
self.tree.heading(i,text = j, anchor = CENTER)
self.treer['displaycolumns'] = ('Sname','Pname','Pcategory','Pbrand','Ptype','Squantity','Sdate','Sprice')
#Binding Treeview to ScrollBar
self.tree.bind('<Double-1>', self.selector)
self.scroll.config(command = self.tree.yview)
#Placement of Frames
self.entry.place_configure(x=45,y=30,height= 293, width=475)
self.view.place_configure(width=950,height =256,x=530,y=55)
self.viewr.place_configure(width =950,height=350,x = 530, y=362)
self.rentry.place(x=45 , y = 350, height = 410, width =475)
#Placement of Labels
self.sheading.pack(fill = X)
self.sname.place(x=15 , y=45)
self.sphone.place(x=15 , y=100)
self.smail.place(x=15 , y=155)
self.sofficer.place(x=15 , y=210)
self.headingr.place(x= 49,y=0)
self.snamer.place(x= 5,y=45)
self.pnamer.place(x = 5, y = 100)
self.pbrandr.place(x = 5, y = 155)
self.quantity.place(x = 5, y = 210)
self.pricer.place(x = 5, y = 265)
#Placement of ComboBoxes
self.scombo.place(x =200 , y =45 )
self.pcombo.place(x =200 , y =100 )
self.bcombo.place(x = 200, y = 155)
#Placement of EntryBoxes
self.nameentry.place(x=210, y=45)
self.phoneentry.place(x=210 , y=100)
self.mailentry.place(x=210 , y=155)
self.officerentry.place(x=210, y=210)
self.quantityentry.place(x =200 , y =210 )
self.priceentry.place(x = 200, y = 265)
#Placement of Buttons
self.backbutton.place(x=650,y= 0)
self.add.place(x=15,y=250)
self.update.place(x=150,y=250)
self.delete.place(x=325,y=250)
self.addr.place(x = 0,y = 305 )
self.updater.place(x = 210 ,y =305 )
#Placement of Treeview
self.tree.place(x=0, y=40,height =700, width=933)
self.treer.place(x=0,y=40,height = 700,width=933)
#Placement of ScrollBar
self.scroll.pack(side = RIGHT, fill = Y)
self.window.mainloop()
You used self.tree in the for loop:
for i,j in zip(self.treer['columns'],self.headingsr):
self.tree.column(i,anchor= CENTER,width= 150, minwidth= 150)
self.tree.heading(i,text = j, anchor = CENTER)
But it should be self.treer instead:
for i,j in zip(self.treer['columns'],self.headingsr):
# should use self.treer instead of self.tree
self.treer.column(i,anchor= CENTER,width= 150, minwidth= 150)
self.treer.heading(i,text = j, anchor = CENTER)
Suggest to go through your code and make sure you use the correct instance variable: self.tree or self.treer.

Take hex code/rgb and display the color in tkinter

Does anybody know a way to take an rgb or hex code and display it in tkinter? I'm just learning about GUI's and such. Thanks
Here I have a simple GUI that'll tell you the RGB and Hex code from some basic colors. Don't have any idea how to do the reverse and that's what I'd like to know.
import tkinter as tk
from tkinter import *
root = tk.Tk()
def color_entry_function(color, arg=None):
color_entry_get = color_entry.get()
if color_entry_get.lower() == "blue":
blue = Label(root, text="#0000FF\nrgb(0, 0, 255)\n")
blue.pack(anchor="center")
elif color_entry_get.lower() == "red":
red = Label(root, text="#FF0000\nrgb(255,0,0)\n")
red.pack(anchor="center")
elif color_entry_get.lower() == "green":
green = Label(root, text="#0000FF\nrgb(0, 0, 255)\n")
green.pack(anchor="center")
elif color_entry_get.lower() == "yellow":
yellow = Label(root, text="#FFFF00\nrgb(255,255,0)\n")
yellow.pack(anchor="center")
Welcome = Label(root, text="Color to Hex Converter")
Welcome.pack(anchor="center")
Welcome_2 = Label(root, text="Enter the color you want to convert:\n")
Welcome_2.pack(anchor="center")
color_entry = Entry(root, justify="center", width=20)
color_entry.focus()
color_entry.bind("<Return>", color_entry_function)
color_entry.pack(anchor="center")
color_entry_linebreak = Label(root, text="")
color_entry_linebreak.pack()
root.mainloop()
Assuming it's 256 color RGB
To convert from a hexcode string to a rgb tuple:
hexcode = "#000000" # black
rgb = tuple(int(hexcode.lstrip('#')[n:n+2], 16) for n in range(0, 6, 2)) # tuple comprehension
To convert from a rgb tuple to a hexcode string:
rgb = (0, 0, 0) # black
hexcode = f"#{rgb[0]:02X}{rgb[1]:02X}{rgb[2]:02X}" # converting each value
hexcode = f"#{''.join([f'{c:02X}' for c in rgb])}" # converting all values with list comprehension
hexcode = "#" + ''.join([f"{c:02X}" for c in rgb]) # for old pythons without f-string nesting
To change colors for a tkinter.Label, use bg for background or fg for text color. You must supply the hexcode string:
import tkinter
root = tkinter.Tk()
label = tkinter.Label(root, text = "COLOR", bg = "#0000FF", fg = "#FFFF00")
label.pack()
root.mainloop()
Your program with those features added:
import tkinter as tk
from tkinter import *
color_list = [
('black', (0, 0, 0)),
('blue', (0, 0, 255)),
('red', (255, 0, 0)),
('green', (0, 0, 255)),
('yellow', (255, 255, 0)),
('white', (255, 255, 255)),
]
hex_list = [f"{n:02X}" for n in range(256)]
def name_to_hex_rgb(event):
try:
color = color_list[name_listbox.curselection()[0]]
except IndexError:
color = color_list[0]
color_name = color[0]
color_rgb = color[1]
color_hex = f"#{''.join([f'{rgb:02X}' for rgb in color_rgb])}"
color_inverse = f"""#{''.join([f'{rgb:02X}' for rgb in
[255 - color for color in color_rgb]])}"""
name_label.config(
bg = color_hex,
fg = color_inverse,
text = f"""name: {color_name}\nhex: {color_hex}\nrgb: \
{color_rgb}""",
)
def hex_to_rgb(event):
try:
color_1 = hex_listbox_1.curselection()[0]
except IndexError:
color_1 = 0
try:
color_2 = hex_listbox_2.curselection()[0]
except IndexError:
color_2 = 0
try:
color_3 = hex_listbox_3.curselection()[0]
except IndexError:
color_3 = 0
color_rgb = (
int(hex_list[color_1], 16),
int(hex_list[color_2], 16),
int(hex_list[color_3], 16),
)
color_rgb_inverse = (
255 - int(hex_list[color_1], 16),
255 - int(hex_list[color_2], 16),
255 - int(hex_list[color_3], 16),
)
color_hex = f"""#{hex_list[color_1]}\
{hex_list[color_2]}\
{hex_list[color_3]}"""
color_hex_inverse = f"""#{hex_list[255 - color_1]}\
{hex_list[255 - color_2]}\
{hex_list[255 - color_3]}"""
text = f"rgb: {color_rgb}"
color_name = [color[0] for color in color_list if color[1] == \
color_rgb]
if len(color_name) > 0:
text = f"name: {color_name[0]}\n" + text
hex_label.config(bg = color_hex, fg = color_hex_inverse,
text = text)
def rgb_to_hex(event):
try:
color_1 = rgb_listbox_1.curselection()[0]
except IndexError:
color_1 = 0
try:
color_2 = rgb_listbox_2.curselection()[0]
except IndexError:
color_2 = 0
try:
color_3 = rgb_listbox_3.curselection()[0]
except IndexError:
color_3 = 0
color_rgb = (color_1, color_2, color_3)
color_rgb_inverse = (255 - color_1, 255 - color_2, 255 - color_3)
color_hex = f"#{color_1:02X}{color_2:02X}{color_3:02X}"
color_hex_inverse = f"""#{255 - color_1:02X}{255 - color_2:02X}\
{255 - color_3:02X}"""
text = f"hex: {color_hex}"
color_name = [color[0] for color in color_list if color[1] == \
color_rgb]
if len(color_name) > 0:
text = f"name: {color_name[0]}\n" + text
rgb_label.config(bg = color_hex, fg = color_hex_inverse,
text = text)
root = tk.Tk()
root.title("Color, RGB and Hex Converter")
name_frame = Frame(root)
name_frame.pack(anchor = "nw", fill = BOTH, side = "left")
name_label = Label(name_frame, text = " ")
name_label.pack(anchor = "center")
name_title = Label(name_frame, text = "NAME to HEX & RGB")
name_title.pack(anchor = "center")
name_listbox = Listbox(
name_frame,
listvariable = Variable(value = [color[0] for color in color_list]),
justify = "center",
width = 18,
)
name_listbox.bind("<<ListboxSelect>>", name_to_hex_rgb)
name_listbox.pack()
hex_frame = Frame(root)
hex_frame.pack(anchor = "n", fill = BOTH, side = "left")
hex_label = Label(hex_frame, text = "")
hex_label.pack(anchor = "center")
hex_title = Label(hex_frame, text = "HEX to RGB")
hex_title.pack(anchor = "center")
hex_listbox_1 = Listbox(
hex_frame,
listvariable = Variable(value = hex_list),
exportselection = False,
justify = "center",
width = 6,
)
hex_listbox_1.bind("<<ListboxSelect>>", hex_to_rgb)
hex_listbox_1.pack(anchor = "nw", fill = BOTH, side = "left")
hex_listbox_2 = Listbox(
hex_frame,
listvariable = Variable(value = hex_list),
exportselection = False,
justify = "center",
width = 6,
)
hex_listbox_2.bind("<<ListboxSelect>>", hex_to_rgb)
hex_listbox_2.pack(anchor = "n", fill = BOTH, side = "left")
hex_listbox_3 = Listbox(
hex_frame,
listvariable = Variable(value = hex_list),
exportselection = False,
justify = "center",
width = 6,
)
hex_listbox_3.bind("<<ListboxSelect>>", hex_to_rgb)
hex_listbox_3.pack(anchor = "ne", fill = BOTH, side = "left")
rgb_frame = Frame(root)
rgb_frame.pack(anchor = "ne", fill = BOTH, side = "left")
rgb_label = Label(rgb_frame, text = "")
rgb_label.pack(anchor = "center")
rgb_title = Label(rgb_frame, text = "RGB to HEX")
rgb_title.pack(anchor = "center")
rgb_listbox_1 = Listbox(
rgb_frame,
listvariable = Variable(value = list(range(256))),
exportselection = False,
justify = "center",
width = 6,
)
rgb_listbox_1.bind("<<ListboxSelect>>", rgb_to_hex)
rgb_listbox_1.pack(anchor = "nw", fill = BOTH, side = "left")
rgb_listbox_2 = Listbox(
rgb_frame,
listvariable = Variable(value = list(range(256))),
exportselection = False,
justify = "center",
width = 6,
)
rgb_listbox_2.bind("<<ListboxSelect>>", rgb_to_hex)
rgb_listbox_2.pack(anchor = "n", fill = BOTH, side = "left")
rgb_listbox_3 = Listbox(
rgb_frame,
listvariable = Variable(value = list(range(256))),
exportselection = False,
justify = "center",
width = 6,
)
rgb_listbox_3.bind("<<ListboxSelect>>", rgb_to_hex)
rgb_listbox_3.pack(anchor = "ne", fill = BOTH, side = "left")
name_to_hex_rgb(None)
hex_to_rgb(None)
rgb_to_hex(None)
root.mainloop()
You can use hex codes as color specifications:
tk.Label(root, background="#FF0000")
You can use the universal widget method winfo_rgb to convert a color specification into a tuple containing the r, g, and b components:
r, g, b = root.winfo_rgb(color_entry.get())
Note: the r, g, and b values will be in the range of 0 to 65535 (16 bits per channel)

tkinter - Scrollbar will not show

I am having a developing a GUI in Python and there is no scrollbar that shows when I run it. It get an empty bar with arrows, but there is no scrollbar inside and the scrollbar does not work at all.
#Scroll Functions
def ScrollAll(event):
self.topcanvas.config(scrollregion = self.topcanvas.bbox("all"))
def on_mousewheel(event):
self.topcanvas.yview('scroll',0, 'units')
#topframe
topframe = Frame(master, background = '#D9F7FE', width = 2100, height = 1700)
topframe.place(x = 0, y = 0)
#Top canvas
self.topcanvas = Canvas(topframe, scrollregion = (0, 0, 1000, 1000))
self.topcanvas.pack(side = TOP, fill = BOTH, expand = TRUE)
self.topcanvas.config(width = 2100, height =1700, background = '#D9F7FE')
#scroll
self.vbar = ttk.Scrollbar(master, orient = 'vertical', command = self.topcanvas.yview)
#self.hbar = ttk.Scrollbar(master, orient = 'horizontal', command = self.topcanvas.xview)
#self.topcanvas.config(xscrollcommand = self.hbar.set)
self.topcanvas.config(yscrollcommand = self.vbar.set)
#self.hbar.pack(side = "bottom", fill = "x")
self.vbar.pack(side = "right", fill = "y")
self.topcanvas.pack(side="left")
self.topcanvas.bind_all("<MouseWheel>", on_mousewheel)
#frame
frame = Frame(self.topcanvas)
frame.config(width = 2100, height = 1700,background = '#D9F7FE')
self.topcanvas.create_window((0,0), window = frame, anchor = 'nw')
frame.bind("<Configure>", ScrollAll)
My scroll region needed to be larger than the actual frame it was in. I increased my scroll region and my scroll bars worked.

Python Tkinter replacing the label and command of a button by clicking another button

I'm writing a scientific calculator with 2nd button. What is the function for second button so for example it changes sin to sin^-1, plus changing the sin button command; and if you click the 2nd button again, it changes sin^-1 back to sin
I would split my calculator up into section using different frames (one to show the calculations , one with buttons which won't have 2 functions and lastly the buttons which have 2 functions).
The reason I would split it up is because I would use destroying objects and making the new ones this splitting up means you can destroy the wanted frame rather than specific buttons (would require less code). Also for this I would have 3 create GUI defs. one would be the buttons with one function and the bit showing the calculations. one be the buttons which have 2 functions (their first function) and lastly the buttons which have 2 functions (their second functions). To decide between which GUI gen def to use have an if statement with global variable which gets changed each time 2nd function button called and that decides which def to use.
If it was just commands you wanted changing instead of both labels and commands I would have a variable which is etheir 1 or 2(change when 2nd button clicked) then in your definitions (ones which your buttons call) have an if statement to decide between to do normal action (e.g cos) or 2nd action (e.g cos-1).
Here is code below that uses what i have described in the first paragraph:
from tkinter import *
class Calc(Frame):
def __init__(self, master):
self.modefunction = 1
""" Initialize the frame. """
super(Calc,self).__init__(master)
self.grid()
self.calculations_frm = Frame(self, width=100, height=30)#bg = "red"
self.calculations_frm.grid(row = 0, column = 0, columnspan=2)
self.buttons_frm = Frame(self, width= 50, height=30,)#bg = "green")
self.buttons_frm.grid(row = 1, column = 1)
self.buttons_2_functions_1_frm = Frame(self, width=50, height=30)#bg = "blue")
self.buttons_2_functions_1_frm.grid(row = 1, column = 0)
self.create_GUI()
def create_show_calculations(self):
self.calculation_lbl = Label(self.calculations_frm, text = "will show caluclations here").pack()
def create_buttons(self):
#mc stands for mode change
self.mode_change_btn = Button(self.buttons_frm, text = "mc", command = self.mode_change, height = 1, width = 5)
self.mode_change_btn.grid(row = 0,column = 0)
self.plus_btn = Button(self.buttons_frm, text = "plus", height = 1, width = 5)
self.plus_btn.grid(row = 1,column = 0)
def create_GUI(self):
self.create_show_calculations()
self.create_buttons()
self.create_1_function_gui()
def create_1_function_gui(self):
self.tan_btn = Button(self.buttons_2_functions_1_frm, text = "tan", height = 1, width = 5)
self.tan_btn.grid(row = 0,column = 0)
self.san_btn = Button(self.buttons_2_functions_1_frm, text = "san", height = 1, width = 5)
self.san_btn.grid(row = 0,column = 1)
self.coz_btn = Button(self.buttons_2_functions_1_frm, text = "coz", height = 1, width = 5)
self.coz_btn.grid(row = 1,column = 0)
def create_2_function_gui(self):
self.buttons_2_functions_2_frm = Frame(self, width=50, height=30)#bg = "blue")
self.buttons_2_functions_2_frm.grid(row = 1, column = 0)
self.inverse_tan_btn = Button(self.buttons_2_functions_2_frm, text = "tan-1", height = 1, width = 5)
self.inverse_tan_btn.grid(row = 0,column = 0)
self.inverse_san_btn = Button(self.buttons_2_functions_2_frm, text = "san-1", height = 1, width = 5)
self.inverse_san_btn.grid(row = 0,column = 1)
self.inverse_coz_btn = Button(self.buttons_2_functions_2_frm, text = "coz-1", height = 1, width = 5)
self.inverse_coz_btn.grid(row = 1,column = 0)
def mode_change(self):
if self.modefunction == 1:
self.buttons_2_functions_1_frm.destroy()
self.modefunction = 2
self.buttons_2_functions_2_frm = Frame(self, width=50, height=30)#bg = "blue")
self.buttons_2_functions_2_frm.grid(row = 1, column = 0)
self.create_2_function_gui()
else:
self.buttons_2_functions_2_frm.destroy()
self.modefunction = 1
self.buttons_2_functions_1_frm = Frame(self, width=50, height=30)#bg = "blue")
self.buttons_2_functions_1_frm.grid(row = 1, column = 0)
self.create_1_function_gui()
root = Tk()
root.title("booking system")
root.geometry("500x500")
root.configure(bg="white")
app = Calc(root)
root.mainloop()

Categories

Resources