【问题标题】:Autohide Tkinter canvas scrollbar with pack geometry使用包几何图形自动隐藏 Tkinter 画布滚动条
【发布时间】:2017-04-26 23:54:31
【问题描述】:

我需要在不需要时自动隐藏 tkinter 滚动条的帮助。我从 effbot.org 找到了自动隐藏滚动条但仅使用网格几何的代码。在我的情况下,我没有使用网格几何。这是我的代码。

import Tkinter as tk
from Tkinter import *
import tkFont


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"


class MainWindow(tk.Tk):

    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        w = 1200
        h = 650
        x = self.winfo_screenwidth()/2 - w/2
        y = self.winfo_screenheight()/2 - h/2
        self.geometry("%ix%i+%i+%i" % (w, h, x, y))

        self.mainTopFrame = Frame(self, height=75)
        self.mainTopFrame.pack(side=TOP, fill=X)
        self.canvas = Canvas(self, borderwidth=0, bg='#ffffff')
        self.mainBottomFrame = Frame(self.canvas, bg='#000000')
        self.yscroll = Scrollbar(self.canvas, orient=VERTICAL, command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.yscroll.set)
        self.canvas.pack(side=TOP, fill=BOTH, expand=1)
        self.yscroll.pack(side=RIGHT, fill=Y)
        self.canvas.create_window((4,4), window=self.mainBottomFrame, anchor=NW)
        self.mainBottomFrame.bind("<Configure>", self.onFrameConfigure)
        self.menuFrame = Frame(self.mainTopFrame, bg='#545454')
        self.menuFrame.pack(side=TOP, fill=BOTH, expand=True)

        self.container = Frame(self.mainBottomFrame)
        self.container.pack(side=TOP, fill=BOTH, expand=True)
        self.frames = {}
        for F in (MonitorPage, PlanPage, DataLogPage, HelpPage):
            self.frame = F(self.container, self)
            self.frames[F] = self.frame
            self.frame.grid(row=0, column=0, sticky="nsew")
        self.show_frame(MonitorPage)

    def show_frame(self, cont):
        self.frame = self.frames[cont]
        self.frame.tkraise()

    def onFrameConfigure(self, event):
        '''Reset the scroll region to encompass the inner frame'''
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

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"

class MonitorPage(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)


        self.labelFont = tkFont.Font(family="Fixedsys", size=15, weight=tkFont.BOLD)


        self.leftFrame0 = Frame(self, bg='#888888')
        self.leftFrame0.pack(side=LEFT, fill=BOTH)
        self.rightFrame0 = Frame(self, bg='#888888')
        self.rightFrame0.pack(side=RIGHT, fill=BOTH)
        self.upLftFrame0 = Frame(self.leftFrame0)
        self.upLftFrame0.pack(side=TOP, fill=BOTH, padx=10, pady=10)
        self.dnLftFrame0 = Frame(self.leftFrame0)
        self.dnLftFrame0.pack(side=BOTTOM, fill=BOTH, padx=10, pady=10)
        self.upLftLblFrame0 = tk.LabelFrame(self.upLftFrame0)
        self.upLftLblFrame0.pack(side=TOP, fill=BOTH, padx=5, pady=5)
        self.dnLftLblFrame0 = tk.LabelFrame(self.dnLftFrame0)
        self.dnLftLblFrame0.pack(side=BOTTOM, fill=BOTH, padx=5, pady=5)
        self.rtLblFrame0 = tk.LabelFrame(self.rightFrame0)
        self.rtLblFrame0.pack(side=TOP, fill=BOTH, padx=10, pady=10)



        self.label0 = Label(self.rtLblFrame0, height=40, width=70)
        self.label0.pack()
        self.label1 = Label(self.upLftLblFrame0, height=25, width=115)
        self.label1.pack()
        self.label2 = Label(self.dnLftLblFrame0, height=10, width=115)
        self.label2.pack()


class PlanPage(tk.Frame, MainWindow):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

