-1

I am trying to code a tkinter application that has three frames - a top frame, where the user inputs some text, a dynamically constructed middle section where some pre-analysis is conducted on the text, and a bottom frame where, once the user has selected which option they want in the middle section, the output will be produced.

The problem is that, depending upon the input, there could be around 10-20 (and in the worst case 30) lines displayed and on a small monitor the output will disappear off the screen.

What I would like is for the top (input) and bottom (output) frames to be visible no matter how the screen is re-sized, and for the middle section to scroll (if required) and still allow the user to select their choice.

I am confused as to how to get the middle section to resize when the screen is resized, show a scrollbar if required, and still allow all of the content to be accessed.

I have created a cut-down version here (for simplicity, I have removed the processing methods and have instead created some fake output in a loop that resembles what the actual middle section would look like).

Please ignore the hideous colour-scheme - I was just trying to understand which frame went where (I will remove the colours as soon as I can!)

Thank you for any suggestions...

import tkinter as tk
from tkinter import scrolledtext

class MyApp(tk.Tk):
    def __init__(self, title="Sample App", *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        self.title(title)
        self.configure(background="Gray")
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)

        # Create the overall frame:
        master_frame = tk.Frame(self, bg="Light Blue", bd=3, relief=tk.RIDGE)
        master_frame.grid(sticky=tk.NSEW)
        master_frame.rowconfigure([0, 2], minsize=90)  # Set min size for top and bottom
        master_frame.rowconfigure(1, weight=1)  # Row 1 should adjust to window size
        master_frame.columnconfigure(0, weight=1)  # Column 0 should adjust to window size

        # Create the frame to hold the input field and action button:
        input_frame = tk.LabelFrame(master_frame, text="Input Section", bg="Green", bd=2, relief=tk.GROOVE)
        input_frame.grid(row=0, column=0, padx = 5, pady = 5, sticky=tk.NSEW)
        input_frame.columnconfigure(0, weight=1)
        input_frame.rowconfigure(0, weight=1)

        # Create a frame for the middle (processing) section.
        middle_frame = tk.LabelFrame(master_frame, text = "Processing Section")
        middle_frame.grid(row=1, column=0, padx=5, pady=5, sticky=tk.NSEW)

        # Create the frame to hold the output:
        output_frame = tk.LabelFrame(master_frame, text="Output Section", bg="Blue", bd=2, relief=tk.GROOVE)
        output_frame.grid(row=2, column=0, columnspan=3, padx=5, pady=5, sticky=tk.NSEW)
        output_frame.columnconfigure(0, weight=1)
        output_frame.rowconfigure(0, weight=1)

        # Add a canvas in the middle frame.
        self.canvas = tk.Canvas(middle_frame, bg="Yellow")
        self.canvas.grid(row=0, column=0)

        # Create a vertical scrollbar linked to the canvas.
        vsbar = tk.Scrollbar(middle_frame, orient=tk.VERTICAL, command=self.canvas.yview)
        vsbar.grid(row=0, column=1, sticky=tk.NS)
        self.canvas.configure(yscrollcommand=vsbar.set)

        # Content for the input frame, (one label, one input box and one button).
        tk.Label(input_frame,
              text="Please type, or paste, the text to be analysed into this box:").grid(row=0, columnspan = 3, sticky=tk.NSEW)
        self.input_box = scrolledtext.ScrolledText(input_frame, height=5, wrap=tk.WORD)
        self.input_box.columnconfigure(0, weight=1)
        self.input_box.grid(row=1, column=0, columnspan = 3, sticky=tk.NSEW)
        tk.Button(input_frame,
               text="Do it!",
               command=self.draw_choices).grid(row=2, column=2, sticky=tk.E)

        # Content for the output frame, (one text box only).
        self.output_box = scrolledtext.ScrolledText(output_frame, width=40, height=5, wrap=tk.WORD)
        self.output_box.grid(row=0, column=0, columnspan=3, sticky=tk.NSEW)


    def draw_choices(self):
         """ This method will dynamically create the content for the middle frame"""
         self.option = tk.IntVar()  # Variable used to hold user's choice
         self.get_input_text()
         for i in range(30):
             tk.Radiobutton(self.canvas,
                        text=f"Option {i + 1}: ", variable=self.option,
                        value=i,
                        command=self.do_analysis
                        ).grid(row=i, column=0, sticky=tk.W)
             tk.Label(self.canvas,
                  text=f"If you pick Option {i + 1}, the output will look like this: {self.shortText}.",
                   anchor=tk.W
                   ).grid(row=i, column=1, sticky=tk.W)
         self.canvas.configure(scrollregion=self.canvas.bbox("all"))


    def get_input_text(self):
         """ Will get the text from the input box and also create a shortened version to display on one line"""
         screenWidth = 78
         self.input_text = self.input_box.get(0.0, tk.END)

         if len(self.input_text) > screenWidth:
             self.shortText = self.input_text[:screenWidth]
         else:
             self.shortText = self.input_text[:]
         self.shortText = self.shortText.replace('\n', ' ')  # strip out carriage returns just in case

    def do_analysis(self):
        """This will ultimately process and display the results"""
        option = self.option.get()  # Get option from radio button press
        output_txt = f"You picked option {option + 1} and here is the output: \n{self.input_text}"
        self.output_box.delete(0.0, tk.END)
        self.output_box.insert(0.0, output_txt)


