Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Tkinter Grid Checkbox

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.

like image 550
Jeremy G Avatar asked Mar 15 '23 10:03

Jeremy G


2 Answers

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()
like image 56
PM 2Ring Avatar answered Mar 21 '23 02:03

PM 2Ring


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()
like image 31
maccartm Avatar answered Mar 21 '23 02:03

maccartm