I have addressed this problem sometime ago; I have either misplaced, misunderstood, or never received a response to this issue so therefore I am again attempting to deal with a Python issue:

I am towards the end of a build, and I am dealing with button commands, something that was not necessary to finalize in the program until this point. I need to either:

  • Code one button widget to allow multiple command arguments, that is, the same button is capable of issuing unique event commands at different points
  • Or, I need to be able to destroy or delete the said button so I am able to reload and redisplay it bound to a single, unique command (example: only one button is seemingly displayed from the user's point-of-view, but it is actually 3, seperate "buttons" that perform 3, unique command events.) This does not seem to be the method to use, but unless I can determine a cleaner method such as one button with multiple command references allowed, I see no other way at this point.

~Thank-you in advance for any responses\ ideas

sharky_machine

Recommended Answers

All 12 Replies

well, one approach could be to have the button attached to a single callback that checks state and calls one of three functions based on the state:

def callback(self):

  commands = [self.myfunc1, self.myfunc2, self.myfunc3]
  commands[self.mydata]()

commands is then a jump table that gets you to the right function.

You could lambda-ize that solution as well, like this:

self.commands = [self.myfunc1, self.myfunc2, self.myfunc3]

self.b = Button(self, ..., command=lambda :self.commands[self.mydata]())

But that latter solution won't work if the callback has to extract info from self in order to find out which function to call.

Jeff

commented: Good Help +1

well, one approach could be to have the button attached to a single callback that checks state and calls one of three functions based on the state:

def callback(self):
 
  commands = [self.myfunc1, self.myfunc2, self.myfunc3]
  commands[self.mydata]()

commands is then a jump table that gets you to the right function.

You could lambda-ize that solution as well, like this:

self.commands = [self.myfunc1, self.myfunc2, self.myfunc3]
 
self.b = Button(self, ..., command=lambda :self.commands[self.mydata]())

But that latter solution won't work if the callback has to extract info from self in order to find out which function to call.

Jeff

TY for your reply and help.

I have addressed this problem sometime ago; I have either misplaced, misunderstood, or never received a response to this issue so therefore I am again attempting to deal with a Python issue:

I am towards the end of a build, and I am dealing with button commands, something that was not necessary to finalize in the program until this point. I need to either:

  • Code one button widget to allow multiple command arguments, that is, the same button is capable of issuing unique event commands at different points
  • Or, I need to be able to destroy or delete the said button so I am able to reload and redisplay it bound to a single, unique command (example: only one button is seemingly displayed from the user's point-of-view, but it is actually 3, seperate "buttons" that perform 3, unique command events.) This does not seem to be the method to use, but unless I can determine a cleaner method such as one button with multiple command references allowed, I see no other way at this point.

~Thank-you in advance for any responses\ ideas

sharky_machine

Hello again:

I have been trying the given suggestions as well as other, possible options researched online; I was able to solve my initial problem concerning "command=" but it displays two (2) buttons instead of a single button -- I require only one button at all times. I am obviously missing something here.

I have tried various things such as: destroy, delete, forget-- in order to get rid of the first displayed button and replace it visibly with the second. One button to harbor a few commands. Right now at this point the program in general is running bug-free, smooth, and fast-- then I reach the post-load where I will require user input more than once via this one, aforementioned button. Below is the button code as it stands:

image00 = PhotoImage(file='grey1.gif') 
btn1 = Button(root ,bg="Black", text="HIT", image=image00,command=show_image2)
btn1. pack()
btn1 = Button(root ,bg="Black", text="HIT", image=image00,command=show_image3)
btn1. pack()
 
def button_command():
    command=show_image2()
    command=show_image3()
btn1 = Button(root ,bg="Black", text="HIT", image=image00)
def switch(): 
    pack.forget() 
    button2.pack()

Thank-you in advance.

sharky_machine

I believe that what is happening is that btn1 is being set to the first instance of Button, which is then packed (I prefer 'grid', BTW). Then btn1 is set to the second instance of Button, which is also packed. The reference to the first Button is then lost; your code has no way of accessing it, even though Tk continues to maintain a reference to it on its parent frame.

I don't know why pack.forget() doesn't fix that problem.

But if I understand your intent correctly, you want to switch out the button text and image, but not the button itself, yes?

One possibility is to use a textvariable for the Button rather than static text. It might also be possible to change the image on the fly.

See http://infohost.nmt.edu/tcc/help/pubs/tkinter.pdf for info on using textvariable.

Sorry for the partial solution; hope it helps.
Jeff

I believe that what is happening is that btn1 is being set to the first instance of Button, which is then packed (I prefer 'grid', BTW). Then btn1 is set to the second instance of Button, which is also packed. The reference to the first Button is then lost; your code has no way of accessing it, even though Tk continues to maintain a reference to it on its parent frame.

I don't know why pack.forget() doesn't fix that problem.

But if I understand your intent correctly, you want to switch out the button text and image, but not the button itself, yes?

One possibility is to use a textvariable for the Button rather than static text. It might also be possible to change the image on the fly.

See http://infohost.nmt.edu/tcc/help/pubs/tkinter.pdf for info on using textvariable.

Sorry for the partial solution; hope it helps.
Jeff

Jeff:

Thank-you for your reply.

Let me talk about the needed button function(s) again:

I am towards the tail-end of a GUI Python game build based on Blackjack. I am using Tkinter as my primary graphics module. The build is going very well and I believe it is almost complete (save for testing\ debugging) but I am at a point where I must finish the user input device (the button used to simply allow the player to "HIT" for another card).

This button will be visually, to the user, "one button", that is, they at all times (when the "HIT" button is made available to them to use) will see something like this: [HIT]
*This is actually a pretty, damaged-metal, GIF-fronted button, not just text. No standard keyboard text will be displayed on this button or changed for any reason.

What does this button exactly do? Allows the player to request additional cards. How many cards? At this point (based on statistical studies I've conducted with decks of real playing cards to see how many cards on average allow one to reach "21" without going over) the player can request three (3) new cards via this button (after receiving two computer "dealt" cards) for a total of five (5) cards displayed for their hand. (I determined that, on average, 5 cards generally achieve "21" without going over "21")

I need this one button to allow a player to request a total of 3 new cards. This seems simple but I'm finding (possibly due to my coding design of logic and variables) this is more difficult than I first believed. I am very used to Visual Basic and I initially assumed I would activate buttons in a similar and easy way. Well, I am struggling with this and do not know why.

I figured as a work-around I could have the same [HIT] with single function displayed 3-times without the player noticing-- no screen flash-- just a refresh of the GIF but with a different, seperate line of code imbedded in the background.

I just need to have one instance of this button allowing 3 "commands" which I cannot, it seems.

I'm not sure what I'm doing wrong. I've tried many things: so far, I only get 3 seperate [HIT] buttons displayed at once-- they all work great... but I only need one to be displayed.

Thanks again for your help.

Regards,
sharky_machine

The game sounds really cool. See if this is what you're after:

from Tkinter import *
import random

class MyFrame(Frame):

    def __init__(self, master):
        Frame.__init__(self, master)
        self.buttons = [Button(self, text="1", command=self.switch), \
                        Button(self, text="2", command=self.switch), \
                        Button(self, text="3", command=self.switch)]
        self.b = random.choice(self.buttons)
        self.b.grid()
        self.grid()

    def switch(self):
        self.b.grid_remove()
        self.b = random.choice(self.buttons)
        self.b.grid()
        
mainw = Tk()
mainw.frame = MyFrame(mainw)
mainw.mainloop()

This allows the user to randomly switch amongst three different buttons. Yours wouldn't be random, of course.

So about the game: if the user hits the button, he gets three cards automatically? I was at first picturing the user hitting the 'Hit' button as many times as he might want, until he busts or wins. But from your description, I'm not sure.

Jeff

The game sounds really cool. See if this is what you're after:

from Tkinter import *
import random
 
class MyFrame(Frame):
 
    def __init__(self, master):
        Frame.__init__(self, master)
        self.buttons = [Button(self, text="1", command=self.switch), \
                        Button(self, text="2", command=self.switch), \
                        Button(self, text="3", command=self.switch)]
        self.b = random.choice(self.buttons)
        self.b.grid()
        self.grid()
 
    def switch(self):
        self.b.grid_remove()
        self.b = random.choice(self.buttons)
        self.b.grid()
 
mainw = Tk()
mainw.frame = MyFrame(mainw)
mainw.mainloop()

This allows the user to randomly switch amongst three different buttons. Yours wouldn't be random, of course.

So about the game: if the user hits the button, he gets three cards automatically? I was at first picturing the user hitting the 'Hit' button as many times as he might want, until he busts or wins. But from your description, I'm not sure.

Jeff

Jeff:

Thanks for the code-- I will try this out today.

About the button and player use: The player may request up to three cards if they need or choose to do so. Mathematically, they may not need of want to get any additional card(s) beyond the first two cards. Say, for example, one is a King\ face card (10) + the second card (both of these two dealt automatically by the machine) , an Ace (which could be a "1" OR an "11", depending on the other card). This is "21", a natural Blackjack.

They can now choose up to three more cards if they wish (or none) for a TOTAL of five, displayed cards. The computer opponent the "dealer" also may have up to five, total cards. So, the button will allow the user\ human player to "HIT" for three additional cards if they wish to. These three, requested cards would require three, seperate pushes of the said button by the player-- one card per push. One card at a time.

I hope this makes sense. I may not be explaining it well. It is, at first glance, a simple game, but I think it is visually beautiful (GUI and theme of the game) and the logic is a bit more complex than I expected; that is, in order for the computer to read, record, compute, and make decisions as an artificial player, it reacts based on the human player's decisions, that is, the machine must react accordingly so it does not lose as the "Dealer".

The Casino "House" (the computer) should always win, theoretically. :cheesy:

Its quite exciting building this as a personal side project and I will post the game here as a zipped file when complete.

Regards,
Matty D.

I have addressed this problem sometime ago; I have either misplaced, misunderstood, or never received a response to this issue so therefore I am again attempting to deal with a Python issue:

I am towards the end of a build, and I am dealing with button commands, something that was not necessary to finalize in the program until this point. I need to either:

  • Code one button widget to allow multiple command arguments, that is, the same button is capable of issuing unique event commands at different points
  • Or, I need to be able to destroy or delete the said button so I am able to reload and redisplay it bound to a single, unique command (example: only one button is seemingly displayed from the user's point-of-view, but it is actually 3, seperate "buttons" that perform 3, unique command events.) This does not seem to be the method to use, but unless I can determine a cleaner method such as one button with multiple command references allowed, I see no other way at this point.

~Thank-you in advance for any responses\ ideas

sharky_machine

I am still trying to discover a way to at least destroy-delete-hide- or forget () a Tkinter button\ widget. I have searched this forum and all over the 'Net; this stands as inconclusive at this point. I have tried various things but get no response from the Python GUI; I assumed this would be rather simple (and, it probably is) but I am finding no way to make this work.

For example:

def button_command():
    command=show_image2()
btn1 = Button(root ,bg="Black", text="HIT", image=image00)
def switch(): 
    button2.pack() 
    pack.forget()

Regards,
sharky_machine

Just a thought, I have used that trick before. You can use

canvas.move(canvas_object, xAmount, yAmount)
root.update()

to move the object you created right off the canvas to hide it. Then bring it or another object back.

Just a thought, I have used that trick before. You can use

canvas.move(canvas_object, xAmount, yAmount)
root.update()

to move the object you created right off the canvas to hide it. Then bring it or another object back.

vegaseat:

This is a cool idea (I love programming "tricks") but it doesn't seem to work. Nothing I have tried for this problem seems to work or even get recognized. The buttons themselves work in that they display the "HIT" blackjack cards from the user\ player, but beyond this... nothing. I really just need to have the button available for any time a player wishes to recieve an additional card during his or her deal. I have tried everything I know of. I thought I could allow one card on one hit dealt, destroy the button, then it could be made available for another "HIT". This is not ideal. I wanted to code one button to accept three "command=", that is, one of each of three cards a player could request if wanted\ needed to achieve a blackjack. This though is an illegal error.

I have tried defs, lambda, delete, destroy, hide, forget. :confused:

I am learning so much :) and I know that when I solve this particular issue with GUI\ Tkinter I will never have to worry about it again in the future. :cheesy:

Regards,
sharkey_machine

Marking this thread as "solved"; I have not completely solved my issues with the button as of yet but I am experimenting with a snippet of Tkinter code which looks promising with a bit of manipulation.

Thanks for everyone's help and advice. ;)

Regards,
sharky_machine

You posted this long ago and just because you did not send final answer to your problem, I coded this, based on some receipt from internet for the single callback functions (http://www.astro.washington.edu/users/rowen/TkinterSummary.html#CallbackShims)

#!/usr/local/bin/Python
import Tkinter
import tkMessageBox
import random

def doButton(buttonName):
    global but
    but=button[buttonName]
    c = v,kind = deck.pop()
    dealt[buttonName]+=1
    hand[buttonName].append(c)
    print 'Dealt %i, %s new card value %s %i' %( dealt[buttonName], buttonName, kind,v)
    but.update()
    if dealt[buttonName]==3:
        print 'Dealt maximum cards (3) for',buttonName
        but.config(state=Tkinter.DISABLED)

class SimpleCallback:
    def __init__(self, callback, *firstArgs):
        self.__callback = callback
        self.__firstArgs = firstArgs

    def __call__(self, *args):
        return self.__callback (*(self.__firstArgs + args))

def init_vars():
    global hand,deck,dealt

    deck=[(x+1,y) for x in range(13) for y in ['hearts','diamond','club','spade']]
    random.shuffle(deck)

    hand=dict(deck)

    for name in buttonNames:
        hand[name]=[deck.pop(),deck.pop()] ## initial dealt
        but=button[name]
        but.config(background='beige', state=Tkinter.NORMAL,text=name)
        dealt[name] = 0

def quit_ap():
    """ Quit with tkMessage confirmation """
    
    if tkMessageBox.askokcancel("Quit?", "Are you sure you want to quit?"):
        exit(0)

if __name__=="__main__":
    root = Tkinter.Tk()
    root.protocol("WM_DELETE_WINDOW", quit_ap)
    root.title('Black Jack')
    root.geometry('200x200')

    buttonNames = ("dealer", "Jack", "Jill")

    dealt=dict()
    button=dict()
    for name in buttonNames:
        callback = SimpleCallback(doButton, name)
        but=Tkinter.Button(root, command=callback)
        button[name]=but
        but.pack(expand=Tkinter.YES, fill=Tkinter.BOTH)
    init_vars()        
    root.mainloop()

Your description of the functionality:


What does this button exactly do? Allows the player to request additional cards. How many cards? At this point (based on statistical studies I've conducted with decks of real playing cards to see how many cards on average allow one to reach "21" without going over) the player can request three (3) new cards via this button (after receiving two computer "dealt" cards) for a total of five (5) cards displayed for their hand. (I determined that, on average, 5 cards generally achieve "21" without going over "21")

I need this one button to allow a player to request a total of 3 new cards. This seems simple but I'm finding (possibly due to my coding design of logic and variables) this is more difficult than I first believed. I am very used to Visual Basic and I initially assumed I would activate buttons in a similar and easy way. Well, I am struggling with this and do not know why.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.