Python GTK: Add a button to a GUI with another button
<p>I'm trying to add another button to my GUI by pressing a button on the GUI itself. Essentially I'm just trying to update it but I'm relatively new to OOP so I'm having issues with scope. </p>

<p>This is what I have so far...</p>

<pre><code>  1 #!/usr/bin/env python
  2 import random, gi, sys
  3 from gi.repository import Gtk
  4 import itertools

 23 class Deck:
 24   """A sample card deck"""
 25   def __init__(self):
 26     deck = []
 27     self.color_list = ["red", "green", "blue"]
 28     for color in self.color_list:
 29       for i in range (1,2):
 30         deck.append((color, i))
 31     self.deck = deck
 32 
 33   def draw_card(self):
 34     print self.deck
 35     try:
 36       card = self.deck.pop()
 37       print self.deck
 38       return card
 39     except IndexError:
 40       print "No cards in deck!"
 41       return

 56 class MyWindow(Gtk.Window):
 57 
 58   def __init__(self):
 59     Gtk.Window.__init__(self, title="RGB 3, 2, 1... GO!")
 60     self.set_border_width(10)
 61     self.set_size_request(450,150)
 62 
 63     grid = Gtk.Grid()
 64     self.add(grid)
 65 
 66     draw_button = Gtk.Button(label="Draw Card")
 67     draw_button.connect("clicked", self.on_draw_button_clicked)
 68     grid.attach(draw_button,0,0,1,1)
 69 
 70     end_button = Gtk.Button(label="End Game")
 71     end_button.connect("clicked", self.on_stop_button_clicked)
 72     grid.attach_next_to(end_button,draw_button,Gtk.PositionType.RIGHT,1,1)
 73 
 74     update_button = Gtk.Button(label="Update")
 75     update_button.connect("clicked", self.update, grid)
 76     grid.attach_next_to(update_button,end_button,Gtk.PositionType.RIGHT,1,1)
 77 
 78   def update(self, widget, grid):
 79     card1_button = Gtk.Button(label="card1")
 80     card1_button.connect("clicked", self.on_card1_button_clicked)
 81     grid.attach_next_to(card1_button,draw_button,Gtk.PositionType.BOTTOM,3,1)
 82     
 83   def on_draw_button_clicked(self, widget):
 84 +--- 13 lines: card = my_deck.draw_card()----------------------------------------------------------------------------------------------------
 97 
 98   def on_stop_button_clicked(self, widget):
 99     Gtk.main_quit()
100     
101   def on_card1_button_clicked(self, widget):
102     Gtk.main_quit()

121 # Objects must be instantiated here
122 my_deck = Deck()
123 print my_deck.deck
124 win = MyWindow()
125 win.connect("delete-event", Gtk.main_quit)
126 win.show_all()
127 Gtk.main()
</code></pre>

<p>When I run this, my GUI pops open. And when I hit my update button I get the following error: </p>

<pre><code>Traceback (most recent call last):
  File "game1.py", line 85, in update
    grid.attach_next_to(card1_button,draw_button,Gtk.PositionType.BOTTOM,3,1)
NameError: global name 'draw_button' is not defined
</code></pre>

<p>How do I get my update function to recognize the GUI/grid I've already created in the def <strong>init</strong> function? Is there a better way to do what I want? </p>

<p>Define an <a href="https://docs.python.org/3/tutorial/classes.html#class-and-instance-variables" rel="nofollow">instance variable</a> in your <code>MyWindow</code> class to keep track of that button. </p>

<p>For example, in your code, whenever you see a <code>draw_button</code> change it to <code>self.draw_button</code>. It will let you track this button in other methods. This would look like:</p>

<pre><code>import random, gi, sys
from gi.repository import Gtk
import itertools

class Deck:
  """A sample card deck"""
  def __init__(self):
    deck = []
    self.color_list = ["red", "green", "blue"]
    for color in self.color_list:
      for i in range(1,2):
        deck.append((color, i))
    self.deck = deck

  def draw_card(self):
    print self.deck
    try:
      card = self.deck.pop()
      print self.deck
      return card
    except IndexError:
      print "No cards in deck!"
      return

