Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

filedialog/messagebox and CTkToplevel #2650

Open
Geo9999 opened this issue Nov 27, 2024 · 4 comments
Open

filedialog/messagebox and CTkToplevel #2650

Geo9999 opened this issue Nov 27, 2024 · 4 comments

Comments

@Geo9999
Copy link

Geo9999 commented Nov 27, 2024

Hello, I'm new to programming and github so excuse any omission or issue with this Issue. Let me know if uploading part of the code would help, I wasn't sure whether I should.

I'm working on an app with a main window and one or more CTkToplevel windows that should be able to load files and make plots on separate windows (or on the main one). The Toplevel is it's own class called hkl_input_window and will be called from the main window.
Problem is the tkinter messagebox, the filedialog windows and the toplevels containing the plots open over the root window and not the toplevel. When I tried to add my Toplevel window (called hkl_input_window) as a parent/master to them, I got the following error: " type object 'hkl_input_window' has no attribute 'tk' ". I added the full error here. Similar errors happened with the messagebox.showerror etc.

Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\GeorgicsB\AppData\Local\Programs\Python\Python312\Lib\tkinter_init_.py", line 1968, in call
return self.func(*args)
^^^^^^^^^^^^^^^^
File "C:\Users\GeorgicsB\AppData\Local\Programs\Python\Python312\Lib\site-packages\customtkinter\windows\widgets\ctk_button.py", line 554, in _clicked
self.command()
File "c:\Users\GeorgicsB\Desktop\New_folder\hkl_input_window.py", line 345, in
self.button=CTkButton(self.frame2, text="Plot poles in a separate window", height=30, command=lambda: self.calculate_poles(plotting="separate")) #### Plot Pole Figures in a separate window ####
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "c:\Users\GeorgicsB\Desktop\New_folder\hkl_input_window.py", line 420, in calculate_poles
self.separate_plot_window(data)
File "c:\Users\GeorgicsB\Desktop\New_folder\hkl_input_window.py", line 427, in separate_plot_window
plot_window = customtkinter.CTkToplevel(hkl_input_window)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\GeorgicsB\AppData\Local\Programs\Python\Python312\Lib\site-packages\customtkinter\windows\ctk_toplevel.py", line 36, in init
super().init(*args, **pop_from_dict_by_set(kwargs, self.valid_tk_toplevel_arguments))
File "C:\Users\GeorgicsB\AppData\Local\Programs\Python\Python312\Lib\tkinter_init
.py", line 2708, in init
BaseWidget.init(self, master, 'toplevel', cnf, {}, extra)
File "C:\Users\GeorgicsB\AppData\Local\Programs\Python\Python312\Lib\tkinter_init
.py", line 2653, in init
self.setup(master, cnf)
File "C:\Users\GeorgicsB\AppData\Local\Programs\Python\Python312\Lib\tkinter_init
.py", line 2622, in _setup
self.tk = master.tk
^^^^^^^^^
AttributeError: type object 'hkl_input_window' has no attribute 'tk'

I tried the following:

  • Removing the .attributes("-topmost", 1) from the Toplevel window, which makes it appear under the root and is undesirable,
  • Using .attributes("-toolwindow", 1), which makes it appear under the root, but if clicked it is brought to the top and the rest appears over it. This one has other problems such as pushing the window to the back when the filedialog window opens and some very minor changes in how it looks
  • Using CTkMessagebox instead of the classic messagebox, which corrected the problems I had with the tkinter messagebox.
  • Adding .attributes("-topmost", 1) to the plotting window, which appeared in front of hkl_input_window only for a moment and then moved behind, on top of the root window.
  • Using the .lift() and .lower() on the toplevel and/or the root, which either did not do anything or did not fix the relative placement of the root, toplevel and rest of the windows
    Any time I tried to enter my Toplevel as a parent/master in the filedialog.askopenfilename or plotting windows I got the " type object 'hkl_input_window' has no attribute 'tk' "

Is it possible that the CTkToplevel widget isn't able to be used like this? What possibilities exist to combat this?

Sorry for the long message and thank you in advance for any help!!

@dipeshSam
Copy link

dipeshSam commented Nov 27, 2024

@Geo9999 Could you please have Sample Reproducible Code along with its visual output?

Regards.

@Geo9999
Copy link
Author

Geo9999 commented Nov 28, 2024

I've written two similar mock versions of the code, no1 works but does not try to pass the secondary_window as a parent, hence the load/plots appear over the root and behind the toplevel, and no2 which produces the same error.
I have attached the code in txt and some screenshots of the result, hope this gives some more information.
Thanks for the help!

sample_for_github_1.txt
sample_for_github_2.txt
sample_1 1
sample_1 2
sample_2

@dipeshSam
Copy link

dipeshSam commented Nov 28, 2024

@Geo9999 The bug lies in your code misspelling. It is recommended that you should use proper naming conventions. Also, instead of passing object reference as self, you were passing the class name itself by confusing with secondary_window name.
And to bring the window up, we can wait 200 ms (milliseconds) and then can lift the window.

Here is your corrected code:
sample_for_github_2(corrected)

import tkinter
from tkinter.messagebox import showerror, showinfo
from tkinter import filedialog as fd
import customtkinter
from customtkinter import CTkButton, CTkToplevel, CTkFrame
from CTkMessagebox import CTkMessagebox
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)


