Python-Tkinter, Button listing

I've come accross a problem with putting Button objects in a List.
I'm not sure if what i'm trying to achieve can actually be achieved but here it is..

working sample:

from tkinter import *

class Main(Frame):
    def __init__(self, master = None):
        Frame.__init__(self, master)
        self.pack(fill = 'both', expand = True)

        # General purpose variables     ----------
        self.isButtonSelected = False

        self.isButton_A_Selected = False    # controll boolean
        self.isButton_B_Selected = False
        self.isButton_C_Selected = False
        self.isButton_D_Selected = False

        # Layers                        ----------
        self.background = Frame(self)

        # Buttons                       ----------
        self.button_A = Button(self.background, text = "A", bg = 'white', command = self.selectButton_A)
        self.button_B = Button(self.background, text = "B", bg = 'white', command = self.selectButton_B)
        self.button_C = Button(self.background, text = "C", bg = 'white', command = self.selectButton_C)
        self.button_D = Button(self.background, text = "D", bg = 'white', command = self.selectButton_D)

        # Packs                         ----------
        #layers
        self.background.pack(fill = 'both', expand = True)
        #buttons
        btnPackPrefix = {'side' : 'left', 'fill' : 'both', 'expand' : 'True'}
        self.button_A.pack(btnPackPrefix)
        self.button_B.pack(btnPackPrefix)
        self.button_C.pack(btnPackPrefix)
        self.button_D.pack(btnPackPrefix)

    def selectButton_A(self):
        '''The idea here is similar to radiobuttons.
           Click to select. Click same to deselect.
           Click other to deselect old and select new.
           Only one button can be selected at a time.
               -repeated for all similar methods.'''

        if self.isButtonSelected == False:          # click-select
            self.button_A.config(bg = 'green')
            self.isButtonSelected = True
            self.isButton_A_Selected = True
        else:                                       
            if self.isButton_A_Selected:                # click-deselect
                self.resetButtons()
                self.isButtonSelected = False
            else:                                       # deselect other and select this
                self.resetButtons()
                self.button_A.config(bg = 'green')
                self.isButtonSelected = True
                self.isButton_A_Selected = True

    def selectButton_B(self):
        if self.isButtonSelected == False:
            self.button_B.config(bg = 'green')
            self.isButtonSelected = True
            self.isButton_B_Selected = True
        else:
            if self.isButton_B_Selected:
                self.resetButtons()
                self.isButtonSelected = False
            else:
                self.resetButtons()
                self.button_B.config(bg = 'green')
                self.isButtonSelected = True
                self.isButton_B_Selected = True

    def selectButton_C(self):
        if self.isButtonSelected == False:
            self.button_C.config(bg = 'green')
            self.isButtonSelected = True
            self.isButton_C_Selected = True
        else:
            if self.isButton_C_Selected:
                self.resetButtons()
                self.isButtonSelected = False
            else:
                self.resetButtons()
                self.button_C.config(bg = 'green')
                self.isButtonSelected = True
                self.isButton_C_Selected = True

    def selectButton_D(self):
        if self.isButtonSelected == False:
            self.button_D.config(bg = 'green')
            self.isButtonSelected = True
            self.isButton_D_Selected = True
        else:
            if self.isButton_D_Selected:
                self.resetButtons()
                self.isButtonSelected = False
            else:
                self.resetButtons()
                self.button_D.config(bg = 'green')
                self.isButtonSelected = True
                self.isButton_D_Selected = True

    def resetButtons(self):
        '''this pretty much resets all buttons and controll booleans
           to their default state'''
        #A
        self.button_A.config(bg = 'white')  # paint button white
        self.isButton_A_Selected = False    # controll boolean reset
        #B
        self.button_B.config(bg = 'white')
        self.isButton_B_Selected = False
        #C
        self.button_C.config(bg = 'white')
        self.isButton_C_Selected = False
        #D
        self.button_D.config(bg = 'white')
        self.isButton_D_Selected = False


def run_Application():
    app = Main()
    app.master.geometry('200x50')
    app.mainloop()

run_Application()

As you noticed, my code has lots of copy-pasted snippets with details changed in each one.
So i need to make it shorter somehow.. i need 1 function that controls all these actions
since they are similar.

I've come up with this.