if __name__ == "__main__":
    app = MyApp("My Simple Text Analysis Program")
    app.mainloop()
ArthurDent
  • 149
  • 2
  • 10
  • Does this answer your question? [tkinter gui layout using frames and grid](https://stackoverflow.com/a/34277295/7414759) and [Adding a scrollbar to a group of widgets](https://stackoverflow.com/a/3092341/7414759) – stovfl Jun 17 '20 at 19:56
  • Yes - Thank you! I think the second of those two links is exactly what I'm looking for (and I must have missed it in all of my googling). However, Bryan Oakley's excellent solution mixes pack and grid geometry and won't work (as is) on my code because I have a master frame on top of my root window. I am however going to refactor my code anyway and get rid of the master frame (because I realise that I no longer need that setup) and follow Bryan's structure. It will take me a while to do, but I will report back... Thanks! – ArthurDent Jun 18 '20 at 07:34
  • ***still struggling ... followed @ bryan oakley's excellent posts***: Retry with [this answer](https://stackoverflow.com/a/62446457/7414759), there the **scrolling part** are independant from the widgets to scroll. Strip also down your example and show your attempt. – stovfl Jun 19 '20 at 16:32
  • @stovfl That is absolutely brilliant! Thank you so much! It is exactly what I have been trying (and failing) to achieve. I am still early in my learning journey with tkinter, and this has helped me enormously. Thank you again. – ArthurDent Jun 19 '20 at 22:41

1 Answers1

0

I understand that you can't mix grid and pack geometries in the same container, and that a scrollbar must be attached to a canvas, and objects to be placed on that canvas must therefore be in yet another container so, attempting to follow Bryan's example, I created a minimal version of what I want - window with three sections - top, middle and bottom. The Top and bottom sections will contain a simple text field, the middle section will contain dynamic content and must be able to scroll as required.


enter image description here

Imports:

  • ScrollbarFrame
    Extends class tk.Frame to support a scrollable Frame]
import tkinter as tk

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("A simple GUI")

        # Top frame
        self.top_frame = tk.Frame(self, bg="LIGHT GREEN")
        self.top_frame.pack(fill=tk.X)
        tk.Label(self.top_frame, bg=self.top_frame.cget('bg'),
                 text="This is a label on the top frame")\
            .grid(row=0, columnspan=3, sticky=tk.NSEW)

        # Middle Frame
        # Import from https://stackoverflow.com/a/62446457/7414759
        # and don't change anything
        sbf = ScrollbarFrame(self, bg="LIGHT BLUE")
        sbf.pack(fill=tk.X, expand=True)

        # self.middle_frame = tk.Frame(self, bg="LIGHT BLUE")
        self.middle_frame = sbf.scrolled_frame

        # Force scrolling by adding multiple Label
        for _ in range(25):
            tk.Label(self.middle_frame, bg=self.middle_frame.cget('bg'),
                     text="This is a label on the dynamic (middle) section")\
                .grid()

        # Bottom Frame
        self.bottom_frame = tk.Frame(self, bg="WHITE")
        self.bottom_frame.pack(fill=tk.X)
        tk.Label(self.bottom_frame, bg=self.bottom_frame.cget('bg'),
                 text="This is a label on the bottom section")\
            .grid(row=0, columnspan=3, sticky=tk.NSEW)


if __name__ == '__main__':
    App().mainloop()
stovfl
  • 14,998
  • 7
  • 24
  • 51
ArthurDent
  • 149
  • 2
  • 10
  • Your main problem, you want to do **all** in one function and you didn't realize that the linked scrollbar examples have to be used as is. If you move only parts into yours, you will break the scrollbar part. – stovfl Jun 20 '20 at 06:36
  • Thank you @stovfl ! I had already managed to use your wonderful ScrollbarFrame class (I actually altered it slightly to inherit from LabelFrame rather than frame becuase I want to use LabelFrames in my app) but I had it working before you kindly edited my post.kindly edited my post. Thanks again! – ArthurDent Jun 20 '20 at 11:42
  • Read up on [What should I do when someone answers my question?](https://stackoverflow.com/help/someone-answers) => [ScrollbarFrame](https://stackoverflow.com/a/62446457/7414759) – stovfl Jun 20 '20 at 11:45