3

I've structured this post into four parts, the goal of what I'm trying to achieve, the minimal amount of code to recreate the problem, things I have already tried, and a conclusion. The code provided will look like this.

Goal:

Using Python Tkinter, set up a canvas to be able to scroll through tk.Entry widgets that are off-screen without the use of a scrollbar. The idea is to eventually lead up to being able to set a root.after to animate the scroll automatically without user input. I'm trying my best not to use a scrollbar as the end result will be a results list for a competition, so it's purely for viewing rather than interaction.

The minimum amount of code to recreate:

(The test data is just long enough to have the last few Entries be off-screen when the geometry is 1920x1080)
# Python program to create a table 
   
import tkinter as tk
from tkinter import ttk
from ESTDisplayEntity import ESTDisplayEntity
  
root = tk.Tk() 
root.geometry("1920x1080")
  
class Table():
    def __init__(self, root, lst):
        self.Tree = ttk.Treeview(root)
        # find total number of rows and 
        # columns in list 
        total_rows = len(lst) 
        total_columns = len(lst[0]) 

        maxWidth = 125
        # code for creating table 
        for i in range(total_rows): 
            for j in range(total_columns): 
                if j == 0:  
                    width = int(maxWidth*.05)
                elif j == 1:
                    width = int(maxWidth*.25)
                elif j == 2:
                    width = int(maxWidth*0.15)
                else:
                    width = int(maxWidth*(.15/total_columns-3))
                        
                e = tk.Entry(self.Tree, width=width, fg='Black', 
                                font=('Arial',16,'bold'))
                        
                e.grid(row=i, column=j, sticky = "EW") 
                    
                e.insert(tk.END, lst[i][j])
                e.configure(state="readonly") 
                
        self.Tree.grid_columnconfigure(0, weight=0)
        self.Tree.grid_columnconfigure(1, weight=3)
        self.Tree.grid_columnconfigure(2, weight=2)
        for i in range(total_columns-3):
            self.Tree.grid_columnconfigure(i+3, weight = 1)
        
  