class DataLogPage(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

class HelpPage(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)


if __name__ == '__main__':
    rungui = MainWindow()
    rungui.mainloop()

因此,即使在使用包几何图形时,我也想自动隐藏滚动条。我希望我的问题很清楚。我对 python 和 tkinter 很陌生。

【问题讨论】:

    标签: python user-interface tkinter scrollbar


    【解决方案1】:

    我将 effbot.org 中的示例改编为 pack 方法:

    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.pack_forget()
            else:
                if self.cget("orient") == HORIZONTAL:
                    self.pack(fill=X)
                else:
                    self.pack(fill=Y)
            Scrollbar.set(self, lo, hi)
        def grid(self, **kw):
            raise TclError, "cannot use grid with this widget"
        def place(self, **kw):
            raise TclError, "cannot use place with this widget"
    
    # create scrolled canvas
    
    root = Tk()
    
    hscrollbar = AutoScrollbar(root, orient=HORIZONTAL)
    canvas = Canvas(root,
                    xscrollcommand=hscrollbar.set)
    canvas.pack(side=TOP, fill=BOTH, expand=True)
    hscrollbar.pack()
    
    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)
    
    rows = 5
    for i in range(1,rows):
        for j in range(1,10):
            button = Button(frame, padx=7, pady=7, text="[%d,%d]" % (i,j))
            button.grid(row=i, column=j, sticky='news')
    
    canvas.create_window(0, 0, anchor=NW, window=frame)
    
    frame.update_idletasks()
    
    canvas.config(scrollregion=canvas.bbox("all"))
    
    root.mainloop()
    

    【讨论】:

    • 我试过你的代码。它确实与包一起工作。但是为什么滚动条会出现在中间呢?
    • 我不习惯pack方法,我通常使用grid,但我终于确定了我的错误,滚动条的`expand = True`是罪魁祸首(我已经编辑了代码)。
    • 我实际上是通过使用 'anchor=E' 解决的。无论如何,非常感谢!
    【解决方案2】:

    我改编自 j_4321:

    from functools import partial
    import tkinter as tk
    
    
    class AutoScrollbar(tk.Scrollbar):
        def __init__(self, master, **kwargs):
            super().__init__(master, **kwargs)
            self.geometry_manager_add = lambda: None
            self.geometry_manager_forget = lambda: None
    
        def set(self, lo, hi):
            if float(lo) <= 0.0 and float(hi) >= 1.0:
                self.geometry_manager_forget()
            else:
                self.geometry_manager_add()
            super().set(lo, hi)
    
        def grid(self, **kwargs):
            self.geometry_manager_add = partial(super().grid, **kwargs)
            self.geometry_manager_forget = super().grid_forget
    
        def pack(self, **kwargs):
            self.geometry_manager_add = partial(super().pack, **kwargs)
            self.geometry_manager_forget = super().pack_forget
    
        def place(self, **kwargs):
            self.geometry_manager_add = partial(super().place, **kwargs)
            self.geometry_manager_forget = super().place_forget
    

    现在它适用于所有几何管理器。它会记住传入的kwargs,并在显示滚动条时始终传递它们。

    【讨论】:

      【解决方案3】:

      TheLizzard 的回答很好,但是当用户调整大小到滚动条开始在可见和不可见之间摆动的区域时,它会被锁定。这可以通过引入一个简单的时间延迟来解决(我还添加了一点,以便用户可以检查滚动条的可见性,因为我没有从 Scrollbar.info_ismapped() 获得好的结果):

      class AutoScrollbar(Scrollbar):
      """A subclass of `tkinter.Scrollbar` that automatically hides and shows itself as needed. If you need to
      find out if an instance of this class is currently visible, check it's :py:attr:`visible` attribute.
      """
      def __init__(self, master, **kwargs):
          ""
          super().__init__(master, **kwargs)
          self.geometry_manager_add = lambda: None # replaced by grid(), pack() or place()
          self.geometry_manager_forget = lambda: None
          self.visible:bool = False # needed because super().winfo_ismapped() doesn't work as expected... 
          "True iff the scrollbar is visible. Users should treat this as a read-only variable; it is only set by this class."
          self._delayTime = time.time()
      
      @override
      def set(self, lo, hi):
          if self._delay(): # do these checks only after a threshold time limit to prevent runaway oscilations in scrollbar visibility.
              if float(lo) <= 0.0 and float(hi) >= 1.0:
                  if self.visible:  # avoid calling the geometry manager too often
                      self.geometry_manager_forget()
                      self.visible = False
              else:
                  if not self.visible: # avoid calling the geometry manager too often
                      self.geometry_manager_add()
                      self.visible = True
          super().set(lo, hi)
          
      def _delay(self) -> bool:
          """This method prevents locking up due to bouncing between showing and hiding the scroll
          bars.  It should be called as part of the conditional to change the state of the scroll bars.
          It works by introducing a one second delay between state changes giving the user time to drag
          by the 'purgatory' in-between state, and at least preventing runaway oscilations from 
          locking up the GUI""" 
          now = time.time()
          if now - self._delayTime > 1.0:
              self._delayTime = now
              return True
          else:
              return False
      
      @override
      def grid(self, **kwargs):
          self.geometry_manager_add = partial(super().grid, **kwargs)
          self.geometry_manager_forget = super().grid_forget
      
      @override
      def pack(self, **kwargs):
          self.geometry_manager_add = partial(super().pack, **kwargs)
          self.geometry_manager_forget = super().pack_forget
      
      @override
      def place(self, **kwargs):
          self.geometry_manager_add = partial(super().place, **kwargs)
          self.geometry_manager_forget = super().place_forget
      

      【讨论】:

        猜你喜欢
        • 2017-07-03
        • 2011-12-05
        • 2020-12-02
        • 2019-06-04
        • 1970-01-01
        • 2014-06-04
        • 2014-03-25
        • 2019-11-23
        • 2016-05-24
        相关资源
        最近更新 更多