0

Hello I am making a table with a header for text to name the columns, cells to store data, and scrolling.

The problem I'm having is that my Table is not displaying correctly. What should happen is the Headers should display above with no other space below them. (other than the small padding I added) The cells display correctly right below the headers.

The scrolling works for the cells in the y direction. The scrolling also works in the x direction for both the cells and headers.

The function the adds the cells to the frame simply does a create then add using grid(row, column) The header only has 1 row so the blank space should not be there.

import tkinter as tk
import collections
from enum import Enum

window = tk.Tk() # Root (main) window

def main():
    window.title('Table')
    window.geometry("1024x600")
    window.update_idletasks()
    table_frame = tk.Frame(window, background='black')
    table_frame.pack(fill='x')
    table = Table(table_frame, 30, 15)
    print(id(table))

    window.mainloop()

class Table:

    def __init__(self, frame, rowCount, columnCount):
        self._rowCount = rowCount
        self._columnCount = columnCount

        main_frame = tk.Frame(frame, bg='blue')
        main_frame.pack(fill='both')

        self._headerCanvas = tk.Canvas(main_frame)
        self._headerCanvas.grid(row=0, column=0, pady=1, sticky='ew')

        self._cellCanvas = tk.Canvas(main_frame)
        self._cellCanvas.grid(row=1, column=0, sticky='ew')

        scroll_bar_y = tk.Scrollbar(main_frame, orient=tk.VERTICAL, command=self._cellCanvas.yview)
        scroll_bar_y.grid(row=1, column=1, padx=1, sticky='ns')

        scroll_bar_x = tk.Scrollbar(main_frame, orient=tk.HORIZONTAL, command=self.xViewScroll)
        scroll_bar_x.grid(row=2, column=0, pady=1, sticky='ew')

        main_frame.grid_columnconfigure(0, weight=1)
        main_frame.grid_rowconfigure(1, weight=1)

        self._cellCanvas.configure(xscrollcommand=scroll_bar_x.set, yscrollcommand=scroll_bar_y.set) #, width=(main_frame.winfo_width()-scroll_bar_y.winfo_width()))

        header_frame = tk.Frame(self._headerCanvas)
        self._headers = Table.ColumnHeaders(header_frame, self._columnCount)
        cell_frame = tk.Frame(self._cellCanvas)
        self._cells = Table.Cells(cell_frame, self._rowCount, self._columnCount)

        self._headerCanvas.create_window(0, 0, window=header_frame, anchor='nw')
        self._headerCanvas.update_idletasks()
        self._cellCanvas.create_window(0, 0, window=cell_frame, anchor='nw')
        self._cellCanvas.update_idletasks()
        self._headerCanvas.configure(scrollregion=self._cellCanvas.bbox("all"))
        self._cellCanvas.configure(scrollregion=self._cellCanvas.bbox("all"))

    def xViewScroll(self, *args):
        self._headerCanvas.xview(*args)
        self._cellCanvas.xview(*args)


    class Cells:

        class Types(Enum):
            Entry = 0
            Button = 1

        class Cell:
            def __init__(self, widget, text=''):
                self._text = text
                self._widget = widget


            def getWidget(self):
                return self._widget

            def setWidget(self, widget):
                self._widget = widget

            widget = property(getWidget, setWidget, "Get and set the widget of a cell.")


        def __init__(self, frame, rows, columns, cellTypes=Types.Entry):
            self._cells = [[],[]]

            for r in range(rows):
                self._cells.append([])
                for c in range(columns):
                    self._cells[r].append(c)
                    if cellTypes == Table.Cells.Types.Entry:
                        self._cells[r][c] = Table.Cells.Cell(tk.Entry(frame, width=15))
                    elif cellTypes == Table.Cells.Types.Button:
                        self._cells[r][c] = Table.Cells.Cell(tk.Button(frame, width=12))

                    self._cells[r][c].widget.grid(row=r, column=c)


        def getCell(self, row, column):
            return self._cells[row][column]

        def setCell(self, row, column, cell):
            self._cells[row][column] = cell

        cells = property(getCell, setCell, "Get and set a cell in the table.")


    class ColumnHeaders:
        def __init__(self, widget, columnCount):
            self._widget = widget
            self._columnCount = columnCount
            self._headers = Table.Cells(self._widget, 1, self._columnCount, cellTypes=Table.Cells.Types.Button)
main()

This is how they are currently showing up. Table not re-sized

After re sizing the screen this is what the original should look like. With the headers just above the cells below with only the padding space. Table re-sized

