0

I am trying to write a program of a bingo grid and want to reduce my code down to make it simpler. I am currently repeating this code:

from tkinter import *
from tkinter import Tk, Button, Label
mycolor = '#FF7F50'
mycolor2 = '#FFFFFF'
mycolor3 = '#BAF0FF'
class Window:

    def __init__(self, master):
        self.master = master
        self.master.title("Bingo")
        self.master.minsize(width=1920, height=1080)
        self.master.config(bg=mycolor3)

        self.A = Button(master, text='1', font=('Helvetica', '23'), height=1, width=20, command=self.toggleA, bg=mycolor2)
        self.A.grid(column=0,row=1)

        self.B = Button(master, text='2', font=('Helvetica', '23'), height=1, width=20, command=self.toggleB, bg=mycolor2)
        self.B.grid(column=0,row=2)

    def toggleA(self):
        self.A.config('bg')
        if self.A.config('bg')[-1] == mycolor2:
            self.A.config(bg=mycolor)
        else:
            self.A.config(bg=mycolor2)
    def toggleB(self):
        self.B.config('bg')
        if self.B.config('bg')[-1] == mycolor2:
            self.B.config(bg=mycolor)
        else:
            self.B.config(bg=mycolor2)
root = Tk()
my_win = Window(root)
root.mainloop()

and using this exactly, I have to repeat this script 75 times by changing the variable names to complete the program, this works, but i want to know if there is a way to the same definition of toggle for each button opposed to defining a new toggle for each button? The toggle is used to affect the color of the button toggling it from one color to the other and Im not sure how to get the same commands to be called by all the buttons to affect each button individually. thank you!

