title: Tkinter: frames (?) moving around
body: <p>I've got a little Tkinter GUI script which I'm kind of happy with, with one exception: my frames seem to move about during the runtime, depending on the size of text being shown in one of the frames. It goes from this:</p>
  
  <p><img src="https://i.stack.imgur.com/ZdP2t.png" alt="Start"></p>
  
  <p>... to this:</p>
  
  <p><img src="https://i.stack.imgur.com/Ed7ps.png" alt="Middle"></p>
  
  <p>... and this:</p>
  
  <p><img src="https://i.stack.imgur.com/Ovq76.png" alt="End"></p>
  
  <p>... depending on the selection in the listbox, i.e. the amount of text being shown in <code>info_message</code>. This is my code:</p>
  
  <pre><code># -*- coding: iso-8859-1 -*-
  
  """ Layout
              dice_frame (1,1)    list_frame(1,2)     info_frame (1,3)
              ref_frame (2,1-3)
  """
  
  from tkinter import *
  import random
  import pandas as pd
  
  root = Tk()
  root.wm_title("RP")
  root.geometry('500x503')
  root.resizable(0, 0)
  
  # ----------------------------------- Dice ------------------------------------
  
  
  def dice_1():
      """ 2d6 generator without modifiers """
  
      dice_result = random.randrange(1, 6) + random.randrange(1, 6)
      dice_main.config(text=dice_result)
  
  
  def dice_2(widget_1, widget_2):
      """ Creature 2d6 dice with skill modifier """
  
      dice_result = random.randrange(1, 6) + random.randrange(1, 6)
      text = widget_2.cget('text')
      if text != '±0':
          modifier = int(text)
      else:
          modifier = 0
      widget_1.config(text=dice_result + modifier)
  
  # Dice widgets
  dice_frame = Frame(root, bd=5)
  dice_frame.grid(row=1, column=1)
  
  dice_label = Label(dice_frame, text='Dice', justify=CENTER)
  dice_label.pack()
  
  dice_main = Button(dice_frame, text='2d6', command=dice_1, height=2, width=2,
                     font=('TkDefaultFont', 10))
  dice_main.pack()
  
  # ------------------------------- List &amp; Info ---------------------------------
  
  # Read stats and personalities from file
  all_stats = pd.read_excel('Enemies.xlsx', sheetname='Sheet1', sep=';',
                            keep_default_na=False, encoding='iso-8859-1')
  all_traits = pd.read_excel('NPC Traits.xlsx', sheetname='Sheet1', sep=';')
  
  # Create and fill listbox
  list_frame = Frame(root, bd=5)
  list_frame.grid(row=1, column=2)
  
  list_label = Label(list_frame, text='Enemies', font=('TkFixedFont', 12))
  list_label.grid(row=1, column=1)
  
  listbox1 = Listbox(list_frame, height=10, width=20)
  for n in range(len(all_stats)):
      listbox1.insert(n+1, all_stats.iloc[n][0])
  listbox1.grid(row=2, column=1, pady=5)
  
  # Info box for listbox selection
  info_message = Message(root, width=300)
  info_message.grid(row=1, column=3)
  
  
  def list_selection(event):
      """ Gets selection and info/stats for info_message """
  
      index = int(listbox1.curselection()[0])
      stats = all_stats.iloc[index]
  
      info_text = str(stats[0]) + '\n'  # just the name
      for m in range(int((len(stats)-2)/2)):  # rest of the stats
          info_text += ('\n' + str(stats[4 + m*2]) + '\t' + str(stats[3 + m*2]))
  
      info_message.config(text=info_text)
  
  listbox1.bind('&lt;ButtonRelease-1&gt;', list_selection)
  
  # -------------------------------- Load Stats ---------------------------------
  
  
  def reduce(widget_1, widget_2):
      """ Function for reducing the value of a skill by 2 """
  
      # Gets current value
      text = widget_1.cget('text')
      if text == '±0':
          value = 0
      else:
          value = int(text)
      value += -2
  
      # Sets new value based on previous value
      if value &gt; 0:
          widget_1.config(text='+' + str(value))
      elif value == 0:
          widget_1.config(text='±0')
      elif value == -2:
          widget_1.config(text='-2')
      elif value &lt; -2:
          widget_1.config(text='X ', state=DISABLED, relief=SUNKEN,
                          disabledforeground='red')
          if widget_2 != '':
              widget_2.config(text='', state=DISABLED, relief=FLAT)
  
  
  def generate_traits():
      """ Generates a random personality for a creature """
      t1 = all_traits.iloc[random.randrange(1, 10)][random.randrange(1, 10)]
      t2 = all_traits.iloc[random.randrange(1, 10)][random.randrange(1, 10)]
      t3 = all_traits.iloc[random.randrange(1, 10)][random.randrange(1, 10)]
      return [t1, t2, t3]
  
  
  def load():
      """ Function for loading a creature's stats into a new window """
  
      # Create a new window
      top = Toplevel(bd=10)
      top.resizable(0, 0)
  
      # Stats based on listbox selection
      index = int(listbox1.curselection()[0])
      stats = all_stats.iloc[index]
  
      # Name
      name = Label(top, text=stats[0], font=('TkDefaultFont', 12))
      name.grid(row=1, column=1, columnspan=3)
  
      # Separator line 1
      separator_1 = Canvas(top, height=15, width=300)
      separator_1.grid(row=2, column=1, columnspan=3)
      separator_1.create_line(10, 7.5, 290, 7.5)
  
      # Creates arbitrary number of modifier buttons, labels and associated dice
      modifiers = dict()
      skills = dict()
      dice = dict()
      for k in range(int((len(stats) - 4)/2)):
          if stats[k*2 + 3] != '':
              modifiers[k] = Button(top, text=stats[4 + k*2], command=lambda a=k:
                                    reduce(modifiers[a], dice[a]), width=1)
              skills[k] = Label(top, text=stats[3 + k*2], justify=CENTER)
              dice[k] = Button(top, width=1, text='Roll', command=lambda a=k:
                               dice_2(dice[a], modifiers[a]))
              modifiers[k].grid(row=k+3, column=1)
              skills[k].grid(row=k+3, column=2)
              dice[k].grid(row=k+3, column=3)
  
      # Skill padding
      padding_row = len(modifiers) + 4
      padding_button = Button(top, width=1, text=stats[1])
      padding_button.config(command=lambda: reduce(padding_button, ''))
      padding_button.grid(row=padding_row, column=1)
      padding_label = Label(top, text='Padding', justify=CENTER)
      padding_label.grid(row=padding_row, column=2)
  
      # Personality
      if stats[2]:
          # Separator line 2
          separator_2 = Canvas(top, height=15, width=300)
          separator_2.grid(row=padding_row + 1, column=1, columnspan=3)
          separator_2.create_line(10, 7.5, 290, 7.5)
  
          # Generate three random personality traits
          traits = generate_traits()
          trait_1 = Label(top, text=traits[0], justify=CENTER)
          trait_1.grid(row=padding_row + 2, column=1)
          trait_2 = Label(top, text=traits[1], justify=CENTER)
          trait_2.grid(row=padding_row + 2, column=2)
          trait_3 = Label(top, text=traits[2], justify=CENTER)
          trait_3.grid(row=padding_row + 2, column=3)
  
  # Button for loading a creature
  load_button = Button(list_frame, text='Load', command=load)
  load_button.grid(row=3, column=1, pady=5)
  
  # ----------------------------- Reference sheet -------------------------------
  
  ref_frame = Frame(root)
  ref_frame.grid(row=3, column=1, columnspan=3, sticky=W)
  
  image = PhotoImage(file='PDQ Table.gif')
  ref_table = Label(ref_frame, image=image)
  ref_table.pack()
  
  root.mainloop()
  </code></pre>
  
  <p>Does anybody know why this is happening? Thanks in advance!</p>