Any idea why Tkinter scrollbar scrolls but contents on canvas didn't move together?

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • Haiyan
    New Member
    • Jul 2010
    • 17

    Any idea why Tkinter scrollbar scrolls but contents on canvas didn't move together?

    Dear experts:

    I am trying to add one xy-scrollbar on the canvas, then put lots of check buttons on the canvas. I got at least following 2 problems:

    1.I can see the y-scroll bar but contents on canvas didn't move together when scroll y-scrollbar.
    2.The x-scrollbar didn't show up as expected.

    Do you have any idea? Thank you very much in advance!

    Best regards,
    Haiyan

    Following is my code and you can run it directly to show the 2 problems above:
    Code:
    from Tkinter import *
    from tkMessageBox import *
    
    class BoatManager( Frame ):
    
       def __init__( self, name, cols, rows ):
    
          Frame.__init__( self )
          self.name = name
          self.cols = cols
          self.rows = rows
          self.master.title( "Apprentice" )
    
          # main frame fills entire container, expands if necessary
          self.master.rowconfigure( 0, weight = 1 )
          self.master.columnconfigure( 0, weight = 1 )
          self.grid( sticky = W+E+N+S )
    
          # Scroll bar
          xscrollbar = Scrollbar(self, orient=HORIZONTAL)
          xscrollbar.grid(row=1, column=0, sticky=E+W)
    
          yscrollbar = Scrollbar(self)
          yscrollbar.grid(row=0, column=1, sticky=N+S)
    
          # create Canvas component
          self.canvas = Canvas(self, bd=0, scrollregion=(0, 0, 1000, 5000),
                          yscrollcommand=yscrollbar.set,
                          xscrollcommand=xscrollbar.set)
          self.canvas.grid(row=0, column=0, sticky=N+S+E+W)
    
          xscrollbar.config(command=self.canvas.xview)
          yscrollbar.config(command=self.canvas.yview)
    
          # make the canvas expandable
          self.canvas.grid_rowconfigure(0, weight=1)
          self.canvas.grid_columnconfigure(0, weight=1)
    
          # Label for name
          self.Label1 = Label( self.canvas, width = 40, height = 2, text = self.name, font = "Arial 20" )
          self.Label1.grid( row = 0, rowspan = 1, column = 1, columnspan =self.cols+1, sticky = W+E+N+S )
    
          # Create one button object and one variable for all the buttons
          self.Button_Name = [[Checkbutton() for i in range(self.rows)] for j in range(self.cols)]
          self.buttonChecked = [[BooleanVar() for i in range(self.rows)] for j in range(self.cols)]
          self.buttonNameVar = [[StringVar() for i in range(self.rows)] for j in range(self.cols)]
    
          for boat_num in xrange(self.cols):
             for boat_slot in xrange(self.rows):
    
                # Freeze the column number to toggle whole column on or off
                def handler_col(i=boat_num):
                   return self.select_full_column(i)
    
                # Freeze the row number to toggle whole row on or off
                def handler_row(i=boat_slot):
                   return self.select_full_row(i)
    
                # For button Button_Name[0][0] to control all boats/slots
                if boat_slot == 0 and boat_num == 0:
                   self.Button_Name[boat_num][boat_slot] = Checkbutton(self.canvas,
                                                                      text = 'All Boats_Slots',
                                                                      padx=5, pady=5,
                                                                      relief = SUNKEN,
                                                                      width = 15,
                                                                      bg = 'Moccasin',
                                                                      variable = self.buttonChecked[boat_num][boat_slot],
                                                                      command = self.select_all_boats)
    
                # For button Button_Name[i][0], i is range(1,boat_num+1) to control all slots in boat i
                elif boat_slot == 0:
                   self.Button_Name[boat_num][boat_slot] = Checkbutton(self.canvas,
                                                                      text = 'Boat_' + str(boat_num),
                                                                      padx=5, pady=5,
                                                                      relief = SUNKEN,
                                                                      width = 15,
                                                                      bg = 'light yellow',
                                                                      variable = self.buttonChecked[boat_num][boat_slot],
                                                                      command = handler_col)
    
                # For button Button_Name[0][i], i is range(1,boat_slot+1) to control all slots in row i
                elif boat_num == 0:
                   self.Button_Name[boat_num][boat_slot] = Checkbutton(self.canvas,
                                                                      text = 'Slot_' + str(boat_slot),
                                                                      padx=5, pady=5,
                                                                      relief = SUNKEN,
                                                                      width = 15,
                                                                      bg = 'light yellow',
                                                                      variable = self.buttonChecked[boat_num][boat_slot],
                                                                      command = handler_row)
    
                # For single button control
                else:
                   self.Button_Name[boat_num][boat_slot] = Checkbutton(self.canvas,
                                                                      textvariable = self.buttonNameVar[boat_num][boat_slot],
                                                                      padx=5, pady=5,
                                                                      relief = SUNKEN,
                                                                      variable = self.buttonChecked[boat_num][boat_slot],
                                                                      width = 15,
                                                                      command = self.select_current_slot)
    
                # Lay out check buttons with grid manager
                self.Button_Name[boat_num][boat_slot].grid(row = boat_slot+1, rowspan = 1, column = boat_num + 2, columnspan = 1)
    
       # Full boats/slots toggle function
       def select_all_boats(self):
          for boat_num in xrange(self.cols):
             for boat_slot in xrange(self.rows):
                if boat_slot != 0 and boat_num != 0:
                   self.Button_Name[boat_num][boat_slot].toggle()
    
       # The column toggle function
       def select_full_column(self, i):
          for j in xrange(1, self.rows):
             self.Button_Name[i][j].toggle()
    
       # The row toggle function
       def select_full_row(self, i):
          for j in xrange(1, self.cols):
             self.Button_Name[j][i].toggle()
    
       def select_current_slot(self):
          pass
    
    boat1 = BoatManager('haiyan', 10,100)
    boat1.mainloop()
  • Haiyan
    New Member
    • Jul 2010
    • 17

    #2
    I modified the scroll bar and canvas part a little bit from self to self.master as follows, and now x-scroll bar showed up as well, but contents on the canvas still doesn't move when scroll the scrollbar.
    Code:
          # Scroll bar
          xscrollbar = Scrollbar(self.master, orient=HORIZONTAL)
          xscrollbar.grid(row=1, column=0, sticky=E+W)
    
          yscrollbar = Scrollbar(self.master)
          yscrollbar.grid(row=0, column=1, sticky=N+S)
    
          # create Canvas component
          self.canvas = Canvas(self.master, bd=0, scrollregion=(0, 0, 1000, 5000),
                          xscrollcommand=xscrollbar.set,
                          yscrollcommand=yscrollbar.set)
          self.canvas.grid(row=0, column=0, sticky=N+S+E+W)

    Comment

    • bvdet
      Recognized Expert Specialist
      • Oct 2006
      • 2851

      #3
      Haiyan,

      I have never had much luck with scroll bars on canvases. I think you should add the table of CheckBoxes in a Frame and place the Frame on the canvas. The scrollbars also should be on the canvas. If I am able to make something work, I will post again. Please do the same.

      Regards,
      bvdet

      Comment

      • Haiyan
        New Member
        • Jul 2010
        • 17

        #4
        Hi, Bvdet:

        Thanks a lot for your help! I modified the script based on your suggestion, and looks like get it working. But I don't like the following implementation, and I hope to keep my original class definition because I need to use the scroll bar lots of places.

        Following is the working code but not in class definition:
        Code:
        from Tkinter import *
        
        class AutoScrollbar(Scrollbar):
            # a scrollbar that hides itself if it's not needed.  only
            # works if you use the grid geometry manager.
            def set(self, lo, hi):
                if float(lo) <= 0.0 and float(hi) >= 1.0:
                    # grid_remove is currently missing from Tkinter!
                    self.tk.call("grid", "remove", self)
                else:
                    self.grid()
                Scrollbar.set(self, lo, hi)
            def pack(self, **kw):
                raise TclError, "cannot use pack with this widget"
            def place(self, **kw):
                raise TclError, "cannot use place with this widget"
        
        # create scrolled canvas
        
        root = Tk()
        
        vscrollbar = AutoScrollbar(root)
        vscrollbar.grid(row=0, column=1, sticky=N+S)
        hscrollbar = AutoScrollbar(root, orient=HORIZONTAL)
        hscrollbar.grid(row=1, column=0, sticky=E+W)
        
        canvas = Canvas(root,
                        yscrollcommand=vscrollbar.set,
                        xscrollcommand=hscrollbar.set)
        canvas.grid(row=0, column=0, sticky=N+S+E+W)
        
        vscrollbar.config(command=canvas.yview)
        hscrollbar.config(command=canvas.xview)
        
        # make the canvas expandable
        root.grid_rowconfigure(0, weight=1)
        root.grid_columnconfigure(0, weight=1)
        
        # create canvas contents
        
        frame = Frame(canvas)
        frame.rowconfigure(1, weight=1)
        frame.columnconfigure(1, weight=1)
        
        # Label for name
        Label1 = Label( frame, width = 40, height = 2, text = 'haiyan', font = "Arial 20" )
        Label1.grid( row = 0, rowspan = 1, column = 1, columnspan =6, sticky = W+E+N+S )
        
        rows = 100
        cols = 10
        # Create one button object and one variable for all the buttons
        Button_Name = [[Checkbutton() for i in range(rows)] for j in range(cols)]
        buttonChecked = [[BooleanVar() for i in range(rows)] for j in range(cols)]
        buttonNameVar = [[StringVar() for i in range(rows)] for j in range(cols)]
        
        for boat_num in xrange(cols):
           for boat_slot in xrange(rows):
        
              # Freeze the column number to toggle whole column on or off
              def handler_col(i=boat_num):
                 return select_full_column(i)
        
              # Freeze the row number to toggle whole row on or off
              def handler_row(i=boat_slot):
                 return select_full_row(i)
        
              # For button Button_Name[0][0] to control all boats/slots
              if boat_slot == 0 and boat_num == 0:
                 Button_Name[boat_num][boat_slot] = Checkbutton(frame,
                                                                    text = 'All Boats_Slots',
                                                                    padx=5, pady=5,
                                                                    relief = SUNKEN,
                                                                    width = 15,
                                                                    bg = 'Moccasin',
                                                                    variable = buttonChecked[boat_num][boat_slot],)
        ##                                                            command = select_all_boats)
        
              # For button Button_Name[i][0], i is range(1,boat_num+1) to control all slots in boat i
              elif boat_slot == 0:
                 Button_Name[boat_num][boat_slot] = Checkbutton(frame,
                                                                    text = 'Boat_' + str(boat_num),
                                                                    padx=5, pady=5,
                                                                    relief = SUNKEN,
                                                                    width = 15,
                                                                    bg = 'light yellow',
                                                                    variable = buttonChecked[boat_num][boat_slot],)
        ##                                                            command = handler_col)
        
              # For button Button_Name[0][i], i is range(1,boat_slot+1) to control all slots in row i
              elif boat_num == 0:
                 Button_Name[boat_num][boat_slot] = Checkbutton(frame,
                                                                    text = 'Slot_' + str(boat_slot),
                                                                    padx=5, pady=5,
                                                                    relief = SUNKEN,
                                                                    width = 15,
                                                                    bg = 'light yellow',
                                                                    variable = buttonChecked[boat_num][boat_slot],)
        ##                                                            command = handler_row)
        
              # For single button control
              else:
                 Button_Name[boat_num][boat_slot] = Checkbutton(frame,
                                                                    textvariable = buttonNameVar[boat_num][boat_slot],
                                                                    padx=5, pady=5,
                                                                    relief = SUNKEN,
                                                                    variable = buttonChecked[boat_num][boat_slot],
                                                                    width = 15,)
        ##                                                            command = select_current_slot)
        
              # Lay out check buttons with grid manager
              Button_Name[boat_num][boat_slot].grid(row = boat_slot+1, rowspan = 1, column = boat_num + 2, columnspan = 1)
        
        
        canvas.create_window(0, 0, anchor=NW, window=frame)
        
        frame.update_idletasks()
        
        canvas.config(scrollregion=canvas.bbox("all"))
        
        root.mainloop()
        Thanks a lot!

        Haiyan

        Comment

        • bvdet
          Recognized Expert Specialist
          • Oct 2006
          • 2851

          #5
          Thanks Haiyan for posting the solution. I learned something from this!

          Comment

          • Haiyan
            New Member
            • Jul 2010
            • 17

            #6
            Hi, Bvdet:

            You are welcome! But I still have trouble to merge into my original class definition. You know the solution I post above is to create one canvas with scrollbar, then create one frame on the canvas, then put the table of checkbox into the frame.

            When I am trying to merge into my original class definition. I didn't understand the relationship of self, self.master and canvas and didn't get it implemented correctly. Please excuse me I didn't read some documents about Python GUI yet, and it is my first time to write Python GUI.

            Could you please give me some hint so I can put the scrollbar implementation in my original class? I hope to understand it because I need the scrollbar implementation of lots of places, some of them are not in the main GUI.

            Thanks a lot!

            Comment

            • bvdet
              Recognized Expert Specialist
              • Oct 2006
              • 2851

              #7
              I think the value of each boat position in the table should be the dependent on the value of the controlling CheckBoxes instead of a toggle. It can be done like this:
              Code:
                  # Full boats/slots toggle function
                  def select_all_boats(self):
                      for boat_num in xrange(1, self.cols):
                          for boat_slot in xrange(1, self.rows):
                              self.buttonChecked[boat_num][boat_slot].set(self.buttonChecked[0][0].get())
              I have taken the liberty of incorporating this method into the following code:
              Code:
              from Tkinter import *
              
              '''
              Autohiding Scrollbars
              August 08, 1998 | Fredrik Lundh
              '''
              class AutoScrollbar(Scrollbar):
                  # a scrollbar that hides itself if it's not needed.  only
                  # works if you use the grid geometry manager.
                  def set(self, lo, hi):
                      if float(lo) <= 0.0 and float(hi) >= 1.0:
                          self.grid_remove()
                      else:
                          self.grid()
                      Scrollbar.set(self, lo, hi)
                  def pack(self, **kw):
                      raise TclError, "cannot use pack with this widget"
                  def place(self, **kw):
                      raise TclError, "cannot use place with this widget"
               
              class BoatManager(object):
               
                  def __init__( self, master, name, cols, rows ):
                      self.master = master
                      self.name = name
                      self.cols = cols
                      self.rows = rows
                      self.master.title( "Apprentice" )
              
                      vscrollbar = AutoScrollbar(root)
                      vscrollbar.grid(row=0, column=1, sticky=N+S)
                      hscrollbar = AutoScrollbar(root, orient=HORIZONTAL)
                      hscrollbar.grid(row=1, column=0, sticky=E+W)
                     
                      canvas = Canvas(root,
                                      yscrollcommand=vscrollbar.set,
                                      xscrollcommand=hscrollbar.set)
                      canvas.grid(row=0, column=0, sticky=N+S+E+W)
                     
                      vscrollbar.config(command=canvas.yview)
                      hscrollbar.config(command=canvas.xview)
                     
                      # make the canvas expandable
                      root.grid_rowconfigure(0, weight=1)
                      root.grid_columnconfigure(0, weight=1)
                     
                      # create canvas contents
                     
                      frame = Frame(canvas)
                      frame.rowconfigure(1, weight=1)
                      frame.columnconfigure(1, weight=1)
                     
                      Label1 = Label( frame, width=40, height=2, text=self.name, font="Arial 20" )
                      Label1.grid( row=0, rowspan=1, column=1, columnspan=6, sticky=W+E+N+S )
              
                      # Create one button object and one variable for each button
                      self.Button_Name = [[Checkbutton() for i in range(rows)] for j in range(cols)]
                      self.buttonChecked = [[BooleanVar() for i in range(rows)] for j in range(cols)]
                      self.buttonNameVar = [[StringVar() for i in range(rows)] for j in range(cols)]
                     
                      for boat_num in xrange(cols):
                          for boat_slot in xrange(rows):
                     
                              # Freeze the column number to turn whole column on or off
                              def handler_col(i=boat_num):
                                 return self.select_full_column(i)
                     
                              # Freeze the row number to turn whole row on or off
                              def handler_row(i=boat_slot):
                                 return self.select_full_row(i)
                         
                              # For button Button_Name[0][0] to control all boats/slots
                              if boat_slot == 0 and boat_num == 0:
                                  self.Button_Name[0][0] = Checkbutton(frame,
                                                                         text='All Boats_Slots',
                                                                         padx=5, pady=5,
                                                                         relief = SUNKEN,
                                                                         width = 15,
                                                                         bg = 'Moccasin',
                                                                         variable=self.buttonChecked[0][0],
                                                                         command=self.select_all_boats)
                         
                              # For button Button_Name[i][0], i is range(1,boat_num+1) to control all slots in boat i
                              elif boat_slot == 0:
                                  self.Button_Name[boat_num][boat_slot] = Checkbutton(frame,
                                                                                 text='Boat_' + str(boat_num),
                                                                                 padx=5, pady=5,
                                                                                 relief = SUNKEN,
                                                                                 width = 15,
                                                                                 bg='light yellow',
                                                                                 variable = self.buttonChecked[boat_num][boat_slot],
                                                                                 command=handler_col)
                         
                              # For button Button_Name[0][i], i is range(1,boat_slot+1) to control all slots in row i
                              elif boat_num == 0:
                                  self.Button_Name[boat_num][boat_slot] = Checkbutton(frame,
                                                                                 text='Slot_' + str(boat_slot),
                                                                                 padx=5, pady=5,
                                                                                 relief=SUNKEN,
                                                                                 width=15,
                                                                                 bg='light yellow',
                                                                                 variable=self.buttonChecked[boat_num][boat_slot],
                                                                                 command=handler_row)
                         
                              # For single button control
                              else:
                                  self.Button_Name[boat_num][boat_slot] = Checkbutton(frame,
                                                                                 textvariable=buttonNameVar[boat_num][boat_slot],
                                                                                 padx=5, pady=5,
                                                                                 relief=SUNKEN,
                                                                                 variable=self.buttonChecked[boat_num][boat_slot],
                                                                                 width=15,
                                                                                 command=self.select_current_slot)
                         
                              # Lay out check buttons with grid manager
                              self.Button_Name[boat_num][boat_slot].grid(row = boat_slot+1, rowspan = 1, column = boat_num + 2, columnspan = 1)
                     
                      canvas.create_window(0, 0, anchor=NW, window=frame)
                      frame.update_idletasks()
                      canvas.config(scrollregion=canvas.bbox("all"))
                    
                  # Full boats/slots control function
                  def select_all_boats(self):
                      for boat_num in xrange(1, self.cols):
                          for boat_slot in xrange(1, self.rows):
                              self.buttonChecked[boat_num][boat_slot].set(self.buttonChecked[0][0].get())
               
                  # The column control function
                  def select_full_column(self, i):
                      for j in xrange(1, self.rows):
                          self.buttonChecked[i][j].set(self.buttonChecked[i][0].get())
              
                  # The row control function
                  def select_full_row(self, i):
                      for j in xrange(1, self.cols):
                          self.buttonChecked[j][i].set(self.buttonChecked[0][i].get())
              
                  def select_current_slot(self):
                    pass
              
              
              if __name__ == "__main__":
                 
                 rows = 100
                 cols = 10
                 root = Tk()
                 boat1 = BoatManager(root, 'haiyan', cols, rows)
                 root.mainloop()
              We should give credit to Fredrik Lundh for the AutoScrollBar.

              Comment

              • Haiyan
                New Member
                • Jul 2010
                • 17

                #8
                Hi, Bvdet:

                Thanks a lot!

                Yes, the credit should give to both Fredrik Lundh and you!

                I also tried another piece of script and merged into the original class and it also works. The script is as follows:
                Code:
                      self.canv = Canvas ( self, width=1200, height=800,
                          scrollregion=(0, 0, 1200, 800) )
                      self.canv.grid ( row=0, column=0 )
                
                      self.scrollY = Scrollbar ( self, orient=VERTICAL,
                          command=self.canv.yview )
                      self.scrollY.grid ( row=0, column=1, sticky=N+S )
                
                      self.scrollX = Scrollbar ( self, orient=HORIZONTAL,
                          command=self.canv.xview )
                      self.scrollX.grid ( row=1, column=0, sticky=E+W )
                
                      self.canv["xscrollcommand"]  =  self.scrollX.set
                      self.canv["yscrollcommand"]  =  self.scrollY.set
                
                      self.frame = Frame(self.canv)
                      self.frame.rowconfigure(1, weight=1)
                      self.frame.columnconfigure(1, weight=1)
                Thanks again!

                Haiyan

                Comment

                Working...