2013-04-24 137 views
29

我的目标是添加一个垂直滚动条到框架中,框架中有几个标签。一旦框架内的标签超过框架的高度,滚动条应自动启用。经过搜索,我发现this有用的职位。基于这篇文章,我明白,为了实现我想要的,(纠正我,如果我错了,我是一个初学者)我必须先创建一个框架,然后在该框架内创建一个画布,并将滚动条粘到那框架以及。之后,创建另一个框架并将其作为窗口对象放在画布内。所以,我终于想出这个框架的Python Tkinter滚动条

from Tkinter import * 

def data(): 
    for i in range(50): 
     Label(frame,text=i).grid(row=i,column=0) 
     Label(frame,text="my text"+str(i)).grid(row=i,column=1) 
     Label(frame,text="..........").grid(row=i,column=2) 

def myfunction(event): 
    canvas.configure(scrollregion=canvas.bbox("all"),width=200,height=200) 

root=Tk() 
sizex = 800 
sizey = 600 
posx = 100 
posy = 100 
root.wm_geometry("%dx%d+%d+%d" % (sizex, sizey, posx, posy)) 

myframe=Frame(root,relief=GROOVE,width=50,height=100,bd=1) 
myframe.place(x=10,y=10) 

canvas=Canvas(myframe) 
frame=Frame(canvas) 
myscrollbar=Scrollbar(myframe,orient="vertical",command=canvas.yview) 
canvas.configure(yscrollcommand=myscrollbar.set) 

myscrollbar.pack(side="right",fill="y") 
canvas.pack(side="left") 
canvas.create_window((0,0),window=frame,anchor='nw') 
frame.bind("<Configure>",myfunction) 
data() 
root.mainloop() 
  1. 我这样做对吗?有更好/更聪明的方法来实现这个代码给我的输出吗?
  2. 为什么我必须使用网格方法? (我尝试了放置方法,但没有任何标签出现在画布上。)
  3. 在画布上创建窗口时,使用anchor ='nw'有什么特别之处?

请保持您的答案简单,因为我是初学者。谢谢你的帮助。

+1

你拥有了它向后你的问题,尽管代码乍看上去是正确的。您必须创建一个框架,将其嵌入到画布中,然后将滚动条附加到画布上。 – 2013-04-24 11:07:37

+2

[将滚动条添加到Tkinter中的窗口小部件的网格]的可能的重复(http://stackoverflow.com/questions/3085696/adding-a-scrollbar-to-a-grid-of-widgets-in-tkinter) – 2013-08-26 15:56:49

+0

@TrevorBoydSmith有很多东西这是一个潜在的重复,但我投票关闭这是一个不同的似乎有最好的答案的副本:http://stackoverflow.com/questions/1873575/how-could -i-get-a-frame-with-a-scrollbar-in-tkinter – ArtOfWarfare 2015-05-03 15:59:34

回答

14

我在做对吧?有没有更好/更聪明的方法来实现这个代码给我的输出?

一般来说,是的,你做对了。除了画布以外,Tkinter没有本地可滚动容器。正如你所看到的,设置起来并不难。正如你的例子所示,它只需要5或6行代码就可以工作 - 这取决于你如何计算行数。

我为什么非要用网格法?(我想地方的方法,但没有一个标签出现在画布上的?)

你问为什么你必须使用网格。没有要求使用网格。地方,网格和包装都可以使用。只是有些更适合特定类型的问题。在这种情况下,它看起来像是在创建一个实际的网格 - 标签的行和列 - 所以网格是自然选择。

在canvas上创建窗口时,使用anchor ='nw'有什么特别之处?

锚点告诉你窗口的哪个部分位于你给出的坐标上。默认情况下,窗口的中心将放置在坐标处。在上面的代码中,您希望左上角(“西北角”)位于坐标上。

28

请注意,所提出的代码只能与Python 2

这里有效是一个例子:

from Tkinter import * # from x import * is bad practice 
from ttk import * 

# http://tkinter.unpythonic.net/wiki/VerticalScrolledFrame 

class VerticalScrolledFrame(Frame): 
    """A pure Tkinter scrollable frame that actually works! 
    * Use the 'interior' attribute to place widgets inside the scrollable frame 
    * Construct and pack/place/grid normally 
    * This frame only allows vertical scrolling 

    """ 
    def __init__(self, parent, *args, **kw): 
     Frame.__init__(self, parent, *args, **kw)    

     # create a canvas object and a vertical scrollbar for scrolling it 
     vscrollbar = Scrollbar(self, orient=VERTICAL) 
     vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE) 
     canvas = Canvas(self, bd=0, highlightthickness=0, 
         yscrollcommand=vscrollbar.set) 
     canvas.pack(side=LEFT, fill=BOTH, expand=TRUE) 
     vscrollbar.config(command=canvas.yview) 

     # reset the view 
     canvas.xview_moveto(0) 
     canvas.yview_moveto(0) 

     # create a frame inside the canvas which will be scrolled with it 
     self.interior = interior = Frame(canvas) 
     interior_id = canvas.create_window(0, 0, window=interior, 
              anchor=NW) 

     # track changes to the canvas and frame width and sync them, 
     # also updating the scrollbar 
     def _configure_interior(event): 
      # update the scrollbars to match the size of the inner frame 
      size = (interior.winfo_reqwidth(), interior.winfo_reqheight()) 
      canvas.config(scrollregion="0 0 %s %s" % size) 
      if interior.winfo_reqwidth() != canvas.winfo_width(): 
       # update the canvas's width to fit the inner frame 
       canvas.config(width=interior.winfo_reqwidth()) 
     interior.bind('<Configure>', _configure_interior) 

     def _configure_canvas(event): 
      if interior.winfo_reqwidth() != canvas.winfo_width(): 
       # update the inner frame's width to fill the canvas 
       canvas.itemconfigure(interior_id, width=canvas.winfo_width()) 
     canvas.bind('<Configure>', _configure_canvas) 


