tkinter code does not proceed after .destroy() (multiple windows)
I am trying to create a simple tkinter application with two separate windows. The first window looks like this and is denoted plot_window in the code. It lets users select which columns should be plotted from dropdown menus. A category column is also used for visualising the labels in the plot.
However, when I run the code underneath and click either the ‘SAVE PLOT’ button or the ‘CANCEL’ button at the bottom one of these methods is triggered:
def close_plot_window(): self.plot_window.destroy() # This is reached def set_save_plot_bool(): print('Destroy') # This is reached self.save_plot_bool = True self.plot_window.destroy()
and the second window save_window should show up, but the code does not proceed.
The strange thing is that if I comment out this snippet for the third and final drop-down menu the code works fine
# IF I COMMENT OUT THIS WHOLE IF STATEMENT, THE CODE RUNS if len(data.columns) > 2: # There exist a third columns as well -> include drop-down for category selection # ******** Drop-down 3: Category selection ******** category_column = string_columns[0] if (len(string_columns) > 0) else numeric_columns[2] dropdown_choice_category.set( category_column) # Set the default option in the dropdown with the first column l3 = Label(self.plot_window, text="Select category column:") l3.grid(row=2, column=0, sticky='e') dropdown_menu_category = OptionMenu(self.plot_window, dropdown_choice_category, *choices) dropdown_menu_category.config(width=16) dropdown_menu_category.grid(row=2, column=1, sticky='w') chosen_columns = {'x_col': x_values_column, 'y_col': y_values_column, 'category_col': category_column}
The whole code:
import matplotlib matplotlib.use('TkAgg') from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure import matplotlib.pyplot as plt import numpy as np from tkinter import * from tkinter import filedialog import pandas as pd import seaborn as sns from pandas.api.types import is_numeric_dtype def center_tk_window(window, height, width): # Helper method for centering windows screen_width = window.winfo_screenwidth() screen_height = window.winfo_screenheight() x_coordinate = int((screen_width / 2) - (width / 2)) y_coordinate = int((screen_height / 2) - (height / 2)) window.geometry("{}x{}+{}+{}".format(width, height, x_coordinate, y_coordinate)) def plot_scatter(data, chosen_columns, ax=None, initial_box=None): plot_type = "scatter" if plot_type == "scatter": fig = Figure(figsize=(7, 6)) if ax is None: # Create a new subplot ax = fig.add_subplot(111) # Selected x-coordinates print('chosen_columns', chosen_columns) x_data = data[chosen_columns['x_col']] # Selected y-coordinates y_data = data[chosen_columns['y_col']] filled_markers = ('o', 'v', '^', '<', '>', '8', 's', 'p', '*', 'h', 'H', 'D', 'd', 'P', 'X') # Category column if 'category_col' in chosen_columns: category_data = data[chosen_columns['category_col']] print(np.unique(np.array(category_data))) # Plotting it all sns.scatterplot(ax=ax, x=x_data, y=y_data, hue=category_data, style=category_data, markers=filled_markers ) # Shrink current axis's height by 20% on the bottom if initial_box is None: initial_box = ax.get_position() ax.set_position([initial_box.x0, initial_box.y0 + initial_box.height * 0.2, initial_box.width, initial_box.height * 0.80]) # Put a legend below current axis ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.15), fancybox=False, shadow=False, ncol=6) plt.tight_layout() else: # Normal scatterplot without any categorical values sns.scatterplot(ax=ax, x=x_data, y=y_data) ax.set_title("User input plot name", fontsize=16) ax.set_ylabel("User input y label", fontsize=14) ax.set_xlabel("User input x label", fontsize=14) return fig, ax, initial_box class GenericPlot: def __init__(self, data): # Plot window self.save_plot_bool = False self.plot_window = Tk() self.dynamic_plots(data) self.plot_window.mainloop() print("After first mainlooop") # This line is never reached print('save_plot_bool', self.save_plot_bool) # Save window self.save_plot_dir = '' self.save_window = Tk() self.save_plot() self.save_window.mainloop() def dynamic_plots(self, data): """ Input : window : tkinter window data : DataFrame object """ def close_plot_window(): self.plot_window.destroy() def set_save_plot_bool(): print('Destroy') # This is reached self.save_plot_bool = True self.plot_window.destroy() center_tk_window(self.plot_window, 720, 600) # Drop-down variables (3 drop-downs) dropdown_choice_x = StringVar(self.plot_window) # Variable holding the dropdown selection for the x column dropdown_choice_y = StringVar(self.plot_window) # Variable holding the dropdown selection for the y column dropdown_choice_category = StringVar( self.plot_window) # Variable holding the dropdown selection for the category column # Create set of column names in the dataset choices = set(data.columns.values) # Find numeric and string columns string_columns = [] numeric_columns = [] [numeric_columns.append(col) if is_numeric_dtype(data[col]) else string_columns.append(col) for col in data.columns] if len(numeric_columns) < 2: raise Exception( "Unable to create scatter plot- need more than two numerical columns in the imported dataset.") # GUI setup self.plot_window.columnconfigure(0, weight=1) self.plot_window.columnconfigure(1, weight=1) self.plot_window.rowconfigure(0, weight=1) self.plot_window.rowconfigure(1, weight=1) self.plot_window.rowconfigure(2, weight=1) self.plot_window.rowconfigure(3, weight=1) self.plot_window.rowconfigure(4, weight=1) # ******** Drop-down 1: x-value selection ******** x_values_column = numeric_columns[0] # Select the first numeric column as the default x values to plot dropdown_choice_x.set(x_values_column) # Set the default option in the dropdown with the first column Label(self.plot_window, text="Select x column:").grid(row=0, column=0, sticky="e") choices_numeric = set(numeric_columns) # Only show numeric columns in the drop-down for x and y dropdown_menu_x = OptionMenu(self.plot_window, dropdown_choice_x, *choices_numeric) dropdown_menu_x.grid(row=0, column=1, sticky="w") dropdown_menu_x.config(width=16) # ******** Drop-down 2: y-value selection ******** y_values_column = numeric_columns[1] # Select the second alternative in the dropdown list for the y values dropdown_choice_y.set(y_values_column) # Set the default option in the dropdown with the first column l2 = Label(self.plot_window, text="Select y column:") l2.grid(row=1, column=0, sticky='e') dropdown_menu_y = OptionMenu(self.plot_window, dropdown_choice_y, *choices_numeric) dropdown_menu_y.config(width=16) dropdown_menu_y.grid(row=1, column=1, sticky='w') chosen_columns = {'x_col': x_values_column, 'y_col': y_values_column} #********* IF I COMMENT OUT THIS WHOLE IF STATEMENT, THE CODE RUNS*********** if len(data.columns) > 2: # There exist a third columns as well -> include drop-down for category selection # ******** Drop-down 3: Category selection ******** category_column = string_columns[0] if (len(string_columns) > 0) else numeric_columns[2] dropdown_choice_category.set( category_column) # Set the default option in the dropdown with the first column l3=Label(self.plot_window, text="Select category column:") l3.grid(row=2, column=0, sticky='e') dropdown_menu_category = OptionMenu(self.plot_window, dropdown_choice_category, *choices) dropdown_menu_category.config(width=16) dropdown_menu_category.grid(row=2, column=1, sticky='w') chosen_columns = {'x_col': x_values_column, 'y_col': y_values_column, 'category_col': category_column} # Plot the initially selected columns fig_initial, ax, initial_box = plot_scatter(data, chosen_columns) canvas = FigureCanvasTkAgg(fig_initial, master=self.plot_window) canvas.get_tk_widget().grid(row=3, columnspan=2, rowspan=True) canvas.draw() def change_dropdown_x(canvas, chosen_columns, ax, *args): # This function is triggered once a dropdown selection is made selected_x_col = dropdown_choice_x.get() chosen_columns['x_col'] = selected_x_col # Create a new plot now ax.clear() # Clearing the previous plot _, ax, _ = plot_scatter(data, chosen_columns, ax, initial_box) canvas.draw() # chosen columns might not be updated... def change_dropdown_y(canvas, chosen_columns, ax, *args): # This function is triggered once a dropdown selection is made selected_y_col = dropdown_choice_y.get() chosen_columns['y_col'] = selected_y_col # Create a new plot now ax.clear() # Clearing the previous plot _, ax, _ = plot_scatter(data, chosen_columns, ax, initial_box) canvas.draw() def change_dropdown_category(canvas, chosen_columns, ax, *args): # This function is triggered once a dropdown selection is made selected_category = dropdown_choice_category.get() chosen_columns['category_col'] = selected_category # Create a new plot now ax.clear() # Clearing the previous plot _, ax, _ = plot_scatter(data, chosen_columns, ax, initial_box) canvas.draw() # Link functions to change dropdown dropdown_choice_x.trace('w', lambda *args, canvas=canvas, chosen_columns=chosen_columns, ax=ax, initial_box=initial_box: change_dropdown_x(canvas, chosen_columns, ax, initial_box, *args)) dropdown_choice_y.trace('w', lambda *args, canvas=canvas, chosen_columns=chosen_columns, ax=ax, initial_box=initial_box: change_dropdown_y(canvas, chosen_columns, ax, initial_box, *args)) dropdown_choice_category.trace('w', lambda *args, canvas=canvas, chosen_columns=chosen_columns, ax=ax, initial_box=initial_box: change_dropdown_category(canvas, chosen_columns, ax, initial_box, *args)) # Save and close buttons Button(self.plot_window, text="CLOSE", command=close_plot_window).grid(row=4, column=0) Button(self.plot_window, text="SAVE PLOT", command=set_save_plot_bool).grid(row=4, column=1) def save_plot(self): if self.save_plot_bool: print(self.save_plot_bool) self.save_window.columnconfigure(0, weight=1) self.save_window.columnconfigure(1, weight=1) self.save_window.rowconfigure(0, weight=1) self.save_window.rowconfigure(1, weight=1) self.save_window.title('Save plot') # Get saving path print("Please select a directory for saving the model...", flush=True) l1 = Label(self.save_window, text=self.save_plot_dir) l1.grid(row=0, columnspan=2) def get_save_dir(): self.save_plot_dir = filedialog.askdirectory() def save_to_file_btn(): print(self.save_plot_dir) plt.save(self.save_plot_dir + '.tif') Button(self.save_window, text="Choose save directory...", command=get_save_dir).grid(row=0, column=1) Button(self.save_window, text="SAVE PLOT TO FILE", command=save_to_file_btn).grid(row=1, columnspan=2) center_tk_window(self.save_window, 300, 400) # window, height, width # Create matrix for testing df = pd.DataFrame(np.random.randint(0,100,size=(150, 4)), columns=list('ABCD')) # Hard code a category column of string with length 150. int_labels = [5, 1, 1, 4, 5, 1, 2, 0, 0, 2, 1, 2, 5, 3, 1, 4, 1, 5, 1, 4, 5, 4, 5, 3, 2, 2, 3, 4, 4, 3, 1, 3, 3, 2, 5, 1, 1, 5, 3, 3, 1, 2, 0, 1, 2, 0, 5, 3, 5, 1, 3, 5, 3, 5, 4, 2, 3, 3, 4, 1, 3, 3, 3, 4, 2, 2, 5, 2, 0, 2, 0, 5, 5, 4, 2, 0, 2, 3, 1, 5, 2, 1, 5, 3, 1, 3, 4, 4, 1, 3, 5, 1, 2, 2, 4, 0, 5, 0, 2, 2, 0, 4, 5, 2, 0, 2, 2, 3, 2, 0, 0, 5, 1, 0, 3, 1, 2, 2, 4, 0, 2, 2, 1, 1, 1, 1, 0, 2, 1, 1, 3, 2, 4, 4, 0, 2, 0, 5, 4, 4, 3, 0, 1, 0, 2, 5, 3, 0, 3, 5] string_labels = [str(i) for i in int_labels] df['category2'] = string_labels GenericPlot(df)
If anyone has an idea as to why the code does not proceed if I include the category dropdown-menu, please let me know.
I found the solution. It was the plt.tight_layout()
causing the problems…