NOT working sample:

from tkinter import *

class Main(Frame):
    def __init__(self, master = None):
        Frame.__init__(self, master)
        self.pack(fill = 'both', expand = True)

        # General purpose variables     ----------
        self.isButtonSelected = False

        self.isButton_A_Selected = False    # controll boolean
        self.isButton_B_Selected = False
        self.isButton_C_Selected = False
        self.isButton_D_Selected = False

        # Layers                        ----------
        self.background = Frame(self)

        # Buttons                       ----------
        self.button_A = Button(self.background, text = "A", bg = 'white', command = self.selectButton(0)) # apparently this doesn't work...
        self.button_B = Button(self.background, text = "B", bg = 'white', command = self.selectButton(1))
        self.button_C = Button(self.background, text = "C", bg = 'white', command = self.selectButton(2))
        self.button_D = Button(self.background, text = "D", bg = 'white', command = self.selectButton(3))

        # Packs                         ----------
        #layers
        self.background.pack(fill = 'both', expand = True)
        #buttons
        btnPackPrefix = {'side' : 'left', 'fill' : 'both', 'expand' : 'True'}
        self.button_A.pack(btnPackPrefix)
        self.button_B.pack(btnPackPrefix)
        self.button_C.pack(btnPackPrefix)
        self.button_D.pack(btnPackPrefix)

    def selectButton(self, i = None):
        '''The idea here is similar to radiobuttons.
           Click to select. Click same to deselect.
           Click other to deselect old and select new.
           Only one button can be selected at a time.
               -do that only on objects
                 at [i]. 'i' is given from the button command'''

        global buttons, btn_bools

        buttons = [self.button_A, self.button_B,
                   self.button_C, self.button_D]    # list buttons.  # Raises error. It seems that Button objects cannot be listed

        btn_bools = [self.isButton_A_Selected, self.isButton_B_Selected,
                     self.isButton_C_Selected, self.isButton_D_Selected]    # list contoll booleans

        if self.isButtonSelected == False:          # click-select
            self.buttons[i].config(bg = 'green')
            self.isButtonSelected = True
            self.btn_bools[i] = True
        else:                                       
            if self.isButton_A_Selected:                # click-deselect
                self.resetButtons()
                self.isButtonSelected = False
            else:                                       # deselect other and select this
                self.resetButtons()
                self.buttons[i].config(bg = 'green')
                self.isButtonSelected = True
                self.btn_bools[i] = True

    def resetButtons(self):
        '''iterate through all buttons/controll booleans
           and reset to their default state'''

        for b in range(len(buttons)):
            buttons[b].config(bg = 'white')

        for bb in range(len(btn_bools)):
            btn_bools[bb] = False



def run_Application():
    app = Main()
    app.master.geometry('200x50')
    app.mainloop()

run_Application()

I tried to test a similar method with 'simple' classes that contained an Int attribute and
it worked perfectly!

From the error report, i get that listing buttons isn't a good idea if not impossible.

I did search Google a lot for any examples or similar problems, but i didn't
get any good results.

I also searched about pointer implementations in Python and
got confused. I was trying to see if it is possible to save a variable pointing to a
button but i think the result is the same.. the List reads it's objects as Buttons and
raises error.

So, my questions are:
1. Can this be done somehow? (Shorting significantly the first code sample.)
2. About listing Buttons or widgets in general: If it can be done, How? If not, Why?
3. On the second code sample, i need my button commands to contain the 'i' argument
that my function needs. How can this be typed? It seems that just adding (i) does not work.

Thanks in advance!

Upvotes: 0

Views: 1048

Answers (1)

Bryan Oakley
Bryan Oakley

Reputation: 386352

Look at this line of code:

self.button_A = Button(..., command = self.selectButton(0)) 

What is happening? What does self.selectButton(0) do? You are calling the function self.selectButton(0), and then you are using the result of that function as the value of the command attribute. This almost certainly not what you intended.

What you need to do instead is something along the lines of this:

self.button_A = Button(..., command = lambda: self.selectButton(0))

This defers the call to self.selectButton until the button is actually clicked. This will now allow the buttons to be created, and you can assign them to a list if you like:

self.buttons = [self.button_A, self.button_B, self.button_C, self.button_D]

Upvotes: 1

Related Questions