Python Forum
tkinter two windows instead of one
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
tkinter two windows instead of one
#1
I am trying to write a program using tkinter. I created a Class and in the init i create a few lists. The window consists of entrys, optionmenus, labels, picture and buttons. all of the widgets except for the two optionmenu widgets are in a window. these two optionmenu widgets are located in their own window. image is attached

picture of problem

please request me to provide the relevant code if need be. anyone knows why this is happening?
Reply
#2
Is the small window with the 4 option menus the root window (window returned by tk.Tk())? When you create a widget and you don't provide a parent argument, the widget is added to the root window. You can see that here:
import tkinter as tk

root = tk.Tk()
root.title("root")
font = (None, 100)
window = tk.Toplevel(root)
window.title("window")
tk.Label(window, text="In Window", font=font).pack()
tk.Label(text="No Parent", font=font).pack()   # Parent not specified
root.mainloop()
Reply
#3
(Jan-31-2024, 11:28 PM)deanhystad Wrote: Is the small window with the 4 option menus the root window (window returned by tk.Tk())? When you create a widget and you don't provide a parent argument, the widget is added to the root window. You can see that here:
import tkinter as tk

root = tk.Tk()
root.title("root")
font = (None, 100)
window = tk.Toplevel(root)
window.title("window")
tk.Label(window, text="In Window", font=font).pack()
tk.Label(text="No Parent", font=font).pack()   # Parent not specified
root.mainloop()
im not trying to have two windows. i want one with all of the widgets present in it. should i post my code? its like 200 something lines
Reply
#4
You must be making two windows. tk.Tk() creates a window and tk.TopLevel() creates a window. Are you calling both in your program? If you only call tk.Tk() you will only have 1 window.

Posting a 200 line program is fine.
Reply
#5
(Feb-03-2024, 06:43 PM)deanhystad Wrote: You must be making two windows. tk.Tk() creates a window and tk.TopLevel() creates a window. Are you calling both in your program? If you only call tk.Tk() you will only have 1 window.

Posting a 200 line program is fine.

im not using top level. i know that creates a second window but like i said, i want only one window. my code follows


from tkinter import *
from tkinter import messagebox
import json
from datetime import datetime,timedelta
from tkcalendar import DateEntry

import calendar as cal


