Tkinter canvas error - full
🧩 Syntax:
import customtkinter as ctk
import CTkListbox
import os
from tkinter import filedialog, messagebox, Canvas, Scrollbar, Frame
from json import dump, load
from tkcalendar import DateEntry
from PIL import Image, ImageTk
# Global variables (yeah, I know, I know...)
thumbnail_images = []
thumbnail_canvas = None
image_file_paths = [] # List to hold the thumbnail image paths
def create_new_project_window(app_instance, update_options_menu=None):
# Create a new window for a new project
new_project_window = ctk.CTk()
# Add this window to the main app's child_windows list
app_instance.child_windows.append(new_project_window)
required_tools = [] # List to hold required tools
required_supplies = [] # List to hold required supplies
def add_tool():
tool = required_tools_entry.get()
required_tools.append(tool) # Append to list
required_tools_listbox.insert("END", tool) # Insert into listbox
required_tools_entry.delete(0, 'end') # Clear entry box
def add_supply():
supply = required_supplies_entry.get()
required_supplies.append(supply) # Append to list
required_supplies_listbox.insert("END", supply) # Insert into listbox
required_supplies_entry.delete(0, 'end') # Clear entry box
def delete_tool():
selected_index = required_tools_listbox.curselection()
if isinstance(selected_index, tuple) and selected_index:
index = selected_index[0]
else:
index = selected_index
if index is not None:
required_tools.pop(index)
required_tools_listbox.delete(index)
def delete_supply():
selected_index = required_supplies_listbox.curselection()
if isinstance(selected_index, tuple) and selected_index:
index = selected_index[0]
else:
index = selected_index
if index is not None:
required_supplies.pop(index)
required_supplies_listbox.delete(index)
def save_project():
try:
project_data = {
'image_file_paths': image_file_paths,
"name": project_name_entry.get(),
"description": description_textbox.get("1.0", "end-1c"),
"cut_list": cut_list_textbox.get("1.0", "end-1c"),
"special_instructions": special_instructions_textbox.get("1.0", "end-1c"),
"required_tools": required_tools,
"required_supplies": required_supplies,
"start_date": start_date.get(),
"complete_date": completion_date.get(),
"budget": {
"project_budget": project_budget_entry.get(),
"cost_of_materials": cost_of_materials_entry.get(),
"cost_of_labor": cost_of_labor_entry.get(),
"total_cost": total_cost_entry.get()
}
}
if not project_data["name"].strip():
raise ValueError("Project name cannot be empty!")
# Determine the path to the projects folder
current_folder_path = os.path.dirname(os.path.abspath(__file__))
projects_folder_path = os.path.join(current_folder_path, 'projects')
# Get the project name from the user
project_name = project_name_entry.get()
# Create a lambda function to get the file path
file_path = (lambda: filedialog.asksaveasfilename(initialdir=projects_folder_path, initialfile=project_name, defaultextension=".json", filetypes=[("JSON files", "*.json")]))()
if file_path:
with open(file_path, 'w') as file:
dump(project_data, file)
new_project_window.destroy()
# Call the update_options_menu method from the main application
if update_options_menu:
update_options_menu()
except ValueError as e:
messagebox.showerror(title="Error", message=str(e))
def tab_order(): # Set tab order
project_name_entry.focus() # Focus starts on the project name box
widgets =[project_name_entry, description_textbox, cut_list_textbox, special_instructions_textbox, notes_textbox, customer_entry, email_entry,phone_entry, other_entry, required_tools_entry, required_tools_button, delete_tool_button, required_supplies_entry, required_supplies_button, delete_supply_button, project_budget_entry, cost_of_materials_entry, cost_of_labor_entry, total_cost_entry,start_date, completion_date, cancel_button, save_button]
for w in widgets:
w.lift()
def focus_next_window(event):
event.widget.tk_focusNext().focus()
return("break")
def open_image_file_in_viewer(file_path):
# Open the image file in the default image viewer when clicking the thumbnail
os.startfile(file_path)
def add_thumbnails(thumbnail_canvas):
# Declare global variables to keep track of thumbnail images and file paths
global thumbnail_images
global image_file_paths
# Open a file dialog box to select multiple image files
file_paths = filedialog.askopenfilenames(title="Select image files", filetypes=[("Image files", "*.jpg;*.png;*.jpeg")])
if file_paths:
# Delete existing thumbnails and file paths
thumbnail_canvas.delete("all")
thumbnail_images.clear()
image_file_paths.clear()
# Determine the canvas width and height based on the number of images
thumbnail_size = 64
spacing = 10
num_columns = thumbnail_canvas.winfo_width() // (thumbnail_size + spacing)
num_rows = (len(file_paths) + num_columns - 1) // num_columns
canvas_height = num_rows * (thumbnail_size + spacing)
thumbnail_canvas.config(scrollregion=(0, 0, thumbnail_canvas.winfo_width(), canvas_height))
# Iterate over the image files and create thumbnails
for idx, file_path in enumerate(file_paths):
row = idx // num_columns
col = idx % num_columns
x = col * (thumbnail_size + spacing) + thumbnail_size // 2
y = row * (thumbnail_size + spacing) + thumbnail_size // 2
image = Image.open(file_path)
# Create the thumbnail image
thumbnail = ImageTk.PhotoImage(image.resize((thumbnail_size, thumbnail_size)))
# Keep a reference to the thumbnail image to prevent garbage collection
thumbnail_images.append(thumbnail)
image_file_paths.append(file_path) # Keep track of the file path for this thumbnail
# Add the thumbnail image to the canvas
thumbnail_image = thumbnail_canvas.create_image(x, y, image=thumbnail, tags="thumbnail")
thumbnail_canvas.tag_bind(thumbnail_image, "<Button-1>", lambda event, path=file_path: open_image_file_in_viewer(path))
def delete_thumbnail(event):
# Get the selected image...
selected_item = thumbnail_canvas.find_withtag("current")
# ... and delete it (gotta be selected)
if selected_item:
thumbnail_canvas.delete(selected_item)
##### Display the new window #####
new_project_window = ctk.CTk()
new_project_window.title("New Project")
new_project_window.geometry("785x820")
############################# LEFT SIDE #############################
# Project Name
ctk.CTkLabel(new_project_window, text="Project Name:", corner_radius=10, text_color=("blue", "yellow")).place(x=15, y=10)
project_name_entry = ctk.CTkEntry(new_project_window, corner_radius=10, width=400)
project_name_entry.place(x=15, y=40)
# Description
ctk.CTkLabel(new_project_window, text="Description:", corner_radius=10, text_color=("blue", "yellow")).place(x=15, y=80)
description_textbox = ctk.CTkTextbox(new_project_window, width=400, height=80, corner_radius=10, wrap="word")
description_textbox.place(x=15, y=110)
description_textbox.bind("<Tab>", focus_next_window)
# Cut List
ctk.CTkLabel(new_project_window, text="Cut List:", corner_radius=10, text_color=("blue", "yellow")).place(x=15, y=200)
cut_list_textbox = ctk.CTkTextbox(new_project_window, width=400, height=80, corner_radius=10, wrap="word")
cut_list_textbox.place(x=15, y=230)
cut_list_textbox.bind("<Tab>", focus_next_window)
# Special Instructions
ctk.CTkLabel(new_project_window, text="Special Instructions:", corner_radius=10, text_color=("blue", "yellow")).place(x=15, y=320)
special_instructions_textbox = ctk.CTkTextbox(new_project_window, width=400, height=80, corner_radius=10, wrap="word")
special_instructions_textbox.place(x=15, y=350)
special_instructions_textbox.bind("<Tab>", focus_next_window)
# Notes
ctk.CTkLabel(new_project_window, text="Notes:", corner_radius=10, text_color=("blue", "yellow")).place(x=15, y=440)
notes_textbox = ctk.CTkTextbox(new_project_window, width=400, height=50, corner_radius=10, wrap="word")
notes_textbox.place(x=15, y=470)
notes_textbox.bind("<Tab>", focus_next_window)
# Customer Information
ctk.CTkLabel(new_project_window, text="Customer:", corner_radius=10, text_color=("blue", "yellow")).place(x=15, y=550)
customer_entry = ctk.CTkEntry(new_project_window, corner_radius=10, width=150)
customer_entry.place(x=15, y=580)
ctk.CTkLabel(new_project_window, text="Phone:", corner_radius=10, text_color=("blue", "yellow")).place(x=225, y=550)
phone_entry = ctk.CTkEntry(new_project_window, corner_radius=10, width=150)
phone_entry.place(x=225, y=580)
ctk.CTkLabel(new_project_window, text="Email:", corner_radius=10, text_color=("blue", "yellow")).place(x=15, y=620)
email_entry = ctk.CTkEntry(new_project_window, corner_radius=10, width=150)
email_entry.place(x=15, y=650)
ctk.CTkLabel(new_project_window, text="Other:", corner_radius=10, text_color=("blue", "yellow")).place(x=225, y=620)
other_entry = ctk.CTkEntry(new_project_window, corner_radius=10, width=150)
other_entry.place(x=225, y=650)
############################# IMAGE CANVAS #############################
# Add Image button
thumbnail_canvas = Canvas(new_project_window, width=580, height=100, background="black")
thumbnail_canvas.place(x=185, y=700)
thumbnail_canvas.tag_bind("thumbnail", "<Button-1>", delete_thumbnail)
scrollbar = Scrollbar(new_project_window, orient="horizontal", command=thumbnail_canvas.xview)
thumbnail_canvas.configure(xscrollcommand=scrollbar.set)
thumbnail_canvas.config(scrollregion=(0,0,1000,100))
add_image_button = ctk.CTkButton(new_project_window, text="Add Image", corner_radius=10, command=lambda: add_thumbnails(thumbnail_canvas), text_color=("blue", "yellow"), width=150)
add_image_button.place(x=15, y=740)
# Delete Image button
delete_image_button = ctk.CTkButton(new_project_window, text="Delete Image", corner_radius=10, command=delete_thumbnail, text_color=("blue", "yellow"), width=150)
delete_image_button.place(x=15, y=775)
# Create the canvas for the image thumbnails
# Bind the click event on the canvas items so we can delete the image
# Scrollbar
# Link the canvas and scrollbar. This is quite important and will drive you nuts for a while if you leave this part out...
############################# RIGHT SIDE #############################
# Required Tools
ctk.CTkLabel(new_project_window, text="Required Tools:", corner_radius=10, text_color=("blue", "yellow")).place(x=450, y=10)
required_tools_listbox = CTkListbox.CTkListbox(new_project_window, width=120, height=100, corner_radius=10)
required_tools_listbox.place(x=450, y=40)
required_tools_entry = ctk.CTkEntry(new_project_window, corner_radius=10, width=150)
required_tools_entry.place(x=450, y=275)
required_tools_button = ctk.CTkButton(new_project_window, text="Add Tool", corner_radius=10, command=add_tool, text_color=("blue", "yellow"), width=150)
required_tools_button.place(x=450, y=315)
delete_tool_button = ctk.CTkButton(new_project_window, text="Delete Tool", command=delete_tool, text_color=("blue", "yellow"), width=150, fg_color="#FF6666", hover_color="#FF3333", corner_radius=10)
delete_tool_button.place(x=450, y=345)
# Required Supplies
ctk.CTkLabel(new_project_window, text="Required Supplies:", corner_radius=10, text_color=("blue", "yellow")).place(x=620, y=10)
required_supplies_listbox = CTkListbox.CTkListbox(new_project_window, width=120, height=100, corner_radius=10)
required_supplies_listbox.place(x=620, y=40)
required_supplies_entry = ctk.CTkEntry(new_project_window, corner_radius=10, width=150)
required_supplies_entry.place(x=620, y=275)
required_supplies_button = ctk.CTkButton(new_project_window, text="Add Supply", corner_radius=10, command=add_supply, text_color=("blue", "yellow"), width=150)
required_supplies_button.place(x=620, y=315)
delete_supply_button = ctk.CTkButton(new_project_window, text="Delete Supply", command=delete_supply, text_color=("blue", "yellow"), width=150, fg_color="#FF6666", hover_color="#FF3333", corner_radius=10)
delete_supply_button.place(x=620, y=345)
# Budgeting
ctk.CTkLabel(new_project_window, text="Project Budget:", corner_radius=10, text_color=("blue", "yellow")).place(x=450, y=400)
project_budget_entry = ctk.CTkEntry(new_project_window, corner_radius=10, width=150)
project_budget_entry.place(x=450, y=430)
ctk.CTkLabel(new_project_window, text="Materials Cost:", corner_radius=10, text_color=("blue", "yellow")).place(x=450, y=470)
cost_of_materials_entry = ctk.CTkEntry(new_project_window, corner_radius=10, width=150)
cost_of_materials_entry.place(x=450, y=500)
ctk.CTkLabel(new_project_window, text="Labor Cost:", corner_radius=10, text_color=("blue", "yellow")).place(x=620, y=400)
cost_of_labor_entry = ctk.CTkEntry(new_project_window, corner_radius=10, width=150)
cost_of_labor_entry.place(x=620, y=430)
ctk.CTkLabel(new_project_window, text="Total Cost:", corner_radius=10, text_color=("blue", "yellow")).place(x=620, y=470)
total_cost_entry = ctk.CTkEntry(new_project_window, corner_radius=10, width=150)
total_cost_entry.place(x=620, y=500)
# DateEntry for Start and Completion
ctk.CTkLabel(new_project_window, text="Start:", corner_radius=10, text_color=("blue", "yellow")).place(x=450, y=550)
ctk.CTkLabel(new_project_window, text="Complete:", corner_radius=10, text_color=("blue", "yellow")).place(x=620, y=550)
start_date = DateEntry(new_project_window, width=12, background="black", foreground="yellow", borderwidth=2)
completion_date = DateEntry(new_project_window, width=12, background="black", foreground="yellow", borderwidth=2)
start_date.place(x=450, y=580)
completion_date.place(x=620, y=580)
# Save button
save_button = ctk.CTkButton(new_project_window, corner_radius=10, text="Save Project", text_color=("white", "black"), width=150, fg_color="#99FFCC", hover_color="#00F800", command=save_project)
save_button.place(x=620, y=650)
# Cancel Button
cancel_button = ctk.CTkButton(new_project_window, text="Cancel", command=new_project_window.destroy, corner_radius=10, width=150, text_color=("blue", "yellow"), fg_color="#FF6666", hover_color="#FF3333")
cancel_button.place(x=450, y=650)
tab_order() # Set the tab order for our widgets
new_project_window.focus_set() # Focus on the window
new_project_window.mainloop()
return new_project_window # Not necessary but gives ability to handle the window elsewhere in the code