DDC208
  • 19
  • 3
  • 1
    You say you're repeating this code; can you show one repetition so we can see what it is you're changing in each repetition? _(PS: Welcome to StackOverflow! )_ – Nathan Hinchey Oct 08 '18 at 18:53
  • Possible duplicate of [How to pass arguments to a Button command in Tkinter?](https://stackoverflow.com/questions/6920302/how-to-pass-arguments-to-a-button-command-in-tkinter) – stovfl Oct 08 '18 at 18:58
  • @NathanHinchey I have added a second iteration to show what i am repeating. Thank you! – DDC208 Oct 08 '18 at 19:04
  • 1
    Define a `Toggle` class whose instances are callable (i.e. define a `__call__()` method) and then create a list of them. You can then use elements of the list whereever it is you're currently using `toggleA`, `toggleB`, etc. – martineau Oct 08 '18 at 19:06
  • 1
    Make a class or use a forloop – thesonyman101 Oct 08 '18 at 19:08
  • @stovfl: It's **not** a dup of that question—but it still doesn't have enough code in it. – martineau Oct 08 '18 at 19:08
  • @martineau what does SB stand for? – Nathan Hinchey Oct 08 '18 at 19:16
  • 1
    DDC208: Please add more code to your question...ideally a minimal but runnable example that has the complete class your code seems to be defining. – martineau Oct 08 '18 at 19:16
  • 1
    @thesonyman101: Should be make a class ***and*** use a `for` loop. – martineau Oct 08 '18 at 19:21
  • 1
    @martineau added code to be runnable. thanks! – DDC208 Oct 08 '18 at 19:25

3 Answers3

1

One option is to put self.A, self.B, etc into a list (e.g. self.buttons).

Then instead of creating toggleA(self), toggleB(self), etc you can make toggle(self,button), that uses its button argument where you are currently using self.A, self.B, etc.

Then use a for loop over self.buttons and call toggle on each of them.

for button in buttons:
    toggle(button)
Nathan Hinchey
  • 1,191
  • 9
  • 30
0

Here's something runnable showing how to implement the approach I was suggested in one of my comments:

from tkinter import Tk, Button, Label

MY_COLOR = '#FF7F50'
MY_COLOR2 = '#FFFFFF'
MY_COLOR3 = '#BAF0FF'
NUM_BUTTONS = 3


class Toggler:
    """ Toggles background color of a Button widget when called. """
    def __init__(self, btn, color, color2):
        self.btn = btn
        self.color = color
        self.color2 = color2

    def __call__(self):
        if self.btn.cget('bg') == self.color:
            self.btn.config(bg=self.color2)
        else:
            self.btn.config(bg=self.color)


class Window:
    def __init__(self, master):
        self.master = master
        self.master.title("Bingo")
        self.master.minsize(width=800, height=600)
        self.master.config(bg=MY_COLOR3)

        # Create the buttons.
        self.buttons = []
        self.togglers = []
        for i in range(NUM_BUTTONS):
            btn = Button(master, text='Button %d' % i, font=('Helvetica', '23'),
                         height=1, width=20, bg=MY_COLOR2)
            btn.grid(column=0, row=i)
            toggler = Toggler(btn, MY_COLOR, MY_COLOR2)
            btn.config(command=toggler)
            self.buttons.append(btn)
            self.togglers.append(toggler)


root = Tk()
my_win = Window(root)
root.mainloop()

In this particular case (now that I can see more of your code), I suspect this might be overkill and could be accomplished with less code (perhaps along the lines of what I think @Nathan Hinchey is currently getting at in his answer). That said, I still think the code show here would be good for you to see and understand what kind of things are possible with Python and tkinter.

I see @Bryan Oakley has now posted an answer which makes Toggler a Button sub-class instead of an independent one as shown here. I considered doing that because it would be more elegant—but decided that might be too advanced and went ahead and implemented as shown here (and since I had mentioned in a comment). Besides that, it looks like Bryan also has an implementation of @Nathan's idea in a way that would work.

martineau
  • 119,623
  • 25
  • 170
  • 301
0

You can create buttons in loops, and store a reference to the button in a dictionary or list.

Non-Object-Oriented approach

The following example takes a traditional approach of doing all of the work within the main class. It uses lambda to pass the button index into the callback function.

from tkinter import *
mycolor = '#FF7F50'
mycolor2 = '#FFFFFF'
mycolor3 = '#BAF0FF'
class Window:

    def __init__(self, master):
        self.master = master
        self.master.title("Bingo")
        self.master.minsize(width=1920, height=1080)
        self.master.config(bg=mycolor3)

        self.buttons = {}
        font = ('Helvetica', 12)
        for i in range(1,10):
            button = Button(master, text=str(i), font=font,
                            height=1, width=20, bg=mycolor2,
                            command=lambda index=i: self.toggle(index))
            button.grid(column=0, row=i+1)
            self.buttons[i] = button

    def toggle(self, index):
        button = self.buttons[index]
        if button.cget('bg') == mycolor2:
            button.configure(bg=mycolor)
        else:
            button.configure(bg=mycolor2)

root = Tk()
my_win = Window(root)
root.mainloop()

Note: you might want to also configure activebackground so that you can see the new color without having to move the mouse off of the button.

button.configure(bg=mycolor, activebackground=mycolor)

Object-oriented approach

Another solution is to create a custom button widget that has the toggle feature built-in so that the caller doesn't have to do anything besides creating instances of the custom button. This simplifies the callback a little since each button knows itself and doesn't have to be told which button to toggle.

This example still stores the buttons in a dictionary, though strictly speaking it's not necessary in this specific example:

from tkinter import *
mycolor = '#FF7F50'
mycolor2 = '#FFFFFF'
mycolor3 = '#BAF0FF'
class Window:

    def __init__(self, master):
        self.master = master
        self.master.title("Bingo")
        self.master.minsize(width=1920, height=1080)
        self.master.config(bg=mycolor3)

        self.buttons = {}
        font = ('Helvetica', 23)
        for i in range(1,75):
            button = Toggler(master, text=str(i), font=font, color1=mycolor, color2=mycolor2)
            button.grid(column=0, row=i+1)
            self.buttons[i] = button


class Toggler(Button):
    def __init__(self, master, text, font, color1, color2):
        self.color1 = color1
        self.color2 = color2
        Button.__init__(self, master, text=text, font=font,
                        height=1, width=20,
                        background=color1, activebackground=color1,
                        command=self.toggle)

    def toggle(self):
        color = self.cget("background")
        new_color = self.color1 if color == self.color2 else self.color2
        self.configure(background=new_color, activebackground=new_color)


root = Tk()
my_win = Window(root)
root.mainloop()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685