class WorkoutTracker:
    def __init__(self, window,canvas):
        self.window = window
        self.day_value_inside= StringVar(self.window, value="Choose a date")
        self.act_value_inside = StringVar(self.window, value="Choose a workout")


        self.canvas=canvas
        self.cur_row = 0
        self.count = 0
        self.updated_list,self.day_lbl,self.act_lbl, \
        self.day_opt,self.act_opt,self.unit_lbl, \
        self.unit_ent,self.min_lbl=[], [],[],[],[],[],[],[]
        self.dates_list = self.all_dates_current_month()
        self.pic_file = PhotoImage(file="./workout.png")
        self.setup_gui()
        self.add_column()




    def all_dates_current_month(self,days=60,format="%a %d %B %Y"):
        '''generates a list of all days inside the current month and returns it.
            The dates are presented in a spcific format ("day name","date","month" and  "year")'''
        dates=[]
        start=datetime.now() + timedelta(days=-30)
        for x in range(-30,days+1):
            dates.append((start + timedelta(days=x)).strftime(format))
        print(dates)
        return dates

    def setup_gui(self):


        # make a list of the 4 past and 4 upcoming days
        for index, dat in enumerate(self.dates_list):

                if dat == datetime.today().strftime('%a %d %B %Y'):
                    self.updated_list.extend(self.dates_list[index - 5:index+6])

        self.window = Tk()
        self.canvas.grid(row=0, column=1, columnspan=8)
        pic = self.canvas.create_image(240, 135, image=self.pic_file)
        remove_button=Button(text="Remove Last Entry",command=self.remove_button)
        remove_button.grid(row=1,column=7)

        gen_row_but = Button(text="Add entry", command=self.add_column)
        gen_row_but.grid(row=1, column=8)
        reg_but = Button(text="Register activities", command=self.reg_act)
        reg_but.grid(row=1, column=9)
        display_but = Button(text="Display Saved Activities", command=self.display_act)
        display_but.grid(row=1, column=10)

    def reg_act(self):
        '''saves dates of workout, activities and duration in a dictionary setting and later saves these in a json file'''
        try:
            with open("saved_data.json", "r") as file:
                saved_data = json.load(file)
        except FileNotFoundError:
            saved_data = {}
            with open("saved_data.json", "w") as file:
                json.dump(saved_data, file)
        except json.decoder.JSONDecodeError:
            saved_data = {}
        finally:

            for i in range(0, self.count):
                day = self.day_value_inside.get()
                activity = self.day_value_inside.title()
                try:
                    unit = float(self.unit_ent[i].get())
                except ValueError:
                    messagebox.showerror(title="Oooops", message="You have not entered duration information")
                else:
                    if messagebox.askokcancel(title=f"{day} Workout Information",
                                              message=f"Activity: {activity} for {unit} minutes.\nSave this activity?"):
                        if day in saved_data:
                            saved_data[day]["activity"].append(activity)
                            saved_data[day]["unit"].append(unit)
                        else:

                            new_data = {day: {"activity": [activity], "unit": [unit]}}
                            saved_data.update(new_data)
                with open("saved_data.json", "w") as file:
                    json.dump(saved_data, file, indent=4)

    def display_act(self):

        '''makes a string of all activities within a specific date. uses messagebox to display each day as it is iterated through'''
        try:
            with open("saved_data.json", "r") as file:
                data = json.load(file)
        except json.decoder.JSONDecodeError:
            data = {}
            messagebox.showerror(title="Ooooops", message=f"There is no saved data to display")
        finally:
            data_str = ""
            for key in data:
                for i, act in enumerate(data[key]['activity']):
                    data_str += f"On {key} you did {data[key]['activity'][i].lower()} for {data[key]['unit'][i]} minutes.\n"
                messagebox.showinfo(title=f"{key} Workout Information", message=f"{data_str}")
                data_str = ""


    def add_column(self):
        '''generates a column of entries and labels
                    names of each entry and label are created dynamically
                    and each widget has a location added. Tkinter "rows" are incremented so each time this function is called
                    the next column of widgets gets generated below the previous'''
        self.cur_row += 1


        self.day_lbl.append(Label(text="On"))
        self.day_lbl[-1].grid(row=self.cur_row, column=0)
        self.day_opt.append(OptionMenu(self.window,self.day_value_inside, self.updated_list[0], self.updated_list[1],
                                       self.updated_list[2], self.updated_list[3], self.updated_list[4],
                                       self.updated_list[5], self.updated_list[6], self.updated_list[7],
                                       self.updated_list[8], self.updated_list[9]))
        self.day_opt[-1].grid(row=self.cur_row, column=1)
        self.act_lbl.append(Label(text="i did"))
        self.act_lbl[-1].grid(row=self.cur_row,column=2)
        self.act_opt.append(OptionMenu(self.window,self.act_value_inside,"Aerobics", "Cycling","Running", "Swimming", "Walking"))
        self.act_opt[-1].grid(row=self.cur_row,column=3)
        self.unit_lbl.append(Label(text="for: "))
        self.unit_lbl[-1].grid(row=self.cur_row,column=4)
        self.unit_ent.append(Entry())
        self.unit_ent[-1].grid(row=self.cur_row,column=5)
        self.min_lbl.append(Label(text="minutes"))
        self.min_lbl[-1].grid(row=self.cur_row,column=6)
        self.count += 1

    def remove_button(self):

        self.day_lbl[-1].forget()
        self.act_lbl[-1].forget()
        self.day_opt[-1].forget()
        self.act_opt[-1].forget()
        self.unit_lbl[-1].forget()
        self.unit_ent[-1].forget()
        self.min_lbl[-1].forget()
        self.count-=1

    def display_act(self):
        '''makes a string of all activities within a specific date. uses messagebox to display each day as it is iterated through'''
        try:
            with open("saved_data.json", "r") as file:
                data = json.load(file)
        except json.decoder.JSONDecodeError:
            data = {}
            messagebox.showerror(title="Ooooops", message=f"There is no saved data to display")
        finally:
            data_str = ""
            for key in data:
                for i, act in enumerate(data[key]['activity']):
                    data_str += f"On {key} you did {data[key]['activity'][i].lower()} for {data[key]['unit'][i]} minutes.\n"
                messagebox.showinfo(title=f"{key} Workout Information", message=f"{data_str}")
                data_str = ""





