Twitchykid Twitchykid - 3 months ago 5
Python Question

Python GTK: Add a button to a GUI with another button

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.

This is what I have so far...

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()


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

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


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

Answer

Define an instance variable in your MyWindow class to keep track of that button.

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

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()

But there are a few other problems in your code:

  • I presume that you are using Gtk 3.0. If that's the case, you should require it before importing Gtk like this:

    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk
    
  • In Deck.__init__() you have used range(1,2) in a for-loop. The problem is that range(1, 2) is equivalent to [1] because you have asked for a range beginning at 1 but stopping just before 2. I'm not sure that was what you wanted.

  • You should add a self.show_all() at the end of your MyWindow.update() method otherwise you won't be able to see the freshly created button.
  • You should use self.close() instead of Gtk.main_quit() in your MyWindow.on_stop_button_clicked() and MyWindow.on_card1_button_clicked() methods otherwise your code won't be able to properly destroy your window when clicking those buttons.

This is how this would look with these recommendations:

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()