if __name__ == "__main__": 

    class SampleApp(Tk): 
     def __init__(self, *args, **kwargs): 
      root = Tk.__init__(self, *args, **kwargs) 


      self.frame = VerticalScrolledFrame(root) 
      self.frame.pack() 
      self.label = Label(text="Shrink the window to activate the scrollbar.") 
      self.label.pack() 
      buttons = [] 
      for i in range(10): 
       buttons.append(Button(self.frame.interior, text="Button " + str(i))) 
       buttons[-1].pack() 

    app = SampleApp() 
    app.mainloop() 

这还没有绑定到滚动鼠标滚轮,但它是可能的。虽然滚动滚动可能会有点颠簸。

编辑:

1)
恕我直言,滚动帧是有点棘手的Tkinter的似乎并没有做很多。似乎没有优雅的方式来做到这一点。
您的代码的一个问题是您必须手动设置画布大小 - 这就是我发布的示例代码解决的问题。

to 2)
你说的是数据功能? Place也适合我。 (通常我更喜欢网格)。

to 3)
好吧,它将窗口放置在画布上。

我注意到的一件事是,您的示例默认处理鼠标滚轮滚动,而我发布的不是。将不得不看一段时间。

+0

即使你没有回答我的问题,谢谢你的例子。但我不愿意为一个带有滚动的框架编写多行代码当我的整个脚本比你的例子短。实际上我的示例代码会做的伎俩,但我不知道为什么我不能使用place方法。 – 2013-04-25 08:29:58

+1

@ChrisAung:这个解决方案的好处在于它有一个可重用的类“VerticalScrolledFrame”,您可以用它来替代任何“Frame”。在解决方案中使用代码时,需要为每个想要滚动的“Frame”重写所有代码。有了这个代码,它几乎可以替代'Frame'。所以不要因为线数而不愿意使用它。如果你只需要一个可滚动的框架,他的解决方案是更多的代码。如果你需要两种技术,两种技术都需要相同数量的代码,而且他的代码更易于维护(减少冗余)。 – ArtOfWarfare 2015-05-02 15:15:04

+0

从上面继续:如果你需要2个以上的可滚动框架,他的解决方案需要的代码少得多,而且更容易保持。说了这么多,我不会因为它的警告而使用这个解决方案。此外,我不喜欢使用'.interior'的要求 - 我想要一个使'.interior'成为实现细节的接口,而不是使用它的人必须知道的东西。 – ArtOfWarfare 2015-05-02 15:18:28

2

即使不使用画布,我们也可以添加滚动条。我在其他许多帖子中看过它,我们不能直接在框架中添加垂直滚动条等等。但经过多次实验发现添加垂直和水平滚动条的方式:)。请在下面的代码中找到用于在treeView和frame中创建滚动条的代码。

f = Tkinter.Frame(self.master,width=3) 
f.grid(row=2, column=0, columnspan=8, rowspan=10, pady=30, padx=30) 
f.config(width=5) 
self.tree = ttk.Treeview(f, selectmode="extended") 
scbHDirSel =tk.Scrollbar(f, orient=Tkinter.HORIZONTAL, command=self.tree.xview) 
scbVDirSel =tk.Scrollbar(f, orient=Tkinter.VERTICAL, command=self.tree.yview) 
self.tree.configure(yscrollcommand=scbVDirSel.set, xscrollcommand=scbHDirSel.set)   
self.tree["columns"] = (self.columnListOutput) 
self.tree.column("#0", width=40) 
self.tree.heading("#0", text='SrNo', anchor='w') 
self.tree.grid(row=2, column=0, sticky=Tkinter.NSEW,in_=f, columnspan=10, rowspan=10) 
scbVDirSel.grid(row=2, column=10, rowspan=10, sticky=Tkinter.NS, in_=f) 
scbHDirSel.grid(row=14, column=0, rowspan=2, sticky=Tkinter.EW,in_=f) 
f.rowconfigure(0, weight=1) 
f.columnconfigure(0, weight=1) 
+0

