0

I have a button called view and when it is clicked on it will create a new window with a list in a listbox and a list of checkboxes that correspond to the listbox. When switching through the items in the listbox you will see that the listbox items are independent of each other and will have their own check box values. As of right now it will remember the checkbox values for each listbox item except when you close the created window. When you close the second window that was created all of that information disappears when you try and open the window back up again. I need a way to create a second window, do everything it does now with the listbox and checkboxes, but when closed it can be opened again and pick up where you left off.

For example, if I highlight the first item in the listbox and check the first checkbox, I should be able to close that window and open it again and when the first item in the listbox is highlighted, I see that there is a check in the first checkbox.

import tkinter
from tkinter import *


def myfunction(event):
    canvas1.configure(scrollregion=canvas1.bbox("all"))

def onselect(evt):
    # Note here that Tkinter passes an event object to onselect()
    w = evt.widget
    x = 0
    index = int(w.curselection()[0])
    value = w.get(index)
    print('You selected item %d: "%s"' % (index, value))

    for y in enable:
        for item in list_for_listbox:
            checkbuttons[item][y][1].grid_forget()
        checkbuttons[value][y][1].grid(row=x, column=0)
        # Label(frame2, text="some text").grid(row=x, column=1)
        x += 1

def printcommand():
    for item in list_for_listbox:
        for y in enable:
            print(item + " [" + y + "] " + str(checkbuttons[item][y][0].get()))

def create_new_window():
    global new_window
    new_window = tkinter.Toplevel()
    new_window.geometry("750x500")
    new_window_commands()

master = tkinter.Tk()
master.title("Checkboxes test")
master.geometry("750x500")

button1 = Button(master, command =create_new_window,text="View")
button1.place(x=50,y=250)

def new_window_commands():
    # enable = ['button 1', 'button 2', 'button 3', 'button 4', 'button 5', 'button 6', 'button 7']
    global list_for_listbox
    global enable
    global checkbuttons
    global canvas1
    enable = []
    for x_number_of_items in range(1, 15):
        enable.append("button " + str(x_number_of_items))

    list_for_listbox = ["one", "two", "three", "four"]

    listbox = Listbox(new_window)
    listbox.place(x=5, y=5, width=100, height=10 + 16*len(list_for_listbox))
    listbox.update()

    frame1 = Frame(new_window, borderwidth=1, relief=GROOVE, highlightthickness=1, highlightbackground="black",
                   highlightcolor="black")
    frame1.place(x=listbox.winfo_width() + 10, y=5, width=300, height=listbox.winfo_height())
    canvas1 = Canvas(frame1)
    frame2 = Frame(canvas1, height=500)
    scrollbar1 = Scrollbar(frame1, orient="vertical", command=canvas1.yview)
    canvas1.configure(yscrollcomman=scrollbar1.set)
    scrollbar1.pack(side="right", fill="y")
    canvas1.pack(side="left")
    canvas1.create_window((0, 0), window=frame2, anchor='nw')
    frame2.bind("<Configure>", myfunction)

    printbutton = Button(new_window, text="Print", command=printcommand)
    printbutton.place(x=100, y=250)

    checkbuttons = {}
    for item in list_for_listbox:
        listbox.insert(END, item)
        checkbuttons[item] = (dict())
        for y in enable:
            temp_var = BooleanVar()
            checkbuttons[item][y] = [temp_var, Checkbutton(frame2, text=y, variable=temp_var)]

    listbox.bind('<<ListboxSelect>>', onselect)

    print(enable)

mainloop()

printcommand()
S.McWhorter
  • 151
  • 1
  • 1
  • 15
  • 1
    You'll need to retain the states of those widgets in window as a `global` object. Though I would argue it's probably better to use an [object oriented approach for tkinter](https://stackoverflow.com/questions/17466561/best-way-to-structure-a-tkinter-application#17470842). Initially it feels like a hassle to convert but once you get used to it, it actually makes a lot of sense to go with this approach. – r.ook Nov 08 '18 at 13:35
  • Would you mind showing me that please? – S.McWhorter Nov 08 '18 at 13:42

1 Answers1

2

With your current structure, the simplest fix would be:

  1. Only create the new_window once.
  2. withdraw() new_window instead of letting it close each time.
  3. Open the same instance of new_window again when called upon.

You'll need to implement the following:

# Default your new_window to None
new_window = None

def create_new_window():
    global new_window
    # If new_window doesn't exist, create a new one
    if not new_window:
        new_window = tkinter.Toplevel()
        new_window.geometry("750x500")
        # add a new protocol to redirect on closing the window.
        new_window.protocol("WM_DELETE_WINDOW", hide_window)
        new_window_commands()
    else:
        # if new_window already exist, just unhide it
        new_window.deiconify()

# add a new function for when window is closing
def hide_window():
    global new_window
    new_window.withdraw()

You might also want to add the same protocol method under master so that when it closes, destroy both master and new_window object:

master.protocol('WM_DELETE_WINDOW', destroy_all)
def destroy_all():
    global master
    global new_window
    master.destroy()
    new_window.destroy()

If possible, for your next tkinter code I would suggest considering an object oriented approach though. I will see if I can provide a short sample here later.

As a side note, while I understand a lot of documentations in tkinter uses the from tkinter import * approach, I would discourage this practice and advise to import tkinter as tk instead (or as you already did, import tkinter, which accomplishes the same thing). See relevant answer here


Here's a quick sample of OOP approach in a similar vein:

import tkinter as tk

# Here the main window can be called upon as its own instance with its own instance attributes.
class Window(tk.Tk):
    def __init__(self):
        super().__init__()
        self.main_button = tk.Button(self, text="Creat a sub window", command=self.open_sub_window)
        self.main_button.pack()

        # define the things you wish to retain under the main window as an instance attribute
        self.sub_check_var = tk.BooleanVar()
        self.sub_entry_var = tk.StringVar()

    # when creating a new window, just reference back to the main attributes you've already created.
    def open_sub_window(self):
        self.sub_window = tk.Toplevel()
        tk.Checkbutton(self.sub_window, text="I'm a checkbox!", variable=self.sub_check_var).pack()
        lbl_frm = tk.LabelFrame(self.sub_window, text="I am an entry!")
        lbl_frm.pack()
        tk.Entry(lbl_frm, text=self.sub_entry_var).pack()

gui = Window()
gui.mainloop()

Note this is but one way to do it. You just need to feel around to get comfortable with your implementation, there's no right/wrong way to do things.

r.ook
  • 13,466
  • 2
  • 22
  • 39
  • 1
    You're welcome. I've also just edited my answer with a quick OOP sample. Feel free to take a look. – r.ook Nov 08 '18 at 14:25