class secondary_window(customtkinter.CTkToplevel):
    def __init__(self, parent, **kwargs):
        super().__init__(parent, **kwargs)

        self.title("Toplevel Window")    #### Should open over the main
        self.geometry("300x300")
       
        self.data = [[1,2,3,4], [5,6,7,8]]

        self.button=CTkButton(self, text="load", command=lambda: self.load_cif(self))
        self.button.grid(row=0, column=0)
        self.button=CTkButton(self, text="plot", command=lambda: self.toplevel_plots(self.data))
        self.button.grid(row=1, column=0)
        
        self.after_idle(self.lift)

    def load_cif(self, parent):
        filetypes = (
                ('cif␣files', '*.cif'),
                ('All␣files', '*.*')
                )
                

        filename = fd.askopenfilename(
        title='Open a file',
        initialdir='/',
        filetypes=filetypes, parent=parent)
        
        

        if filename:
            CTkMessagebox(self, title='Selected File', message=filename)
        else:
            showinfo(title='Message',
                    message="No file was selected!",
                    parent = parent)


    def toplevel_plots(self, data):
        window=CTkToplevel(self)
        window.title("Plots in separate window")
        
        window.after(200, window.lift)
    
        frame = CTkFrame(window)
        frame.grid(row=0, column=0, sticky="nsew")
        canvas = self.create_canvas(frame, data)
        canvas.get_tk_widget().grid(row=0, column=0, sticky="nsew")
        print(data)


    def create_canvas(self, master, data):
        x, y = data
        fig = plt.figure()
        ax = fig.add_axes([0.05,0.05,0.9,0.9])
        canvas = FigureCanvasTkAgg(fig, master=master)
        ax.scatter(x, y)
        canvas.draw()
        return canvas
    


def secondary():
    window = secondary_window(parent=root)

root = customtkinter.CTk()    #### Should always stay open at the back, everything else opens from in
root.title("Main Window")
root.geometry("300x300")
button=CTkButton(root, command=secondary)
button.grid(row=0, column=0)
root.mainloop()

You could read some standard naming convention over here.

I also created a refined version of this code:
new_refined_version.py

from customtkinter import (
    CTk,
    CTkButton,
    CTkFrame,
    CTkToplevel
)
import matplotlib.pyplot as plt
from tkinter import messagebox, filedialog
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

class PlottingWindow(CTkToplevel):
    def __init__(self, data: list, **kwargs):
        self._data = data
        super().__init__(**kwargs)

        self.wm_title("Plotting Window")
        
        # Setting it over the option window
        self.after(200, self.lift)
        
        self._frame = CTkFrame(self)
        self._frame.grid(row=0, column=0, sticky="nsew")

        self._create_canvas()
        
    def _create_canvas(self):
        x, y = self._data
        fig = plt.figure()
        ax = fig.add_axes([0.05, 0.05, 0.9, 0.9])
        canvas = FigureCanvasTkAgg(fig, master=self._frame)
        ax.scatter(x, y)
        canvas.draw()
        canvas.get_tk_widget().grid(row=0, column=0, sticky="nsew")

    
class OptionWindow(CTkToplevel):
    def __init__(self, master: CTk = None, **kwargs):
        super().__init__(master, **kwargs)
        
        self.wm_title("Option Window")
        self.geometry("400x250")
        
        self._plotting_loaded: bool = False
        self._default_data: list[list[int]] = [[1,2,3,4], [5,6,7,8]]
        
        # Setting it over the main window
        self.after(200, self.lift)
        
        # Adding options
        self._load_button = CTkButton(self, text="Load", command=self._on_load)
        self._load_button.place(relx=0.5, anchor="center", rely=0.4)

        self._plot_button = CTkButton(self, text="Plot", command=self._on_plot)
        self._plot_button.place(relx=0.5, anchor="center", rely=0.6)


    def _on_load(self):
        filetypes = (('cif␣files', '*.cif'), ('All␣files', '*.*'))
                
        filename = filedialog.askopenfilename(title="Open a file", 
                                              initialdir='/', 
                                              filetypes=filetypes, 
                                              parent=self)
        
        if filename:
            messagebox.showinfo("Message", f"Selected file is:\n\"{filename}\"", parent = self)
        else:
            messagebox.showinfo("Message", f"No file was selected!", parent = self)
        
        
    def _on_plot(self):
        if not self._plotting_loaded:
            self._plotting_loaded = True
            plotting_window = PlottingWindow(self._default_data)
            plotting_window.bind("<Destroy>", self._restore, True)
        
    def _restore(self, e=None):
        self._plotting_loaded = False
        
        
    
class MainWindow(CTk):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.wm_title("Main Window")
        self.geometry("400x250")
        
        self._options_loaded: bool = False
        
        self._option_button = CTkButton(self, text="Options", command=self._on_option)
        self._option_button.place(relx=0.5, anchor="center", rely=0.4)
        
        self._exit_button = CTkButton(self, text="Quit", command=self.quit)     # If it is not used, internal bug error may occur after using plot.
        self._exit_button.place(relx=0.5, anchor="center", rely=0.6)
                        
        
    def _on_option(self):
        if not self._options_loaded:
            self._options_loaded = True
            option_window = OptionWindow(self)
            option_window.bind("<Destroy>", self._restore, True)
        
    def _restore(self, e=None):
        self._options_loaded = False



if __name__ == "__main__":
    app = MainWindow()
    app.mainloop()

Refined output:
Screenshot (280)

Hope, this information is helpful to you.
Regards.

@Geo9999
Copy link
Author

Geo9999 commented Dec 1, 2024

Thank you so much for the help, I really appreciate how fast and detailed you were. I will implement the changes tomorrow and check again.
Wishing you the best!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants