【问题标题】:How to properly use grid in tkinter如何在 tkinter 中正确使用网格
【发布时间】:2021-10-13 02:05:46
【问题描述】:

我正在粘贴我一直在为“条形图”编写的所有代码。如果有兴趣,我可以提供我从中获取的原始代码。代码运行良好并按预期生成图表,但网格布局很奇怪。按钮和标签更新都在形状笨拙的“空间”中。我的假设是主画布区域占据了框架(窗口?)的很大一部分,所以当其他小部件添加到网格时,它们会按照更大的画布窗口放置。

有没有办法对正在绘制的画布框架/窗口进行子集化,这样它就不会影响其他小部件的大小?或者有没有更好的方法将所有东西放在一起?

如果我需要澄清任何事情,请告诉我。

谢谢!

编辑

这是图形用户界面的图像。首先,主要的绘图画布在网格(行=1,列=1)中。当我将按钮放在图的右侧时,它们位于同一行 = 1,但现在分别位于第 2 列和第 3 列。由于行高如此之大,因此按钮放置在该行的中心。在查看“Ch_A”和“Ch_B”标签时,按钮的列宽也会发生类似的情况。

理想情况下,我希望按钮和相关的“频道标签”彼此整齐排列。有没有最佳实践方法来解决这个问题?

编辑

# (c) MIT License Copyright 2014 Ronald H Longo
# Please reuse, modify or distribute freely.

from collections import OrderedDict
import tkinter as tk
from tkinter import Label, ttk


# convert rate axis label to something useful and not too large...
def ratestr(f):
    if (f >= 1e6):
        return('{:.1f} MHz'.format(f/1000000.0))
    elif (f >= 1e3):
        return('{:.1f} kHz'.format(f/1000.0))
    else:
        return('{:.1f} Hz'.format(f))

class StripChart( tk.Frame ):
   def __init__( self, parent, scale, historySize, trackColors, *args, **opts ):
      # Initialize
      super().__init__( parent, *args, **opts )
      self._trackHist   = OrderedDict() # Map: TrackName -> list of canvas objID 
      self._trackColor  = trackColors   # Map: Track Name -> color
      #Set Scaling
      self._chartHeight = scale + 1
      self._chartLength = historySize * 2  # Stretch for readability
      self.max_x = self._chartLength
      self.max_y = self._chartHeight -1

      #Create main canvas
      self._canvas = tk.Canvas( self, height=self._chartHeight + 50,
                                width=self._chartLength, background='black' )
      # self._canvas.grid( sticky=tk.N+tk.S+tk.E+tk.W )
      self._canvas.grid(row=1, column=1)
      self._canvas.create_text(6,50,text="Total Counts")

      #Labeling
      self.Ch_A = Label(self)
      self.Ch_A.grid(row=0, column=2)
      self.Ch_B = Label(self)
      self.Ch_B.grid(row=0, column=3)
      self.Ch_C = Label(self)
      self.Ch_C.grid(row=0, column=4)
      self.Ch_D = Label(self)
      self.Ch_D.grid(row=0, column=5)
      self.channel_labels = [self.Ch_A, self.Ch_B, self.Ch_C, self.Ch_D]
      # _y_label.grid(row=0, column=0)
      
      # Draw horizontal to divide plot from tick labels
      x,  y  = 0, self._chartHeight + 2
      x2, y2 = self._chartLength, y
      self._baseLine = self._canvas.create_line( x, y, x2, y2, fill='white' )

    # create axis labels
      self.maxy = Label(self, text=ratestr(self.max_y), font=("Arial", 15), width=8)
      self.maxy.grid(column=0,row=0)

      self.midy = Label(self, text=ratestr(self.max_y/2), font=("Arial", 15), width=8)
      self.midy.grid(column=0,row=1)


      self.midx = Label(self, text="Seconds", font=("Arial", 15), width=6)
      self.midx.grid(column=1,row=2)


      
      # Init track def and histories lists
      self._trackColor.update( { 'tick':'white', 'tickline':'white',
                                 'ticklabel':'white' } )
      for trackName in self._trackColor.keys():
         self._trackHist[ trackName ] = [ None for x in range(historySize) ]

      #Make stop button
      self.Button1 = ttk.Button(master=self, text= "Quit", command=quit)
      self.Button1.grid(row=1,column=2)
      #Collect Data Button
      self.Button2 = ttk.Button(self, text="Collect Data" )
      self.Button2.grid(row=1, column=3)

   def plotValues( self, **vals ):
      row_val = 0
      for trackName, trackHistory in self._trackHist.items():

         self._canvas.delete( trackHistory.pop(0) )# Remove left-most canvas objs
         self._canvas.move( trackName, -2, 0 )# Scroll canvas objs 2 pixels left
         # Plot the new values
         
         try:
            val = vals[ trackName ]
            x = self._chartLength
            y = self._chartHeight - val #Makes random value maxy -> y=0
            color = self._trackColor[ trackName ]
            
            objId = self._canvas.create_line( x, y, x+1, y, fill=color,
                                              width=3, tags=trackName )
            trackHistory.append( objId )
            row_val += 1

         except:

            trackHistory.append( None )


   def makeLabels(self, **vals):
      for x, l in zip(vals,self.channel_labels): #change your for loops to this
         l.configure(text = x +": "+ str(vals[x]), fg=self._trackColor[x])
         #l['text'] = x + ": " + str(vals[x])
         # l.after(1,self.makeLabels(**vals))


   def drawTick( self, text=None, **lineOpts ):
      # draw vertical tick line
      x = self._chartLength
      y = 1
      x2 = x
      y2 = self._chartHeight
      color = self._trackColor[ 'tickline' ]
      
      objId = self._canvas.create_line( x, y, x2, y2, fill=color,
                                        tags='tick', **lineOpts )
      self._trackHist[ 'tickline' ].append( objId )
      
      # draw tick label
      if text is not None:
         x = self._chartLength
         y = self._chartHeight + 10
         color = self._trackColor[ 'ticklabel' ]
         
         objId = self._canvas.create_text( x, y, text=text,
                                           fill=color, tags='tick' )
         self._trackHist[ 'ticklabel' ].append( objId )

      

   def configTrackColors( self, **trackColors ):
      # Change plotted data color
      for trackName, colorName in trackColors.items( ):
         self._canvas.itemconfigure( trackName, fill=colorName )
      
      # Change settings so future data has the new color
      self._trackColor.update( trackColors )


##########################
#
#        MAIN PROGRAM
#
##########################

if __name__ == '__main__':
   top = tk.Tk( )
   # style = ttk.Style(top)
   # style.theme_use('classic')
   scale_max = 600
   graph = StripChart( top, scale_max, 400, { 'Ch_A':'blue', 'Ch_B':'green', 'Ch_C':'red','Ch_D':'yellow' } )
   graph.grid( )
   
   val_A = 0
   val_B = 0
   val_C = 0
   val_D = 0
   delta = [ -4, -3, -2, -1, 0, 1, 2, 3, 4 ]  # randomly vary the values by one of these
   tickCount = 0
   
   def nextVal( current, lowerBound, upperBound ):
      from random import choice
      
      current += choice( delta )
      if current < lowerBound:
         return lowerBound
      elif current > upperBound:
         return upperBound
      else:
         return current
   
   def plotNextVals( ): #Main function
      global val_A, val_B, val_C, val_D, tickCount
   
      if tickCount % 50 == 0:
         graph.drawTick( text=str(tickCount), dash=(1,4) )
         graph.makeLabels(Ch_A=val_A, Ch_B=val_B, Ch_C=val_C, Ch_D=val_D )
      tickCount += 1
      
      val_A = nextVal( val_A, 0, scale_max )
      val_B = nextVal( val_B, 0, scale_max )
      val_C = nextVal( val_C, 0, scale_max )
      val_D = nextVal( val_D, 0, scale_max )

      graph.plotValues( Ch_A=val_A, Ch_B=val_B, Ch_C=val_C, Ch_D=val_D )
      
      changeColor = { 800: 'black',
        1200: 'yellow',
        1600: 'orange',
        2000: 'white',
        2400: 'brown',
        2800: 'blue' }
      if tickCount in changeColor:
         graph.configTrackColors( A=changeColor[tickCount] )
      
      top.after( 250, plotNextVals )#Repeats after 1000 ms

   top.after( 100, plotNextVals )
   top.mainloop( )

【问题讨论】:

  • 您可以添加具有当前布局和您期望的布局的图像。
  • 我还没有运行它,但你说button label update - 你的意思是现有button/label 中的update 值还是添加新的button/label 来替换价值观。也许您只需要创建一次button/label,然后替换现有小部件中的值。
  • 我运行代码,但我不知道是什么问题。也许您应该混合不同的布局并将Frame 与按钮一起使用,这些按钮将使用pack() 或其他grid()Frame 中组织小部件。或者,您可能需要columnspanrowspan 来为更大的元素使用更多的列/行。或者,也许您应该使用 grid() 中的参数,例如 grid(..., sticky='N') 将小部件移动到单元格顶部 (North)。
  • 谢谢!我添加了一个数字和一些额外的解释。
  • 看看使用sticky,例如sticky="N",Quit 和Collect Data 会将它们放在频道标签下方。

标签: python tkinter tkinter-layout


【解决方案1】:

如果你使用grid(..., sticky="N"),那么它会将元素移动到上边框(North

      #Make stop button
      self.Button1 = ttk.Button(self, text= "Quit", command=quit)
      self.Button1.grid(row=1, column=2, sticky='N')
      
      #Collect Data Button
      self.Button2 = ttk.Button(self, text="Collect Data" )
      self.Button2.grid(row=1, column=3, sticky='N')

但按钮与Ch_ACh_B 位于同一列,因此列中的两个元素具有相同的宽度。您可以在 Frame 中放置按钮或标签,并使用 columnspan=4 和此框架将此框架放入网格中(并且按钮不会改变标签的宽度

      self.frame_buttons = tk.Frame(self)
      self.frame_buttons.grid(row=1, column=2, sticky='N', columnspan=4)
      
      #Make stop button
      self.Button1 = ttk.Button(self.frame_buttons, text= "Quit", command=quit)
      self.Button1.grid(row=1, column=1)
      
      #Collect Data Button
      self.Button2 = ttk.Button(self.frame_buttons, text="Collect Data" )
      self.Button2.grid(row=1, column=2)

此时Ch_ACh_B 不要依赖按钮。因为框架使用columnspan=4,所以它使用 4 列的宽度,并且以该宽度为中心。框架不使用全宽 - 左右有空闲空间。


如果我使用sticky='NEW',那么框架将使用全宽,但按钮(或者更确切地说是列)不会使用此空间。我在框架中添加了red 背景以显示它。

self.frame_buttons = tk.Frame(self, bg='red')
self.frame_buttons.grid(row=1, column=2, sticky='NEW', columnspan=4)


如果我使用.columnconfigure(..., weight=1),那么我可以重用现有列中的可用空间。

      self.frame_buttons = tk.Frame(self, bg='red')
      self.frame_buttons.grid(row=1, column=2, sticky='NEW', columnspan=4)
      self.frame_buttons.columnconfigure(1, weight=1)
      self.frame_buttons.columnconfigure(2, weight=1)

weight 的总和是2,第一列的权重=1,因此它使用1/2 的可用空间,第二行也是如此。但是我可以在weight 中使用更大的值来以不同的方式分割可用空间。


如果我将sticky='WE' 用于按钮,那么按钮将在列中使用全尺寸。这样按钮将具有相同的宽度

      self.frame_buttons = tk.Frame(self, bg='red')
      self.frame_buttons.grid(row=1, column=2, sticky='NEW', columnspan=4)
      self.frame_buttons.columnconfigure(1, weight=1)
      self.frame_buttons.columnconfigure(2, weight=1)
      
      #Make stop button
      self.Button1 = ttk.Button(self.frame_buttons, text= "Quit", command=quit)
      self.Button1.grid(row=1, column=1, sticky='WE')
      
      #Collect Data Button
      self.Button2 = ttk.Button(self.frame_buttons, text="Collect Data" )
      self.Button2.grid(row=1, column=2, sticky='WE')


【讨论】:

  • 这太棒了!感谢您的详细反馈。正是我在安排小部件时所寻找的。​​span>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-11
  • 2021-07-17
  • 2023-02-09
  • 2020-10-18
  • 2020-11-10
相关资源
最近更新 更多