0

I am having a problem with my first tkinter (Python 3) notebook app.

The canvas on which the data is displayed only needs to be 775px wide, by 480px high. This is all very well until the number of tabs makes the window wider than that. All the data is placed on one side and the other is a sea of emptyness. I have tried to make the notebook widget scrollable but I cannot get it to work.

Any advice would be greatly received.

#!/usr/bin/python

# Try to work with older version of Python
from __future__ import print_function

import sys

if sys.version_info.major < 3:
    import Tkinter as tk
    import Tkinter.ttk as ttk
else:
    import tkinter as tk
    import tkinter.ttk as ttk

#============================================================================
#   MAIN CLASS
class Main(tk.Frame):
    """ Main processing
    """
    def __init__(self, root, *args, **kwargs):
        tk.Frame.__init__(self, root, *args, **kwargs)

        self.root = root
        self.root_f = tk.Frame(self.root)

        self.width = 700
        self.height = 300

        # Create a canvas and scroll bar so the notebook can be scrolled
        self.nb_canvas = tk.Canvas(self.root_f, width=self.width, height=self.height)
        self.nb_scrollbar = tk.Scrollbar(self.root_f, orient='horizontal')

        # Configure the canvas and scrollbar to each other
        self.nb_canvas.config(yscrollcommand=self.nb_scrollbar.set,
                              scrollregion=self.nb_canvas.bbox('all'))
        self.nb_scrollbar.config(command=self.nb_canvas.xview)

        # Create the frame for the canvas window, and place
        self.nb_canvas_window = tk.Frame(self.nb_canvas, width=self.width, height=self.height)
        self.nb_canvas.create_window(0, 0, window=self.nb_canvas_window)

        # Put the whole notebook in the canvas window
        self.nb = ttk.Notebook(self.nb_canvas_window)

        self.root_f.grid()
        self.nb_canvas.grid()
        self.nb_canvas_window.grid()
        self.nb.grid(row=0, column=0)
        self.nb_scrollbar.grid(row=1, column=0, sticky='we')

        self.nb.enable_traversal()

        for count in range(20):
            self.text = 'Lots of text for a wide Tab ' + str(count)
            self.tab = tk.Frame(self.nb)
            self.nb.add(self.tab, text=self.text)
            # Create the canvas and scroll bar for the tab contents
            self.tab_canvas = tk.Canvas(self.tab, width=self.width, height=self.height)
            self.tab_scrollbar = tk.Scrollbar(self.tab, orient='vertical')
            # Convigure the two together
            self.tab_canvas.config(xscrollcommand=self.tab_scrollbar.set,
                                      scrollregion=self.tab_canvas.bbox('all'))
            self.tab_scrollbar.config(command=self.tab_canvas.yview)
                # Create the frame for the canvas window
            self.tab_canvas_window = tk.Frame(self.tab_canvas)
            self.tab_canvas.create_window(0, 0, window=self.tab_canvas_window)

            # Grid the content and scrollbar
            self.tab_canvas.grid(row=1, column=0)
            self.tab_canvas_window.grid()
            self.tab_scrollbar.grid(row=1, column=1, sticky='ns')

            # Put stuff in the tab
            for count in range(20):
                self.text = 'Line ' + str(count)
                self.line = tk.Label(self.tab_canvas_window, text=self.text)
                self.line.grid(row=count, column=0)

        self.root.geometry('{}x{}+{}+{}'.format(self.width, self.height, 100, 100))

        return

#   MAIN (MAIN) =======================================================
def main():
    """ Run the app
    """
    # # Create the screen instance and name it
    root = tk.Tk()
    # # This wll control the running of the app.
    app = Main(root)
    # # Run the mainloop() method of the screen object root.
    root.mainloop()
    root.quit()

#   MAIN (STARTUP) ====================================================
#   This next line runs the app as a standalone app
if __name__ == '__main__':
    # Run the function name main()
    main()
Garry
  • 51
  • 9
  • 2
    This question would be much better if you could reduce that code down to an [MCVE](http://www.stackoverflow.com/help/mcve). For example, a simple loop that creates a bunch of tabs is all you need to duplicate the problem, right? – Bryan Oakley Feb 01 '16 at 20:31
  • Can you also add a picture showing whats happening? Its hard to visualise what you have described. –  Feb 01 '16 at 23:10
  • Yes, I thought it was a lot of code. I will try to prune it down, but I don't know what an MCVE is. I have a screen shot, but I don't see how to upload it. Give me a moment on that. – Garry Feb 02 '16 at 17:22
  • So I'm not reputable enough to embed images, so I'll work on the code. Don't know what an MCVE is though. – Garry Feb 02 '16 at 17:25
  • So I have changed the code; hope it helps. To rephrase, I have a window of a desired size, but the number of notebook tabs is more than the window can hold. I cannot make the scrolling work. – Garry Feb 02 '16 at 18:22
  • If you run out of space for tabs it is a hint that you should redesign your dialog and use something else to select the page to be shown. Something like an "options dialog" where you use a treeview to select the page or a canvas with some icons on it or whatever. More than a few tabs starts to be a usability issue. See the link on this answer for some classic examples of such bad UI design (http://stackoverflow.com/a/30847430/291641) – patthoyts Feb 04 '16 at 14:17
  • Following further experimentation I have managed to put the notebook into the create_window frame and scroll that. I thought I had tried that yesterday, but I had been having problems with the scroll region, but I think I have a handle on that now. I take your point about redesign, and I am considering a panel of buttons to represent the tabs. Thank you for the suggestion. – Garry Feb 04 '16 at 14:57

3 Answers3

1

OK, so I think I understand now. The tabs are inside the notebook, and inseperable from the notebook. As such, the notebook will always be as wide as the frames within it. To get the effect I wanted I would need put a canvas into the notebook, and then add the tabs the the canvas. And that is not allowed. So back to the drawing board!

Garry
  • 51
  • 9
0

If the tabs are of 'constant' width and you know how many will fit the desired (fixed?)size of the window, you could create a "scrolling tabs" widget by hiding the ones that don't fit your width. Create two buttons, left and right that for example hides the one to the right and shows the next hidden one to the left.

If there a way to figure out the width of a tab (fontsize in the label, padding etc?) it could be done more 'dynamic'.

ingo
  • 117
  • 10
0

I would recommend combining the solutions from here: Is there a way to add close buttons to tabs in tkinter.ttk.Notebook? (to be able to close a tab) and here: https://github.com/muhammeteminturgut/ttkScrollableNotebook to use buttons instead of a scroll-bar to handle the width issue. Two changes to get it to work are to load the "notebookTab" variable as the CustomNotebook and to put the closing icon on the left side by switching the order of the innermost children of style.layout in the first answer. This produces a slidable and closeable custom notebook type.

Elarion
  • 56
  • 5