if __name__ == "__main__":
    window = Tk()
    window.title("Workout Tracker")
    canvas=Canvas(width=400,height=227)
    tracker = WorkoutTracker(window,canvas)
    window.mainloop()
Reply
#6
You do this on line 172:
if __name__ == "__main__":
    window = Tk()
And this on line 41
def setup_gui(self):


        # make a list of the 4 past and 4 upcoming days
        for index, dat in enumerate(self.dates_list):

                if dat == datetime.today().strftime('%a %d %B %Y'):
                    self.updated_list.extend(self.dates_list[index - 5:index+6])

        self.window = Tk()
That is 2 windows. Calling Tk() twice in a program is also an error.

And I don't see where you ever use the correct arguments for making buttons or labels. Instead of this:
gen_row_but = Button(text="Add entry", command=self.add_column)
You really need to do this:
gen_row_but = Button(window, text="Add entry", command=self.add_column)
You only specify the parent window when you make option menus. That is why option menus show in the other window.

The first argument should always be the parent window for the widget. If you leave this out, the default is to add widgets to the window created by Tk(). This lazy behavior will start causing more problems as your windows become more complicated.


Additional comments.

Learn about list pack/unpack. Instead of this:
        self.day_opt.append(OptionMenu(self.window,self.day_value_inside, self.updated_list[0], self.updated_list[1],
                                       self.updated_list[2], self.updated_list[3], self.updated_list[4],
                                       self.updated_list[5], self.updated_list[6], self.updated_list[7],
                                       self.updated_list[8], self.updated_list[9]))
do this:
        self.day_opt.append(OptionMenu(self.window, self.day_value_inside, *self.updated_list))
This is an error
 self.act_opt.append(OptionMenu(self.window,self.act_value_inside,"Aerobics", "Cycling","Running", "Swimming", "Walking"))
You cannot reuse the self.act_value_inside. You need to create a new variable for each OptionMenu instance.

As mentioned in your other thread, this will not work:
    def remove_button(self):
 
        self.day_lbl[-1].forget()
        self.act_lbl[-1].forget()
        self.day_opt[-1].forget()
        self.act_opt[-1].forget()
        self.unit_lbl[-1].forget()
        self.unit_ent[-1].forget()
        self.min_lbl[-1].forget()
        self.count-=1
You are using the wrong type of forget (need to use grid_forget()), and your logic is wrong. Your forgotten widgets remain in the lists. When you call remove_button() a second time it forgets the already forgotten widgets.
Reply
#7
If you have a table of workout activities, your program should have something like a table, and something that represents workout activities.
import tkinter as tk
from tkinter import ttk
from datetime import datetime, timedelta


class DatePicker(ttk.Combobox):
    """A widget for entering a date.  Can type in or select from list."""
    format = "%b %d, %Y"

    def __init__(self, *args, date=None, days=10, **kwargs):
        """date sets current value.  Defaults to today.
        days is number of days in list surrounding current selection.
        """
        super().__init__(*args, postcommand=self.postcommand, **kwargs)
        date = datetime.today() if date is None else date
        self.set(date.strftime(self.format))
        self.days = days

    def postcommand(self):
        """Update list to self.days before and after current date."""
        date = datetime.strptime(self.get(), self.format)
        self["values"] = [
            (date + timedelta(days=d)).strftime(self.format)
            for d in range(-self.days, self.days+1)
        ]