If I shrink it a little more it causes another problem the scrollbar and the headers disappear from view. The scrollbar on the right never disappears however. ( I'm guessing this is because I can't shrink the screen anymore. Table shrunk even more.

This is what happens with main_frame.grid_rowconfigure(1, weight=1) It causes this after resizes to a smaller window. Table with main_frame.grid_rowconfigure(1, weight=1)

stovfl
  • 14,998
  • 7
  • 24
  • 51
deathismyfriend
  • 2,182
  • 2
  • 18
  • 25
  • This code won't run. When I add just enough code to make it work I get `AttributeError: type object 'Table' has no attribute 'ColumnHeaders'` – Bryan Oakley Dec 12 '19 at 00:23
  • I didn’t post all the code but I can when I get home. I only posted the section of code that controls the layout. The others just populate with either button or entry from tkinter. – deathismyfriend Dec 12 '19 at 00:31
  • We don't need "all the code", we need just enough to duplicate the problem. If the `ColumnHeaders` isn't necessary to reproduce the layout problem, just remove it. – Bryan Oakley Dec 12 '19 at 00:44
  • @deathismyfriend: You need to bind event `""`, because the `Canvas` does not follow the size of the `Frame`. Something like this: [use canvas to create dynamically window with scroll bar](https://stackoverflow.com/a/58219385/7414759) – stovfl Dec 12 '19 at 01:01
  • @BryanOakley I updated the code above with all needed code to produce the problem. – deathismyfriend Dec 12 '19 at 02:55
  • @stovfl The scrolling works perfectly as is. It's the display which is messed up. I say that above in description. – deathismyfriend Dec 12 '19 at 21:31
  • @deathismyfriend ***"scrolling works perfectly as is"***: As i wrote: *somthing* like the given link. Focus at the event ``. What you get displayed is the **default** `height` of a `Canvas`. You have to set the `height` explicit to the `height` of the `header_frame`. – stovfl Dec 12 '19 at 21:39
  • @stovfl I tried bind("") it does the exact same thing as above. If you are sure this will work can you show me a piece of code that will ? – deathismyfriend Dec 12 '19 at 23:42

1 Answers1

1

Question: Frame Grid sizing not displaying correctly

You are misguided, the header_frame is not layouted using .grid(...).
Using .create_window(... add the widget to the Canvas at the fixed position 0, 0, anchor='nw'. Therefore, no "Grid Layout Manager" magic, like auto resizing, happens.


Why do you get this Layout?
Note: main_frame == 'blue', _headerCanvas == 'lightgreen', _cellCanvas == 'red':

ABCD
Layout: A                             Layout: B                           Layout: C                             Layout: D

        self._headerCanvas = tk.Canvas(main_frame, bg='lightgreen')
        self._headerCanvas.grid(row=0, column=0, pady=1, sticky='ew')

        main_frame.grid_rowconfigure(1, weight=1)

        self._cellCanvas = tk.Canvas(main_frame, bg='red')
        self._cellCanvas.grid(row=1, column=0, sticky='ew')

Layout: A: You create both Canvas with the default height. Therefore you get a similar layout using "The Grid Layout Manager".

Layout: B: Adding itmes to a Canvas, here the Cells Frame, does not change anything at the Layout.


Layout: C: Sync the Canvas height with the header_frame height results in.

  1. Using .bind('<Configure>'

    header_frame = tk.Frame(self._headerCanvas)
    header_frame.bind('<Configure>', self.on_configure)
    
    def on_configure(self, event):
        # Sync _headerCanvas height with header_frame height
        w = event.widget
        self._headerCanvas.configure(height=w.winfo_height())
    
  2. Using .update_idletask()

        header_frame = tk.Frame(self._headerCanvas)
        self._headers = Table.ColumnHeaders(header_frame, self._columnCount)
        root.update_idletasks()
        self._headerCanvas.configure(height=header_frame.winfo_height())
    

Still not what you want?
Layout: D: You have allowed the .grid(row=1 to grow as much as possible by doing:

    main_frame.grid_rowconfigure(1, weight=1)

But you don't allow the Canvas to grow using sticky='ns'. This result in the 'blue' space above/bottom. Change to sticky='nsew'.

stovfl
  • 14,998
  • 7
  • 24
  • 51
  • Thanks. Your method 2 works great. But for some reason your method 1. using bind creates a weird bar underneath the header. I added the new pic to my question and put red lines to show the error. Can you tell me why this is happening. – deathismyfriend Dec 13 '19 at 16:18
  • @deathismyfriend: Different `height` value? Compare the output of `w.winfo_height()` with `header_frame.winfo_height()`. The value depends at which code line you are calling `.update_idletasks()` and `._headerCanvas.configure(...`. You are using `pady=1` but this sould for both the same. – stovfl Dec 13 '19 at 16:45
  • I have tried the height. It seems to be something else as it is actually in both methods. When maximizing it shows up with a border almost. I have updated the last picture you can see it there. I have changed all backgrounds to blue but that still shows up. – deathismyfriend Dec 13 '19 at 16:52
  • I fixed it it was a problem with the borders showing up. Thanks a lot for your help. – deathismyfriend Dec 13 '19 at 16:58