# test data 
lest = [ 
        (1,'Raj Mumbai'," Rifle",19,15,250,400), 
       (2,'Aaryan Pune'," Rifle",18,100,300,500), 
       (3,'Vaishnavi Mumbai'," Rifle",20,155,300,560), 
       (4,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (4,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (4,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (5,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (5,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (5,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (5,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (5,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (5,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (5,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (13,'Shubham Delhi'," Rifle",21,122,300,741),
       (14,'Shubham Delhi'," Rifle",21,122,300,741),
       (14,'Shubham Delhi'," Rifle",21,122,300,741),
       (14,'Shubham Delhi'," Rifle",21,122,300,741),
       (14,'Shubham Delhi'," Rifle",21,122,300,741),
       (14,'Shubham Delhi'," Rifle",21,122,300,741),
       (14,'Shubham Delhi'," Rifle",21,122,300,741),
       (14,'Shubham Delhi'," Rifle",21,122,300,741),
       (15,'Shubham Delhi'," Rifle",21,122,300,741),
       (15,'Shubham Delhi'," Rifle",21,122,300,741),
       (15,'Shubham Delhi'," Rifle",21,122,300,741),
       (15,'Shubham Delhi'," Rifle",21,122,300,741),
       (16,'Shubham Delhi'," Rifle",21,122,300,741),
       (16,'Shubham Delhi'," Rifle",21,122,300,741),
       (16,'Shubham Delhi'," Rifle",21,122,300,741),
       (16,'Shubham Delhi'," Rifle",21,122,300,741),
       (17,'Shubham Delhi'," Rifle",21,122,300,741),
       (17,'Shubham Delhi'," Rifle",21,122,300,741),
       (17,'Shubham Delhi'," Rifle",21,122,300,741),
       (17,'Shubham Delhi'," Rifle",21,122,300,741),
       (18,'Shubham Delhi'," Rifle",21,122,300,741),
       (18,'Shubham Delhi'," Rifle",21,122,300,741),
       (19,'Shubham Delhi'," Rifle",21,122,300,741),
       (19,'Shubham Delhi'," Rifle",21,122,300,741),
       (19,'Shubham Delhi'," Rifle",21,122,300,741),
       (19,'Shubham Delhi'," Rifle",21,122,300,741)
       ]       


topframe = tk.Frame(root)
topframe.pack(fill=tk.X)
scrollingFrame = tk.Canvas(root)
scrollingFrame.pack(fill=tk.BOTH)

t = Table(scrollingFrame, lest)
t.Tree.pack(fill=tk.BOTH)

# SB = tk.Button(topframe, command=t.Tree.yview_moveto(fraction=50), text= "TWSTF")
SB = tk.Button(topframe, command=scrollingFrame.yview_moveto(fraction=50), text= "TWSTF")
SB.pack()   

root.mainloop()

Things I have tried:

  • Tried modifying self.Tree to be a Listbox in order to follow Volting's advice. When using Volting's code, it did not produce the desired result as instead, it's more of a "following" scroll rather than my desired effect. Plus it adds a scrollbar, which isn't apart of my design.
  • I've tried creating a method called "scroll" that does the same thing as the button in my example code and calling it after the Table has been populated. Same results as the button, nothing happened.
  • Modifying self.Tree to be a Listbox (again...) and then using the Listbox.see method so try and move the view to the end of the list box. Similar to this question and answer.
  • Setting Canvas.yview_moveto() and yview_scroll() to a button and a method, similar to this question. It produced the same effect as the current minimum amount of code, nothing happened.

Conclusion

I've had some practice in animating in Tkinter, so if I can get it to move just an inch, I'm sure I'll be able to get it to auto-scroll. My main focus right now is literally just to get the frame to move down. There can be anywhere from 2 to 500 or more entries, so I've already recreated this in a very O.O.P style, but it's a little large to post it.
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Jjaxs
  • 31
  • 3
  • You are creating a Treeview but not actually using the features of the Treeview. You can't scroll through items added with `grid`. Do you really need entry widgets for every cell, or is this for display only? If it's for display only, why aren't you using the `insert` method of the Treeview? – Bryan Oakley Jul 23 '20 at 18:53
  • Bryan, to expand on what you're saying, are you suggesting I format each line of test data and then insert each line into the Treeview using ~~~insert~~~? If so, reading the documentation – Jjaxs Jul 23 '20 at 19:21
  • Yes, you have to use insert your data into the treeview using treeview methods (assuming you don't actually need entry widgets). The same is true for a listbox, canvas, or any other widget that supports scrolling. – Bryan Oakley Jul 23 '20 at 19:29

1 Answers1

0

Okay! So, after a weekend of not looking at the code, I came back and realized a few things. First and foremost, I understood what Bryan was getting at and I decided to scrap using the Treeview. I want it to appear as if it is scrolling, rather than it actually scrolling. I'm not actually scrolling anything here, it's like in Star Wars where the text "scrolls" up the screen at the beginning.

For any future individuals who come across this wanting something similar to Star Wars where the text appears to float up the screen, or scroll up the screen, or any other type of keyword that boils down to text moving from top to bottom or bottom to top here is my implementation. It involves animation. Below is all the code you need to run and test this yourself in Python 3.X

# Python program to create a table 
   
import tkinter as tk
from tkinter import ttk
from ESTDisplayEntity import ESTDisplayEntity
  
class Table():
    def __init__(self, root):
        #This is the base frame that all Labels will be nested inside of
        self._movingFrame = tk.Frame(root)


    def populateTable(self, lst):
        # find total number of rows and 
        # columns in list 
        total_rows = len(lst) 
        total_columns = len(lst[0]) 

        #Magic number alert: This maxWidth is a magic number that just so happens to let the columns fill up the space needed for a 1920x1080 monitor
        #It has something to do with tkinter using character units as a measurement of width when text is involved.
        maxWidth = 200
        # code for creating table. Simple nested for loop for accessing all parts of a 2D array. 
        # our data is in the form of [[],[],[],[]]
        for i in range(total_rows): 
            for j in range(total_columns): 
                #Creating an anchor variable depending on what column we are inside of. 
                #Will look like this: [   1|Some Name Here         |Some club here        |  20  |  34  |  44  |  total  |]
                if j == 0:  
                    width = int(maxWidth*.03)
                    anchor = 'e'
                elif j == 1:
                    width = int(maxWidth*.40)
                    anchor = 'w'     
                elif j == 2:
                    width = int(maxWidth*0.25)
                    anchor = 'w'
                    
                else:
                    width = int(maxWidth*(.30/(total_columns-3)))
                    anchor = 'center'
                    
                        
                e = tk.Label(self._movingFrame, width=width, fg='Black', 
                                font=('Arial',16,'bold'),bd=1, relief='solid', text=lst[i][j], anchor=anchor)
                        
                e.grid(row=i, column=j) 
            
        #This adjusts how quickly each column adjusts. Currently the second and third row are important.
        #  as their weights are higher, so they will adjust faster. The first column does not adjust
        #  all columns after the club column will be lower than the club column but higher than the rank column
        #  so the score will be able to shrink and expand as needed. In the off chance numbers get ridicuously high.
        self._movingFrame.grid_columnconfigure(0, weight=0)
        self._movingFrame.grid_columnconfigure(1, weight=3)
        self._movingFrame.grid_columnconfigure(2, weight=2)
        for i in range(total_columns-3):
            self._movingFrame.grid_columnconfigure(i+3, weight = 1)
       
        # This is setting up and initializing the aniamtion variable
        self._rely_loc = 0.0

    def Start(self):
        self._rankedListAnimate()
      
    
    def _rankedListAnimationUpdate(self):
        #This is how much the frame will move given the next frame update, the smaller the better
        #  though it will slow down the animation the smaller you make it.
        #  Since we are using the "rely" option of .place everything is in percentage of the parent window.
        self._rely_loc += -0.001
        
        #Checks and balances to reset system when text scrolls off screen.
        if self._rely_loc >= 1.0:
            self._rely_loc = 0.0
        #Need to fix hard coded variable for larger lists
        elif self._movingFrame.winfo_rooty() < -900:
            self._rely_loc=0.999

        #Brains of operation, this .place method is where and how the text scrolls up the screen
        #  Using relwidth of 1.0, the widget will expand the whole screen, similar to .pack(fill=BOTH)
        self._movingFrame.place(anchor= tk.NW,relx=0, rely=self._rely_loc, relwidth= 1.0)
        #updating the frame just to be safe.
        self._movingFrame.update()

    #Recursive style function that calls itself with a .after method
    def _rankedListAnimate(self):        
        self._rankedListAnimationUpdate()
        #This is the heart of the operation, without the .after method, there is no animation
        #  the first variable is how often the second variable (usually a method) gets called in milliseconds
        #  setting this to be smaller makes the animation faster, making it slower makes the animation choppy.
        self._movingFrame.master.after(10, self._rankedListAnimate)
        
  
# test data 
lest = [ 
        (1,'Raj Mumbai'," Rifle",19,15,250,400), 
       (2,'Aaryan Pune'," Rifle",18,100,300,500), 
       (3,'Vaishnavi Mumbai'," Rifle",20,155,300,560), 
       (4,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (4,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (4,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (5,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (5,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (5,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (5,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (5,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (5,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (5,'Rachna Mumbai'," Rifle",21,125,300,450), 
       (13,'Shubham Delhi'," Rifle",21,122,300,741),
       (14,'Shubham Delhi'," Rifle",21,122,300,741),
       (14,'Shubham Delhi'," Rifle",21,122,300,741),
       (14,'Shubham Delhi'," Rifle",21,122,300,741),
       (14,'Shubham Delhi'," Rifle",21,122,300,741),
       (14,'Shubham Delhi'," Rifle",21,122,300,741),
       (14,'Shubham Delhi'," Rifle",21,122,300,741),
       (14,'Shubham Delhi'," Rifle",21,122,300,741),
       (15,'Shubham Delhi'," Rifle",21,122,300,741),
       (15,'Shubham Delhi'," Rifle",21,122,300,741),
       (15,'Shubham Delhi'," Rifle",21,122,300,741),
       (15,'Shubham Delhi'," Rifle",21,122,300,741),
       (16,'Shubham Delhi'," Rifle",21,122,300,741),
       (16,'Shubham Delhi'," Rifle",21,122,300,741),
       (16,'Shubham Delhi'," Rifle",21,122,300,741),
       (16,'Shubham Delhi'," Rifle",21,122,300,741),
       (17,'Shubham Delhi'," Rifle",21,122,300,741),
       (17,'Shubham Delhi'," Rifle",21,122,300,741),
       (17,'Shubham Delhi'," Rifle",21,122,300,741),
       (17,'Shubham Delhi'," Rifle",21,122,300,741),
       (18,'Shubham Delhi'," Rifle",21,122,300,741),
       (18,'Shubham Delhi'," Rifle",21,122,300,741),
       (19,'Shubham Delhi'," Rifle",21,122,300,741),
       (19,'Shubham Delhi'," Rifle",21,122,300,741),
       (19,'Shubham Delhi'," Rifle",21,122,300,741),
       (19,'Shubham Delhi'," Rifle",21,122,300,741)
       ]       




resolutionWidth = 1920
resolutionHeight = 1080
root = tk.Tk() 
root.geometry("{}x{}".format(resolutionWidth, resolutionHeight))
t= Table(root)

t.populateTable(lest)
t.Start()


root.mainloop()



This code should produce this.

Hope this helps anyone out there trying to make something similar to scrollable text, without using Treeview, listbox, or canvas.

Jjaxs
  • 31
  • 3