class TableRow(tk.Frame):
    """A row in a table.  Get set values using properites."""
    activities = ("Aerobics", "Cycling", "Running", "Swimming", "Walking")

    def __init__(self, parent, *args, **kwargs):
        """Create widgets that appear in a row"""
        super().__init__(parent, *args, **kwargs)
        self._date = DatePicker(self, width=12)
        self._activity = ttk.Combobox(self, values=self.activities, width=10)
        self.activity = self.activities[0]
        self._duration = tk.DoubleVar(parent, 30)

        for x in (
            self._date,
            self._activity,
            tk.Entry(self, textvariable=self._duration, width=8, justify=tk.RIGHT),
            tk.Button(self, text="X", command=lambda: parent.remove_row(self))
        ): x.pack(padx=2, side=tk.LEFT)

    def __str__(self):
        return f"{self.activity} on {self.date} for {self.duration} minutes"

    def __repr__(self):
        return str(self)

    @property
    def activity(self):
        return self._activity.get()

    @activity.setter
    def activity(self, value):
        self._activity.set(value)

    @property
    def duration(self):
        return self._duration.get()

    @duration.setter
    def duration(self, value):
        self._duration.set(value)

    @property
    def date(self):
        return self._date.get()

    @date.setter
    def date(self, value):
        self._date.set(value)


