4

I was wondering if there is an easy way to create a grid of checkboxes using Tkinter. I am trying to make a grid of 10 rows and columns (so 100 checkboxes) so that only two checkboxes can be selected per row.

Edit: I'm using python 2.7 with spyder

What I have so far:

from Tkinter import*

master = Tk()
master.title("Select Groups")

rows=10
columns=10


for x in range(rows):
    for y in range(columns):
        Label(master, text= "Group %s"%(y+1)).grid(row=0,column=y+1)
        Label(master, text= "Test %s"%(x+1)).grid(row=x+1,column=0)
        Checkbutton(master).grid(row=x+1, column=y+1)

mainloop()

I'm trying to use state='Disabled' to grey out a row once two checkboxes have been selected.

3
  • 2
    So show us what you've tried so far! (Also tell us what version of Tkinter, and which version of Python you're using). What do you want to happen when the user selects a 3rd box in a given row? Do you want the new box to refuse to be selected? Or do you want to de-select one of the currently selected boxes, and if so, which one? Commented Jul 14, 2015 at 15:54
  • I've edited my example code to show how you could do it using what you've already provided. Commented Jul 14, 2015 at 16:49
  • Thanks for fixing up your question, Jeremy. The added code & information makes your question better for people who want to answer it, but more importantly, it also makes your question & our answers a lot more useful for future readers. Commented Jul 15, 2015 at 8:10

2 Answers 2

5

Here's a version that puts everything into a class so we don't need to use global variables. It also avoids the import * construction which is generally considered bad style in Python. True, lots of example code uses import * but it's not a good practice because it clutters up the global namespace with all the names from the imported module. So those names can clash with the names of your own variables, and they can also clash with the names of other modules you import using import *.

The program prints lists of the selected Groups for each Test row when the window closes.

#!/usr/bin/env python

''' Create a grid of Tkinter Checkbuttons

    Each row permits a maximum of two selected buttons

    From http://stackoverflow.com/q/31410640/4014959

    Written by PM 2Ring 2015.07.15
'''

import Tkinter as tk

class CheckGrid(object):
    ''' A grid of Checkbuttons '''
    def __init__(self, rows=10, columns=10):
        master = tk.Tk()
        master.title("Select Groups")

        rowrange = range(rows)
        colrange = range(columns)

        #Create the grid labels
        for x in colrange:
            w = tk.Label(master, text="Group %s" % (x + 1))
            w.grid(row=0, column=x+1)

        for y in rowrange:
            w = tk.Label(master, text="Test %s" % (y + 1))
            w.grid(row=y+1, column=0)

        #Create the Checkbuttons & save them for future reference
        self.grid = []
        for y in rowrange:
            row = []
            for x in colrange:
                b = tk.Checkbutton(master)

                #Store the button's position and value as attributes
                b.pos = (y, x)
                b.var = tk.IntVar()

                #Create a callback bound to this button
                func = lambda w=b: self.check_cb(w)
                b.config(variable=b.var, command=func)
                b.grid(row=y+1, column=x+1)
                row.append(b)
            self.grid.append(row)

        #Track the number of on buttons in each row
        self.rowstate = rows * [0]

        master.mainloop()

    def check_cb(self, button):
        ''' Checkbutton callback '''
        state = button.var.get()
        y, x = button.pos

        #Get the row containing this button
        row = self.grid[y]

        if state == 1:
           self.rowstate[y] += 1 
           if self.rowstate[y] == 2:
               #Disable all currently off buttons in this row
               for b in row:
                   if b.var.get() == 0:
                        b.config(state=tk.DISABLED)
        else:
           self.rowstate[y] -= 1 
           if self.rowstate[y] == 1:
               #Enable all currently off buttons in this row
               for b in row:
                   if b.var.get() == 0:
                        b.config(state=tk.NORMAL)

        #print y, x, state, self.rowstate[y] 

    def get_checked(self):
        ''' Make a list of the selected Groups in each row'''
        data = []
        for row in self.grid:
            data.append([x + 1 for x, b in enumerate(row) if b.var.get()])
        return data


def main():
    g = CheckGrid(rows=10, columns=10)

    #Print selected Groups in each Test row when the window closes
    data = g.get_checked()
    for y, row in enumerate(data):
        print "Test %2d: %s" % (y + 1, row)


if __name__ == '__main__':
    main()
Sign up to request clarification or add additional context in comments.

Comments

4

Here's an example using your provided 10x10 grid. It should give you the basic idea of how to implement this.

Just make sure you keep a reference to every Checkbutton (boxes in the example) as well as every IntVar (boxVars in the example).

Here's why:

-Checkbuttons are needed to call config(state = DISABLED/NORMAL).

-IntVars are needed to determine the value of each Checkbutton.

Aside from those crucial elements its basically just some 2D array processing.

Here's my example code (now based off of your provided code).

from Tkinter import *

master = Tk()
master.title("Select Groups")

rows=10
columns=10

boxes = []
boxVars = []

# Create all IntVars, set to 0

for i in range(rows):
    boxVars.append([])
    for j in range(columns):
        boxVars[i].append(IntVar())
        boxVars[i][j].set(0)

def checkRow(i):
    global boxVars, boxes
    row = boxVars[i]
    deselected = []

    # Loop through row that was changed, check which items were not selected 
    # (so that we know which indeces to disable in the event that 2 have been selected)

    for j in range(len(row)):
        if row[j].get() == 0:
            deselected.append(j)

    # Check if enough buttons have been selected. If so, disable the deselected indeces,
    # Otherwise set all of them to active (in case we have previously disabled them).

    if len(deselected) == (len(row) - 2):
        for j in deselected:
            boxes[i][j].config(state = DISABLED)
    else:
        for item in boxes[i]:
            item.config(state = NORMAL)

def getSelected():
    selected = {}
    for i in range(len(boxVars)):
        temp = []
        for j in range(len(boxVars[i])):
            if boxVars[i][j].get() == 1:
                temp.append(j + 1)
        if len(temp) > 1:
            selected[i + 1] = temp
    print selected


for x in range(rows):
    boxes.append([])
    for y in range(columns):
        Label(master, text= "Group %s"%(y+1)).grid(row=0,column=y+1)
        Label(master, text= "Test %s"%(x+1)).grid(row=x+1,column=0)
        boxes[x].append(Checkbutton(master, variable = boxVars[x][y], command = lambda x = x: checkRow(x)))
        boxes[x][y].grid(row=x+1, column=y+1)

b = Button(master, text = "Get", command = getSelected, width = 10)
b.grid(row = 12, column = 11)
mainloop()

5 Comments

Thanks! You said that in your example the IntVars determine the value of the Checkbutton. Is there a way to use them to output a list of the buttons that were selected. For example, if on row one columns 1 and 3 were selected, on row two columns 1 and 9 were selected, and on row 3 only column 3 was selected the list would look like [(1,3),(1,9)]. Any suggestions?
You only want rows that have 2 elements selected?
Yes, only 2 elements.
Check the new code. I used a button on the far right side to check, but you could add it to the end of the checkRow method (so that it will check every time you select a new checkbox). I used a dict to keep track of which rows contain which elements, and only rows with 2 elements are displayed. To access them simply access it like a list: selected[1] would yield the list [1, 3] in this case. Note that I had to use a list because tuples (as you show in your example) are immutable.
The dict is printed out in the example to show you how it looks but you could store it globally or whatever you decide.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.