class MyWindow(Gtk.Window):

  def __init__(self):
    Gtk.Window.__init__(self, title="RGB 3, 2, 1... GO!")
    self.set_border_width(10)
    self.set_size_request(450,150)

    grid = Gtk.Grid()
    self.add(grid)

    # Here we create the instance variable and use it afterward
    self.draw_button = Gtk.Button(label="Draw Card")
    self.draw_button.connect("clicked", self.on_draw_button_clicked)
    grid.attach(self.draw_button,0,0,1,1)

    end_button = Gtk.Button(label="End Game")
    end_button.connect("clicked", self.on_stop_button_clicked)
    grid.attach_next_to(end_button, self.draw_button, Gtk.PositionType.RIGHT, 1, 1)

    update_button = Gtk.Button(label="Update")
    update_button.connect("clicked", self.update, grid)
    grid.attach_next_to(card1_button, self.draw_button, Gtk.PositionType.BOTTOM, 3, 1)

  def update(self, widget, grid):
    card1_button = Gtk.Button(label="card1")
    card1_button.connect("clicked", self.on_card1_button_clicked)
    # Here we recall the value stored in self.draw_button
    grid.attach_next_to(card1_button, self.draw_button, Gtk.PositionType.BOTTOM, 3, 1)

  def on_draw_button_clicked(self, widget):
    for x in range(13):
      card = my_deck.draw_card()

  def on_stop_button_clicked(self, widget):
    Gtk.main_quit()

  def on_card1_button_clicked(self, widget):
    Gtk.main_quit()

# Objects must be instantiated here
my_deck = Deck()
print my_deck.deck
win = MyWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
</code></pre>

<p>But there are a few other problems in your code:</p>

<ul>
<li><p>I presume that you are using Gtk 3.0. If that's the case, you should require it before importing Gtk like this:</p>

<pre><code>gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
</code></pre></li>
<li><p>In <code>Deck.__init__()</code> you have used <code>range(1,2)</code> in a for-loop. The problem is that <code>range(1, 2)</code> is equivalent to <code>[1]</code> because you have asked for a range beginning at 1 but stopping just before 2. I'm not sure that was what you wanted.</p></li>
<li>You should add a <code>self.show_all()</code> at the end of your <code>MyWindow.update()</code> method otherwise you won't be able to see the freshly created button.</li>
<li>You should use <code>self.close()</code> instead of <code>Gtk.main_quit()</code> in your <code>MyWindow.on_stop_button_clicked()</code> and <code>MyWindow.on_card1_button_clicked()</code> methods otherwise your code won't be able to properly destroy your window when clicking those buttons.</li>
</ul>

<p>This is how this would look with these recommendations:</p>

<pre><code>import random, gi, sys
import itertools

gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class Deck:
  """A sample card deck"""
  def __init__(self):
    deck = []
    self.color_list = ["red", "green", "blue"]
    for color in self.color_list:
      for i in range(1,2):    # Check to see if this is valid
        deck.append((color, i))
    self.deck = deck

  def draw_card(self):
    print self.deck
    try:
      card = self.deck.pop()
      print self.deck
      return card
    except IndexError:
      print "No cards in deck!"
      return

class MyWindow(Gtk.Window):

  def __init__(self):
    Gtk.Window.__init__(self, title="RGB 3, 2, 1... GO!")
    self.set_border_width(10)
    self.set_size_request(450,150)

    grid = Gtk.Grid()
    self.add(grid)

    self.draw_button = Gtk.Button(label="Draw Card")
    self.draw_button.connect("clicked", self.on_draw_button_clicked)
    grid.attach(self.draw_button,0,0,1,1)

    end_button = Gtk.Button(label="End Game")
    end_button.connect("clicked", self.on_stop_button_clicked)
    grid.attach_next_to(end_button, self.draw_button, Gtk.PositionType.RIGHT, 1, 1)

    update_button = Gtk.Button(label="Update")
    update_button.connect("clicked", self.update, grid)
    grid.attach_next_to(update_button, end_button, Gtk.PositionType.RIGHT, 1, 1)

  def update(self, widget, grid):
    card1_button = Gtk.Button(label="card1")
    card1_button.connect("clicked", self.on_card1_button_clicked)
    grid.attach_next_to(card1_button, self.draw_button, Gtk.PositionType.BOTTOM, 3, 1)
    self.show_all()      # So that we can see the freshly created button

  def on_draw_button_clicked(self, widget):
    for x in range(13):
      card = my_deck.draw_card()

  def on_stop_button_clicked(self, widget):
    self.close()         # Instead of Gtk.main_quit()

  def on_card1_button_clicked(self, widget):
    self.close()         # Instead of Gtk.main_quit()

# Objects must be instantiated here
my_deck = Deck()
print my_deck.deck
win = MyWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
</code></pre>

python oop gtk