2

so I am making an application that takes notes(similar to Windows Sticky Notes). Since I need to display multiple notes simultaneously, I have used a class which inherits from Thread and also creates a tkinter window. The problem is that my windows do not open simultaneously. The second opens up after the first is closed. Here is the code. What am I doing wrong? Is there another method that I can use? [For now I am just displaying notes I have hard-coded.]

from tkinter import *
from threading import Thread

class Note(Thread):
nid = 0
title = ""
message = ""

    def __init__(self, nid, title, message):
      Thread.__init__(self)
      self.nid = nid
      self.title = title
      self.message = message


    def display_note_gui(self): 
      '''Tkinter to create a note gui window with parameters '''    
      window = Tk()
      window.title(self.title)
      window.geometry("200x200")
      window.configure(background="#BAD0EF")

      title = Entry(relief=FLAT, bg="#BAD0EF", bd=0)
      title.pack(side=TOP)
      scrollBar = Scrollbar(window, takefocus=0, width=20)
      textArea = Text(window, height=4, width=1000, bg="#BAD0EF", font=("Times", "14"))
      scrollBar.pack(side=RIGHT, fill=Y)
      textArea.pack(side=LEFT, fill=Y)
      scrollBar.config(command=textArea.yview)
      textArea.config(yscrollcommand=scrollBar.set)
      textArea.insert(END, self.message)
      window.mainloop()

    def run(self):
      self.display_note_gui()

new_note1 = Note(0, "Hello", "Hi, how are you?")
new_note1.start()
new_note1.join()


new_note2 = Note(1, "2", "How's everyone else?")
new_note2.start()
new_note2.join()
Me95
  • 45
  • 1
  • 4

3 Answers3

2

If all you need is multiple note windows then you definitely don't need threads. Tkinter is quite capable of managing dozens or hundreds of open windows.

Just create instances of Toplevel for every window except the root window. Here's a somewhat over-engineered example:

import Tkinter as tk

class Notepad(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.text = tk.Text(self, wrap="word")
        self.vsb = tk.Scrollbar(self, orient="vertical", comman=self.text.yview)
        self.text.configure(yscrollcommand=self.vsb.set)
        self.vsb.pack(side="right", fill="y")
        self.text.pack(side="left", fill="both", expand=True)

def main():
    root = tk.Tk()
    Notepad(root).pack(fill="both", expand=True)
    for i in range(5):
        top = tk.Toplevel(root)
        Notepad(top).pack(fill="both", expand=True)

    root.mainloop()

if __name__ == "__main__":
    main()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
2

Instead of subclassing Thread just subclass Toplevel, a top level in tkinter is a separate window in the same application which sounds like exactly what you are trying to accomplish:

from tkinter import *
#from threading import Thread #no longer needed

class Note(Toplevel):
    nid = 0
    #title = "" #this would block the method to override the current title
    message = ""

    def __init__(self, master, nid, title, message):
      Toplevel.__init__(self,master)
      self.nid = nid 
      self.title(title) #since toplevel widgets define a method called title you can't store it as an attribute
      self.message = message
      self.display_note_gui() #maybe just leave that code part of the __init__?


    def display_note_gui(self): 
      '''Tkinter to create a note gui window with parameters '''    
      #no window, just self
      self.geometry("200x200")
      self.configure(background="#BAD0EF")
      #pass self as the parent to all the child widgets instead of window
      title = Entry(self,relief=FLAT, bg="#BAD0EF", bd=0)
      title.pack(side=TOP)
      scrollBar = Scrollbar(self, takefocus=0, width=20)
      textArea = Text(self, height=4, width=1000, bg="#BAD0EF", font=("Times", "14"))
      scrollBar.pack(side=RIGHT, fill=Y)
      textArea.pack(side=LEFT, fill=Y)
      scrollBar.config(command=textArea.yview)
      textArea.config(yscrollcommand=scrollBar.set)
      textArea.insert(END, self.message)
      #self.mainloop() #leave this to the root window

    def run(self):
      self.display_note_gui()

root = Tk()
root.withdraw() #hide the root so that only the notes will be visible

new_note1 = Note(root, 0, "Hello", "Hi, how are you?")
#new_note1.start()
#new_note1.join()


new_note2 = Note(root, 1, "2", "How's everyone else?")
#new_note2.start()
#new_note2.join()

root.mainloop() #still call mainloop on the root

note that instead of storing the title as an attribute you can call self.title() to get the current title of the window and self.title("new title") to change it.

Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59
  • Thanks, this worked! Thanks for the suggestion about the title, I'm quite new to python and I understood it very well :) – Me95 Mar 31 '16 at 05:55
  • normally using an attribute called `title` would be fine, it was just the only thing that really needed to change about your code to make it compatible with subclass of `Toplevel` – Tadhg McDonald-Jensen Mar 31 '16 at 05:57
  • if it didn't raise a bunch of `str object is not callable` errors when I was testing it I would have left it exactly how you had it. :) – Tadhg McDonald-Jensen Mar 31 '16 at 05:58
  • I have another question I'm stuck with, could you help me with [this question](http://stackoverflow.com/questions/36504887/how-do-i-unpickle-a-series-of-objects-from-a-file-in-python) – Me95 Apr 08 '16 at 16:40
0

Problem statement and the Solutions are wonderful for learning. These kinds of examples are needed for Teaching Multithreaded GUI with python's tkinter module.

I faced a similar issue when I created an App with one thread per GUI idea (common idea, I think). BTW, the error is “RuntimeError: main thread is not in main loop”.

There is another solution I came across using PySimpleGUI package. But that requires a big shift in architecture. I recommend to look at the approach using PySimpleGUI package, It gives some ideas for abstraction on the UI elements.

  • You can use `sg.read_all_windows()` to run multiple windows simultaneously. The error tkinter returns when it detects you're using threads can vary. It's not always the main loop message. – Mike from PSG Jun 30 '23 at 16:40