我不明白这些答案使用“Tkinter”,“tk”和“ttk”。我希望在每个例子中只有一个这样的前缀,但第三个都有三个。是什么赋予了? – 4dummies 2017-12-16 22:14:34

7

请看我的课是一个可滚动的框架。它的垂直滚动条也被绑定到<Mousewheel>事件。所以,你所要做的就是创建一个框架,用你喜欢的方式填充小部件,然后将这个框架设为我的ScrolledWindow.scrollwindow的子项。随意询问是否有不清楚的地方。

用过很多来自@ Brayan奥克利的回答接近这个质疑

class ScrolledWindow(tk.Frame): 
    """ 
    1. Master widget gets scrollbars and a canvas. Scrollbars are connected 
    to canvas scrollregion. 

    2. self.scrollwindow is created and inserted into canvas 

    Usage Guideline: 
    Assign any widgets as children of <ScrolledWindow instance>.scrollwindow 
    to get them inserted into canvas 

    __init__(self, parent, canv_w = 400, canv_h = 400, *args, **kwargs) 
    docstring: 
    Parent = master of scrolled window 
    canv_w - width of canvas 
    canv_h - height of canvas 

    """ 


    def __init__(self, parent, canv_w = 400, canv_h = 400, *args, **kwargs): 
     """Parent = master of scrolled window 
     canv_w - width of canvas 
     canv_h - height of canvas 

     """ 
     super().__init__(parent, *args, **kwargs) 

     self.parent = parent 

     # creating a scrollbars 
     self.xscrlbr = ttk.Scrollbar(self.parent, orient = 'horizontal') 
     self.xscrlbr.grid(column = 0, row = 1, sticky = 'ew', columnspan = 2)   
     self.yscrlbr = ttk.Scrollbar(self.parent) 
     self.yscrlbr.grid(column = 1, row = 0, sticky = 'ns')   
     # creating a canvas 
     self.canv = tk.Canvas(self.parent) 
     self.canv.config(relief = 'flat', 
         width = 10, 
         heigh = 10, bd = 2) 
     # placing a canvas into frame 
     self.canv.grid(column = 0, row = 0, sticky = 'nsew') 
     # accociating scrollbar comands to canvas scroling 
     self.xscrlbr.config(command = self.canv.xview) 
     self.yscrlbr.config(command = self.canv.yview) 

     # creating a frame to inserto to canvas 
     self.scrollwindow = ttk.Frame(self.parent) 

     self.canv.create_window(0, 0, window = self.scrollwindow, anchor = 'nw') 

     self.canv.config(xscrollcommand = self.xscrlbr.set, 
         yscrollcommand = self.yscrlbr.set, 
         scrollregion = (0, 0, 100, 100)) 

     self.yscrlbr.lift(self.scrollwindow)   
     self.xscrlbr.lift(self.scrollwindow) 
     self.scrollwindow.bind('<Configure>', self._configure_window) 
     self.scrollwindow.bind('<Enter>', self._bound_to_mousewheel) 
     self.scrollwindow.bind('<Leave>', self._unbound_to_mousewheel) 

     return 

    def _bound_to_mousewheel(self, event): 
     self.canv.bind_all("<MouseWheel>", self._on_mousewheel) 

    def _unbound_to_mousewheel(self, event): 
     self.canv.unbind_all("<MouseWheel>") 

    def _on_mousewheel(self, event): 
     self.canv.yview_scroll(int(-1*(event.delta/120)), "units") 

    def _configure_window(self, event): 
     # update the scrollbars to match the size of the inner frame 
     size = (self.scrollwindow.winfo_reqwidth(), self.scrollwindow.winfo_reqheight()) 
     self.canv.config(scrollregion='0 0 %s %s' % size) 
     if self.scrollwindow.winfo_reqwidth() != self.canv.winfo_width(): 
      # update the canvas's width to fit the inner frame 
      self.canv.config(width = self.scrollwindow.winfo_reqwidth()) 
     if self.scrollwindow.winfo_reqheight() != self.canv.winfo_height(): 
      # update the canvas's width to fit the inner frame 
      self.canv.config(height = self.scrollwindow.winfo_reqheight()) 
+1

小bug,你的最后一行应读作self.canv.config(高度= ...)' – 2016-07-21 20:22:30

+0

@AdamSmith感谢,增加了一个修正 – 2016-07-25 06:21:48

+0

你是什么意思,“使这个框架我ScrolledWindow.scrollwindow的孩子”。你能给个例子吗? – Jacob 2018-01-29 22:05:50