class Table(tk.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        header = tk.Frame(self)
        header.pack(side=tk.TOP, expand=1, fill="x")
        tk.Label(header, text="Date", width=13).pack(side=tk.LEFT)
        tk.Label(header, text="Activity", width=13).pack(side=tk.LEFT)
        tk.Label(header, text="Minutes").pack(side=tk.LEFT)
        self.rows = []

    def append_row(self):
        self.rows.append(TableRow(self))
        self.rows[-1].pack(side=tk.TOP)

    def remove_row(self, row):
        if row in self.rows:
            self.rows.remove(row)
            row.destroy()

    def __str__(self):
        return "\n".join(str(row) for row in self.rows)


class WorkoutTracker(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.table = Table(self)
        self.table.pack(side=tk.TOP)
        buttons = tk.Frame(self)
        buttons.pack(side=tk.TOP)
        self.append_btn = tk.Button(buttons, text="Add Activity", command=self.table.append_row)
        self.append_btn.pack(side=tk.LEFT)
        self.print_btn = tk.Button(buttons, text="Print Activities", command=lambda: print(self.table))
        self.print_btn.pack(side=tk.LEFT)

        for _ in range(3):
            self.table.append_row()

window = WorkoutTracker()
window.mainloop()
One thing I don't like about this solution is that the rows prevent using a grid layout
Reply
#8
This treats the table row like a dictionary and uses a json file to save/restore the table.
import tkinter as tk
from tkinter import ttk
from datetime import datetime, timedelta
import json


class DatePicker(ttk.Combobox):
    """A widget for entering a date.  Can type in or select from list."""
    format = "%b %d, %Y"

    def __init__(self, *args, date=None, days=10, **kwargs):
        """date sets current value.  Defaults to today.
        days is number of days in list surrounding current selection.
        """
        super().__init__(*args, postcommand=self.postcommand, **kwargs)
        date = datetime.today() if date is None else date
        self.set(date.strftime(self.format))
        self.days = days

    def postcommand(self):
        """Update list to self.days before and after current date."""
        date = datetime.strptime(self.get(), self.format)
        self["values"] = [
            (date + timedelta(days=d)).strftime(self.format)
            for d in range(-self.days, self.days+1)
        ]


class TableRow(tk.Frame):
    """A row in a table.  Get set values using properites."""
    activities = ("Aerobics", "Cycling", "Running", "Swimming", "Walking")

    def __init__(self, parent, *args, date=None, activity=None, minutes=None, **kwargs):
        """Create widgets that appear in a row"""
        super().__init__(parent, *args, **kwargs)

        self._values = {
            "date": DatePicker(self, date=date, width=12),
            "activity": ttk.Combobox(self, values=self.activities, width=10),
            "minutes": tk.DoubleVar(parent, minutes if minutes is not None else 30.0)
        }
        self["activity"] = activity if activity is not None else self.activities[0]

        for x in (
            self._values["date"],
            self._values["activity"],
            tk.Entry(self, textvariable=self._values["minutes"], width=8, justify=tk.RIGHT),
            tk.Button(self, text="X", command=lambda: parent.remove_row(self))
        ): x.pack(padx=2, side=tk.LEFT)

    def __str__(self):
        return str(self.values)

    def __repr__(self):
        return str(self)

    def __iter__(self):
        """Key iterator."""
        return iter(self._values)

    def __getitem__(self, name):
        """Return field value."""
        return self._values[name].get()

    def __setitem__(self, name, value):
        """Set field value."""
        self._values[name].set(value)

    @property
    def values(self):
        """Return field values as a dictionary."""
        return {key: self[key] for key in self._values}

    @values.setter
    def values(self, new_values):
        """Set field values from new_values dictionary."""
        for key, value in new_values.items():
            self[key] = value


class Table(tk.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        header = tk.Frame(self)
        header.pack(side=tk.TOP, expand=1, fill="x")
        tk.Label(header, text="Date", width=13).pack(side=tk.LEFT)
        tk.Label(header, text="Activity", width=13).pack(side=tk.LEFT)
        tk.Label(header, text="Minutes").pack(side=tk.LEFT)
        self.rows = []

    def append(self, values=None):
        """Add a row to the table.  Values is a dictionary of values for the row."""
        row = TableRow(self)
        if values is not None:
            row.values = values
        print("<", ", ".join([f"{key} = {row[key]}" for key in row]), ">")
        row.pack(side=tk.TOP)
        self.rows.append(row)
        return row

    def remove(self, row):
        """Remove row with matching ID."""
        if row in self.rows:
            self.rows.remove(row)
            row.destroy()

    def clear(self):
        """Delete all rows"""
        for row in self.rows:
            row.destory()
        self.rows = []

    @property
    def values(self):
        """Return list of row values.  Row values are dictionaries."""
        return [row.values for row in self.rows]

    @values.setter
    def values(self, new_values):
        """Reset table to show new_values.  new_values is list of row values."""
        self.clear()
        for values in new_values:
            self.append(values)

    def __str__(self):
        return "\n".join(str(row) for row in self.rows)


class WorkoutTracker(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.filename = None
        self.table = Table(self)
        self.table.pack(side=tk.TOP)
        buttons = tk.Frame(self)
        buttons.pack(side=tk.TOP, expand=1, fill=tk.X)
        self.append_btn = tk.Button(buttons, text="Add Activity", command=self.table.append)
        self.append_btn.pack(side=tk.LEFT, padx=5, pady=5, expand=1, fill=tk.X)
        self.print_btn = tk.Button(buttons, text="Print Activities", command=lambda: print(self.table))
        self.print_btn.pack(side=tk.LEFT, padx=(0, 5), pady=5, expand=1, fill=tk.X)
        self.protocol("WM_DELETE_WINDOW", self.on_close)

    def on_close(self):
        """Dump values to json file."""
        if self.filename:
            with open(self.filename, "w") as file:
                json.dump(self.table.values, file)
        self.destroy()

    def load(self, filename):
        """Load values from json file."""
        self.filename = filename
        try:
            with open(self.filename, "r") as file:
                self.table.values = json.load(file)
        except:
            pass


window = WorkoutTracker()
window.load("table.json")
window.mainloop()
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  pass a variable between tkinter and toplevel windows janeik 10 2,382 Jan-24-2024, 06:44 AM
Last Post: Liliana
  Tkinter multiple windows in the same window tomro91 1 870 Oct-30-2023, 02:59 PM
Last Post: Larz60+
  Dual Tkinter windows and shells Astrikor 6 3,960 Sep-03-2020, 10:09 PM
Last Post: Astrikor
  Tkinter scaling windows conten to or with its size not working Detzi 5 4,496 Jan-12-2020, 12:42 PM
Last Post: Detzi
  How to close one of the windows in Tkinter scratchmyhead 3 4,825 Dec-21-2019, 06:48 PM
Last Post: pashaliski
  Using tkinter on Windows to launch python 3 scripts Mocap 1 2,751 Jul-17-2019, 05:16 AM
Last Post: Yoriz
  [Tkinter] Using tkinter and mutiprocess ok on windows, locks up on ubuntu? ice 3 2,694 May-29-2019, 08:44 AM
Last Post: ice
  Int Variables in different Tkinter windows only returning 0 harry76 3 4,162 May-26-2019, 10:24 AM
Last Post: Yoriz
  [Tkinter] Ignore windows scaling in tkinter Gangwick 2 4,473 Jul-23-2018, 02:41 PM
Last Post: Gangwick
  Using a class to create instances of Tkinter Toplevel() windows nortski 2 11,017 Mar-27-2018, 11:44 AM
Last Post: nortski

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020