title: Automatic Resizing in Python's tkinter
body: <p>In the following method, I am trying to create a frame, put a label and text widget inside of it, and place them inside of another text widget. There are two problems with the results. How should it be changed to:</p>
  
  <ol>
  <li>Have the inner text objects have the correct height based on the inserted text?</li>
  <li>Get the frame and text to resize to the current dimensions of the outer widget?</li>
  </ol>
  
  <p>Suggestions would be appreciated! It is somewhat difficult to get messages to appear as intended in the code. They are supposed to automatically wrap and resize as the main widget gets stretched around.</p>
  
  <pre><code>def display(self, name, message):
      frame = tkinter.ttk.Frame(self.__text, borderwidth=1)
      frame.grid_rowconfigure(0, weight=1)
      frame.grid_columnconfigure(1, weight=1)
      name = tkinter.ttk.Label(frame, text=name)
      name.grid(row=0, column=0)
      text = tkinter.Text(frame, wrap=tkinter.WORD, height=1)
      text.grid(row=0, column=1, sticky=tkinter.EW)
      text.insert('1.0', message)
      text.configure(state=tkinter.DISABLED)
      self.__text.window_create('1.0', window=frame, stretch=tkinter.TRUE)
  </code></pre>
  
  <p>The code is supposed to generate a frame with a label in it along with word-wrapped text beside it. Each new message that is being displayed should be on top of older messages, and as the message list grows, it should be possible to scroll and read older messages (indefinitely). Unfortunately, this does not work any better than the code up above.</p>
  
  <pre><code>def display(self, name, message):
      frame = tkinter.ttk.Frame(self.__text, borderwidth=1, relief='solid')
      name = tkinter.ttk.Label(frame, text=name)
      text = tkinter.Text(frame, wrap=tkinter.WORD, height=1)
      frame.pack(expand=tkinter.TRUE, fill=tkinter.BOTH)
      name.pack(fill=tkinter.BOTH, side=tkinter.LEFT)
      text.pack(expand=tkinter.TRUE, fill=tkinter.BOTH)
      text.insert('1.0', message)
      text.configure(state=tkinter.DISABLED)
      self.__text.window_create('1.0', window=frame)
  </code></pre>
  
  <p>The frame appears to be properly configured, but getting the outer text box to act like a geometry manager and setting the height property of the inner text box appear to be the main problems here. The outer text box is not currently resizing the frame, and I am not sure what code to write to resize the height of the inner text box based on how much text is inside of it. Here is the full code of the program:</p>
  
  <pre><code>import tkinter
  import tkinter.ttk
  
  import datetime
  import getpass
  import os
  import uuid
  
  ################################################################################
  
  class DirectoryMonitor:
  
      def __init__(self, path):
          self.__path = path
          self.__files = {}
  
      def update(self, callback):
          for name in os.listdir(self.__path):
              if name not in self.__files:
                  path_name = os.path.join(self.__path, name)
                  self.__files[name] = FileMonitor(path_name)
          errors = set()
          for name, monitor in self.__files.items():
              try:
                  monitor.update(callback)
              except OSError:
                  errors.add(name)
          for name in errors:
              del self.__files[name]
  
  
  ################################################################################
  
  class FileMonitor:
  
      def __init__(self, path):
          self.__path = path
          self.__modified = 0
          self.__position = 0
  
      def update(self, callback):
          modified = os.path.getmtime(self.__path)
          if modified != self.__modified:
              self.__modified = modified
              with open(self.__path, 'r') as file:
                  file.seek(self.__position)
                  text = file.read()
                  self.__position = file.tell()
              callback(self.__path, text)
  
  ################################################################################
  
  class Aggregator:
  
      def __init__(self):
          self.__streams = {}
  
      def update(self, path, text):
          if path not in self.__streams:
              self.__streams[path] = MessageStream()
          parts = text.split('\0')
          assert not parts[-1], 'Text is not properly terminated!'
          self.__streams[path].update(parts[:-1])
  
      def get_messages(self):
          all_messages = set()
          for stream in self.__streams.values():
              all_messages.update(stream.get_messages())
          return sorted(all_messages, key=lambda message: message.time)
  
  ################################################################################
  
  class MessageStream:
  
      def __init__(self):
          self.__name = None
          self.__buffer = None
          self.__waiting = set()
  
      def update(self, parts):
          if self.__name is None:
              self.__name = parts.pop(0)
          if self.__buffer is not None:
              parts.insert(0, self.__buffer)
              self.__buffer = None
          if len(parts) &amp; 1:
              self.__buffer = parts.pop()
          for index in range(0, len(parts), 2):
              self.__waiting.add(Message(self.__name, *parts[index:index+2]))
  
      def get_messages(self):
          messages = self.__waiting
          self.__waiting = set()
          return messages
  
  ################################################################################
  
  class Message:
  
      def __init__(self, name, timestamp, text):
          self.name = name
          self.time = datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%SZ')
          self.text = text
  
  ################################################################################
  
  class MessageWriter:
  
      def __init__(self, path, name):
          assert '\0' not in name, 'Name may not have null characters!'
          self.__name = str(uuid.uuid1())
          self.__path = os.path.join(path, self.__name)
          with open(self.__path, 'w') as file:
              file.write(name + '\0')
  
      @property
      def name(self):
          return self.__name
  
      def write(self, text):
          assert '\0' not in text, 'Text may not have null characters!'
          timestamp = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
          with open(self.__path, 'a') as file:
              file.write(timestamp + '\0' + text + '\0')
  
  ################################################################################
  
  class Logos(tkinter.ttk.Frame):
  
      @classmethod
      def main(cls, path):
          tkinter.NoDefaultRoot()
          root = tkinter.Tk()
          root.title('Logos 2.0')
          root.minsize(320, 240)  # QVGA
          view = cls(root, path)
          view.grid(row=0, column=0, sticky=tkinter.NSEW)
          root.grid_rowconfigure(0, weight=1)
          root.grid_columnconfigure(0, weight=1)
          root.mainloop()
  
      def __init__(self, master, path, **kw):
          super().__init__(master, **kw)
          self.configure_widgets()
          self.__writer = MessageWriter(path, getpass.getuser())
          self.__monitor = DirectoryMonitor(path)
          self.__messages = Aggregator()
          self.after_idle(self.update)
  
      def configure_widgets(self):
          # Create widgets.
          self.__text = tkinter.Text(self, state=tkinter.DISABLED)
          self.__scroll = tkinter.ttk.Scrollbar(self, orient=tkinter.VERTICAL,
                                                command=self.__text.yview)
          self.__entry = tkinter.ttk.Entry(self, cursor='xterm')
          # Alter their settings.
          self.__text.configure(yscrollcommand=self.__scroll.set)
          # Place everything on the grid.
          self.__text.grid(row=0, column=0, sticky=tkinter.NSEW)
          self.__scroll.grid(row=0, column=1, sticky=tkinter.NS)
          self.__entry.grid(row=1, column=0, columnspan=2, sticky=tkinter.EW)
          self.grid_rowconfigure(0, weight=1)
          self.grid_columnconfigure(0, weight=1)
          # Setup box for typing.
          self.__entry.bind('&lt;Control-Key-a&gt;', self.select_all)
          self.__entry.bind('&lt;Control-Key-/&gt;', lambda event: 'break')
          self.__entry.bind('&lt;Return&gt;', self.send_message)
          self.__entry.focus_set()
  
      def select_all(self, event):
          event.widget.selection_range(0, tkinter.END)
          return 'break'
  
      def send_message(self, event):
          text = self.__entry.get()
          self.__entry.delete(0, tkinter.END)
          self.__writer.write(text)
  
      def update(self):
          self.after(1000, self.update)
          self.__monitor.update(self.__messages.update)
          for message in self.__messages.get_messages():
              self.display(message.name, message.text)
  
      def display(self, name, message):
          frame = tkinter.ttk.Frame(self.__text, borderwidth=1, relief='solid')
          name = tkinter.ttk.Label(frame, text=name)
          text = tkinter.Text(frame, wrap=tkinter.WORD, height=1)
          name.grid(row=0, column=0)
          text.grid(row=0, column=1, sticky=tkinter.EW)
          frame.grid_rowconfigure(0, weight=1)
          frame.grid_columnconfigure(1, weight=1)
          text.insert('1.0', message)
          text.configure(state=tkinter.DISABLED)
          self.__text.window_create('1.0', window=frame)
  
  ################################################################################
  
  if __name__ == '__main__':
      Logos.main('Feeds')
  </code></pre>