How to select multiple checkboxes with SHIFT in tkinter? - python

The question is quite simple,I've created many checkboxes in a Text widget using window_create .Here is the code:
import tkinter as tk
root = tk.Tk()
sb = tk.Scrollbar(orient="vertical")
text = tk.Text(root, width=40, height=20, yscrollcommand=sb.set)
sb.config(command=text.yview)
sb.pack(side="right",fill="y")
text.pack(side="top",fill="both",expand=True)
for i in range(30):
cb = tk.Checkbutton(text="checkbutton %s" % i,padx=0,pady=0,bd=0)
text.window_create("end", window=cb)
text.insert("end", "\n")
root.mainloop()
And here is what it looks like:
I want to select multiple checkboxes,which is troublesome if I have to click every checkbox.So is there a way that SHIFT can be used here?

You should bind the '<Shift-Button-1>' event to every checkbutton, and also the '<Button-1> to indicate the start of the range which should be selected. Also, consider to wrap your code in a class for better readability:
class App:
def __init__(self, root):
self.start = 0
self.root = root
self.sb = tk.Scrollbar(orient="vertical")
text = tk.Text(root, width=40, height=20, yscrollcommand=self.sb.set)
self.sb.config(command=text.yview)
self.sb.pack(side="right",fill="y")
text.pack(side="top", fill="both", expand=True)
self.chkbuttons = [tk.Checkbutton(text="checkbutton %s" % i,padx=0,pady=0,bd=0)
for i in range(30)]
for cb in self.chkbuttons:
text.window_create("end", window=cb)
text.insert("end", "\n")
cb.bind("<Button-1>", self.selectstart)
cb.bind("<Shift-Button-1>", self.selectrange)
def selectstart(self, event):
self.start = self.chkbuttons.index(event.widget)
def selectrange(self, event):
start = self.start
end = self.chkbuttons.index(event.widget)
sl = slice(min(start, end)+1, max(start, end))
for cb in self.chkbuttons[sl]:
cb.toggle()
self.start = end
if __name__ == '__main__':
root = tk.Tk()
app = App(root)
root.mainloop()

Related

How change tkinter Notebook tab and keep configuration?

I have a gui with two tabs and would like to switch back and forth between the tabs while keeping the content of the tabs. If I do this all in one function, then it works fine, but since I have more in the individual functions. I have outsourced them. Now I have bound the individual functions to the tabs. But here I have the problem that I overwrite the tabs with every change. But I would like to switch between the tabs without overwriting the entries. I understand why I overwrite the input. After all, I call the individual functions with rountine and overwrite them that way. Unfortunately I do not know how to do it better.
import tkinter as tk
from tkinter import ttk
class display():
def __init__(self):
self.root = tk.Tk()
self.root.geometry('400x300')
self.root.title('Notebook Demo')
def gui(self):
# create a notebook
self.notebook = ttk.Notebook(self.root)
self.notebook.place(rely=0.0, relheight=1, relwidth=1)
# create frames
self.frame1 = ttk.Frame(self.notebook, width=400, height=280)
self.frame2 = ttk.Frame(self.notebook, width=400, height=280)
self.frame1.place(rely=0.0, relheight=1, relwidth=1)
self.frame2.place(rely=0.0, relheight=1, relwidth=1)
# add frames to notebook
self.notebook.add(self.frame1, text='General Information')
self.notebook.add(self.frame2, text='Profile')
self.notebook.bind("<<NotebookTabChanged>>", self.routine)
self.gu1()
self.root.update_idletasks()
self.root.mainloop()
def routine(self,event):
if self.notebook.index("current") == 1:
self.gu1()
else:
self.gu2()
def gu1(self):
button1 = tk.Button(self.frame1, text="test1", command=self.changeText)
button1.grid(column=0, row=0)
self.label1 = tk.Label(self.frame1, text="test1")
self.label1.grid(column=1, row=0)
self.root.mainloop()
def gu2(self):
button2 = tk.Button(self.frame2, text="test2", command=self.changeText2)
button2.grid(column=0, row=0)
self.label2 = tk.Label(self.frame2, text="test2")
self.label2.grid(column=1, row=0)
self.root.mainloop()
def changeText(self):
self.label1["text"] = "change1"
def changeText2(self):
self.label2["text"] = "change2"
if __name__ == "__main__":
display().gui()
All problem is that you use .gu1() and .gu2() in routine().
You should run .gu1() and .gu2() only once - at start in gui().
def gui(self):
# ... code ...
self.notebook.bind("<<NotebookTabChanged>>", self.routine)
self.gu1() # create at start
self.gu2() # create at start
def routine(self,event):
if self.notebook.index("current") == 1:
#self.gu1() # DON'T DO THIS
print('selected tab 1')
else:
#self.gu2() # DON'T DO THIS
print('selected tab 2')
Working code:
tkinter should use only one mainloop()
import tkinter as tk
from tkinter import ttk
class display():
def __init__(self):
self.root = tk.Tk()
self.root.geometry('400x300')
self.root.title('Notebook Demo')
def gui(self):
# create a notebook
self.notebook = ttk.Notebook(self.root)
self.notebook.place(rely=0.0, relheight=1, relwidth=1)
# create frames
self.frame1 = ttk.Frame(self.notebook, width=400, height=280)
self.frame2 = ttk.Frame(self.notebook, width=400, height=280)
self.frame1.place(rely=0.0, relheight=1, relwidth=1)
self.frame2.place(rely=0.0, relheight=1, relwidth=1)
# add frames to notebook
self.notebook.add(self.frame1, text='General Information')
self.notebook.add(self.frame2, text='Profile')
self.notebook.bind("<<NotebookTabChanged>>", self.routine)
self.gu1()
self.gu2()
self.root.update_idletasks()
self.root.mainloop()
def routine(self,event):
if self.notebook.index("current") == 1:
#self.gu1() # DON'T DO THIS
print('selected tab 1')
else:
#self.gu2() # DON'T DO THIS
print('selected tab 2')
def gu1(self):
button1 = tk.Button(self.frame1, text="test1", command=self.changeText)
button1.grid(column=0, row=0)
self.label1 = tk.Label(self.frame1, text="test1")
self.label1.grid(column=1, row=0)
def gu2(self):
button2 = tk.Button(self.frame2, text="test2", command=self.changeText2)
button2.grid(column=0, row=0)
self.label2 = tk.Label(self.frame2, text="test2")
self.label2.grid(column=1, row=0)
def changeText(self):
self.label1["text"] = "change1"
def changeText2(self):
self.label2["text"] = "change2"
if __name__ == "__main__":
display().gui()
EDIT:
If you have to create some widgets in tabs with delays (ie. to wait for user's values in other widgets) then you should use boolean variables (True/False) to run it only once in routine()
def gui(self):
# ... code ...
self.gu1_created = False
self.gu2_created = False
self.notebook.bind("<<NotebookTabChanged>>", self.routine)
def routine(self,event):
if self.notebook.index("current") == 1:
if not self.gu1_created:
self.gu1()
self.gu1_created = True
print('selected tab 1')
else:
if not self.gu2_created:
self.gu2()
self.gu2_created = True
print('selected tab 2')

clearing multiple labels values

I am losing my peanuts here. I am trying to clear two label values but i get an error
AttributeError: 'Label' object has no attribute 'delete'
basically if i were to click the calculate subtotal button then click the divide total button. I get my intended values. Now if I were to click on the clear values button i get an error. Literally shaking my head as I type this. Anyone care to explain why this is the case?
try:
import Tkinter as tk
except:
import tkinter as tk
class GetInterfaceValues():
def __init__(self):
self.root = tk.Tk()
self.totalValue = tk.StringVar()
self.root.geometry('500x200')
self.calculateButton = tk.Button(self.root,
text='Calculate Subtotal',
command=self.getSubtotals)
self.divideTotalButton = tk.Button(self.root,
text='Divide total',
command=self.divide)
self.textInputBox = tk.Text(self.root, relief=tk.RIDGE, height=1, width = 6, borderwidth=2)
self.firstLabel = tk.Label(self.root, text="This is the subtotal:")
self.secondLabel = tk.Label(self.root, text="This is the Divide Total:")
self.clearTotalButton = tk.Button(self.root, text='clear the values',command = self.clear)
self.firstLabel.pack(side="bottom")
self.secondLabel.pack(side="bottom")
self.textInputBox.pack()
self.calculateButton.pack()
self.divideTotalButton.pack()
self.clearTotalButton.pack()
self.root.mainloop()
def getTextInput(self):
result = self.textInputBox.get("1.0", "end")
return result
def getSubtotals(self):
userValue = int(self.getTextInput())
self.firstLabel["text"] = self.firstLabel["text"] + str(userValue * 5)
def divide(self):
userValue = int(self.getTextInput())
self.secondLabel["text"] = self.secondLabel["text"] + str(userValue / 10)
def clear(self):
self.firstLabel["text"] = self.firstLabel.delete("1.0","end")
app = GetInterfaceValues()
try:
import Tkinter as tk
except:
import tkinter as tk
class GetInterfaceValues():
def __init__(self):
self.root = tk.Tk()
self.totalValue = tk.StringVar()
self.root.geometry('500x200')
self.calculateButton = tk.Button(self.root,
text='Calculate Subtotal',
command=self.getSubtotals)
self.divideTotalButton = tk.Button(self.root,
text='Divide total',
command=self.divide)
self.textInputBox = tk.Text(self.root, relief=tk.RIDGE, height=1, width = 6, borderwidth=2)
self.firstLabelDefault = "This is the subtotal:"
self.secondLabelDefault = "This is the Divide Total:"
self.firstLabel = tk.Label(self.root, text=self.firstLabelDefault)
self.secondLabel = tk.Label(self.root, text=self.secondLabelDefault)
self.clearTotalButton = tk.Button(self.root, text='clear the values',command = self.clear)
self.firstLabel.pack(side="bottom")
self.secondLabel.pack(side="bottom")
self.textInputBox.pack()
self.calculateButton.pack()
self.divideTotalButton.pack()
self.clearTotalButton.pack()
self.root.mainloop()
def getTextInput(self):
result = self.textInputBox.get("1.0", "end")
return result
def getSubtotals(self):
userValue = int(self.getTextInput())
self.firstLabel["text"] = self.firstLabel["text"] + str(userValue * 5)
def divide(self):
userValue = int(self.getTextInput())
self.secondLabel["text"] = self.secondLabel["text"] + str(userValue / 10)
def clear(self):
self.firstLabel["text"] = self.firstLabelDefault
self.secondLabel["text"] = self.secondLabelDefault
self.textInputBox.delete("1.0", "end")
app = GetInterfaceValues()
You may have confused the methods of tkinter.Text and tkinter.Label. The method you called was tkinter.label.delete, which is not defined (does not exist), however it does exist for the tkinter.Text. Therefore, the only way to 'reset' would be to change the text attribute of the tkinter.Labels back to a 'default' string. It would perhaps be more appropriate to use another widget instead.

How to add a scrollbar to window made with tkinter

A simple quiz game
I got this code and I need scrollbars, I tried to search how to add it on stackoverflow (ScrolledWindow with tix...) but I still can't get something that works properly. Could someone help me?
from tkinter import *
from random import randint
root = Tk()
root.title("Quiz")
root.geometry("400x300")
class Window:
def __init__(self, question, answer):
self.text = [question, answer]
self.createLabel()
# self.createText()
self.createEntry()
self.createButton()
def retrieve_input(self):
# inputValue = self.textBox.get("1.0", "end-1c")
# print(inputValue)
if self.mystring.get() == self.text[1]:
print("Esatto. è " + self.text[1])
self.left['text'] = "Esatto"
def createLabel(self):
self.labelframe = LabelFrame(root, text="Domanda:")
self.labelframe.pack(fill="both", expand="yes")
self.left = Label(self.labelframe, text=self.text[0])
self.left.pack()
def createText(self):
self.textBox = Text(height=1)
self.textBox.pack()
def createEntry(self):
self.mystring = StringVar()
self.myentry = Entry(root, textvariable=self.mystring).pack()
def createButton(self):
self.but = Button(text="Click", command=self.retrieve_input)
self.but.pack()
for i in range(10):
one = randint(1, 10)
two = randint(1, 10)
Window("Quanto fa " + str(one) + "+" + str(two) + "?", str(one + two))
root.mainloop()
output
With ScrolledFrame it can look like this
I renamed Window into Question because it makes more sense
I use self.question and self.answer instead of self.text = [question, answer] to make it more readable.
I put classes and functions before root = tk.Tk() to make it more readable.
I use import tkinter as tk instead of from tkinter import * to make it more readable.
Question gets inner frame from ScrolledFrame and use as parent for LabelFrame. Other widgets use labelframe as parent.
BTW: you had entry = Entry(..).pack() which assign None to entry because pack()/grid()/place() returns None. I put pack() in next line and now I can get text directly from Entry (without StringVar)
Code
import tkinter as tk
from random import randint
# --- classes ---
class ScrolledFrame(tk.Frame):
def __init__(self, parent, vertical=True, horizontal=False):
super().__init__(parent)
# canvas for inner frame
self._canvas = tk.Canvas(self)
self._canvas.grid(row=0, column=0, sticky='news') # changed
# create right scrollbar and connect to canvas Y
self._vertical_bar = tk.Scrollbar(self, orient='vertical', command=self._canvas.yview)
if vertical:
self._vertical_bar.grid(row=0, column=1, sticky='ns')
self._canvas.configure(yscrollcommand=self._vertical_bar.set)
# create bottom scrollbar and connect to canvas X
self._horizontal_bar = tk.Scrollbar(self, orient='horizontal', command=self._canvas.xview)
if horizontal:
self._horizontal_bar.grid(row=1, column=0, sticky='we')
self._canvas.configure(xscrollcommand=self._horizontal_bar.set)
# inner frame for widgets
self.inner = tk.Frame(self._canvas, bg='red')
self._window = self._canvas.create_window((0, 0), window=self.inner, anchor='nw')
# autoresize inner frame
self.columnconfigure(0, weight=1) # changed
self.rowconfigure(0, weight=1) # changed
# resize when configure changed
self.inner.bind('<Configure>', self.resize)
self._canvas.bind('<Configure>', self.frame_width)
def frame_width(self, event):
# resize inner frame to canvas size
canvas_width = event.width
self._canvas.itemconfig(self._window, width = canvas_width)
def resize(self, event=None):
self._canvas.configure(scrollregion=self._canvas.bbox('all'))
class Question:
def __init__(self, parent, question, answer):
self.parent = parent
self.question = question
self.answer = answer
self.create_widgets()
def get_input(self):
value = self.entry.get()
print('value:', value)
if value == self.answer:
print("Esatto. è " + self.answer)
self.label['text'] = "Esatto"
def create_widgets(self):
self.labelframe = tk.LabelFrame(self.parent, text="Domanda:")
self.labelframe.pack(fill="both", expand=True)
self.label = tk.Label(self.labelframe, text=self.question)
self.label.pack(expand=True, fill='both')
self.entry = tk.Entry(self.labelframe)
self.entry.pack()
self.button = tk.Button(self.labelframe, text="Click", command=self.get_input)
self.button.pack()
# --- main ---
root = tk.Tk()
root.title("Quiz")
root.geometry("400x300")
window = ScrolledFrame(root)
window.pack(expand=True, fill='both')
for i in range(10):
one = randint(1, 10)
two = randint(1, 10)
Question(window.inner, "Quanto fa {} + {} ?".format(one, two), str(one + two))
root.mainloop()

Display selected options from checkbox to a new list in next tkinter window

I want to display the selected options from my checkbox list in a new list in a new Tkinter window and when browsing it comes back to the main screen
(using Python 3.5 with Ubuntu 16.04).
import tkinter as tk
import tkMessageBox
lista=['jpeg','jfit','tiff','gif','png','bmp']
class PopUp(tk.Toplevel):
def __init__(self, number=10):
tk.Toplevel.__init__(self)
self.global_state = tk.BooleanVar()
cb = tk.Checkbutton(self, text="select/deselect all", variable=self.global_state, command=self.select_clear_states)
cb.grid(row=0, column=0, padx=5, pady=1)
self.states = []
for n in range(len(lista)):
var = tk.BooleanVar()
cb = tk.Checkbutton(self, text=str(lista[n]), variable=var)
cb.grid(row=n+1, column=0, padx=5, pady=1)
self.states.append(var)
def select_clear_states(self):
state = self.global_state.get()
for x in self.states:
x.set(state)
def popup(num):
win = PopUp(num)
root = tk.Tk()
b = tk.Button(root, text="5 checkboxes", command=lambda:popup(5))
b.pack()
root.mainloop()
Assuming I understand you correctly, if you wanted to do this with Checkbuttons you could do something like the below:
from tkinter import *
class App:
def __init__(self, root):
self.root = root
self.top = Toplevel(root)
self.frame = Frame(self.top)
self.frame.pack()
self.check = []
self.var = []
for i in range(10):
self.var.append(IntVar())
self.var[i].trace("w", self.callback)
self.check.append(Checkbutton(self.root, text="Option "+str(i), variable=self.var[i]))
self.check[i].pack()
def callback(self, *args):
self.frame.destroy()
self.frame = Frame(self.top)
self.frame.pack()
for i, c in zip(self.check, self.var):
if c.get() == 1:
Label(self.frame, text=i.cget("text")).pack()
root = Tk()
App(root)
root.mainloop()
Alternatively you might find that a Listbox accomplishes what you want and (IMO) looks a lot cleaner and more user friendly:
from tkinter import *
class App:
def __init__(self, root):
self.root = root
self.listbox = Listbox(self.root, selectmode=MULTIPLE)
self.listbox.pack()
for i in range(10):
self.listbox.insert(END, "Option "+str(i))
root = Tk()
App(root)
root.mainloop()
Using the Listbox you probably wouldn't even need two windows as the selection quite clearly shows which options are selected by highlighting them.

Creating new entry boxes with button Tkinter

How do i make the button to add two box (side by side) below when it is being clicked as the user decided to put more input?
def addBox():
labelframe = Tkinter.Frame()
labelframe.bind("<Add Input>", callback)
labelframe.pack()
labelframe = Tkinter.Frame()
labelFrom = Tkinter.Label(labelframe, text= "from")
labelFrom.grid(column=1, row=0)
e = Tkinter.Entry(labelframe)
e.grid(column=1, row=1)
labelTo = Tkinter.Label(labelframe, text= "to")
labelTo.grid(column=2, row=0)
e2 = Tkinter.Entry(labelframe)
e2.grid(column=2, row=1)
labelframe.pack()
addboxButton = Button( root,text='<Add Time Input>', fg="Red",command="addBox")
addboxButton.pack(side=Tkinter.TOP)
This is example how to add Entry.
Probably you get problem because you use quotation marks in command=addBox
Because you will have to get values from entries you have to remeber them on list.
I add button which print text from entries.
from Tkinter import *
#------------------------------------
def addBox():
print "ADD"
ent = Entry(root)
ent.pack()
all_entries.append( ent )
#------------------------------------
def showEntries():
for number, ent in enumerate(all_entries):
print number, ent.get()
#------------------------------------
all_entries = []
root = Tk()
showButton = Button(root, text='Show all text', command=showEntries)
showButton.pack()
addboxButton = Button(root, text='<Add Time Input>', fg="Red", command=addBox)
addboxButton.pack()
root.mainloop()
#------------------------------------
EDIT:
Example with boxes side by side.
I use new frame to keep entries side by side using grid().
This way I don't mix grid() with pack() in main window/frame.
I use len(all_entries) to get number of next free column.
from Tkinter import *
#------------------------------------
def addBox():
print "ADD"
# I use len(all_entries) to get nuber of next free column
next_column = len(all_entries)
# add label in first row
lab = Label(frame_for_boxes, text=str(next_column+1))
lab.grid(row=0, column=next_column)
# add entry in second row
ent = Entry(frame_for_boxes)
ent.grid(row=1, column=next_column)
all_entries.append( ent )
#------------------------------------
def showEntries():
for number, ent in enumerate(all_entries):
print number, ent.get()
#------------------------------------
all_entries = []
root = Tk()
showButton = Button(root, text='Show all text', command=showEntries)
showButton.pack()
addboxButton = Button(root, text='<Add Time Input>', fg="Red", command=addBox)
addboxButton.pack()
frame_for_boxes = Frame(root)
frame_for_boxes.pack()
root.mainloop()
#------------------------------------
EDIT:
Another example:
from Tkinter import *
#------------------------------------
def addBox():
print "ADD"
frame = Frame(root)
frame.pack()
Label(frame, text='From').grid(row=0, column=0)
ent1 = Entry(frame)
ent1.grid(row=1, column=0)
Label(frame, text='To').grid(row=0, column=1)
ent2 = Entry(frame)
ent2.grid(row=1, column=1)
all_entries.append( (ent1, ent2) )
#------------------------------------
def showEntries():
for number, (ent1, ent2) in enumerate(all_entries):
print number, ent1.get(), ent2.get()
#------------------------------------
all_entries = []
root = Tk()
showButton = Button(root, text='Show all text', command=showEntries)
showButton.pack()
addboxButton = Button(root, text='<Add Time Input>', fg="Red", command=addBox)
addboxButton.pack()
root.mainloop()
#------------------------------------
First of all, the indentation is a whole mess, so I don't know where does the addBox function end ..
Second, I don't think you need a button, I suppose a checkbutton will do the same thing and it's also more common and familiar to users, I once had this problem and I simply created an entry box and put above it a label indicating that it's optional, and as for code, I simply ignored it if it was empty and verified the input if I found any input ..Howerver, that was for only one entry box, and you probably will need something more complex ..
See this ..
class OptionsView(Frame):
"""Frame for options in main window"""
def __init__(self, x, y, parent):
Frame.__init__(self, parent)
self.x = x
self.y = y
self.placed = False
self.hidden = False
self.btn = Button(self, text = 'Button attached to the frame ..', command = lambda: print('Button in frame clicked ..')).pack()
def pack(self):
self.place(x = self.x, y = self.y)
self.placed = True
def toggle_view(self):
if self.hidden:
self.pack()
self.hidden = False
else:
self.place_forget()
self.hidden = True
if __name__ == '__main__':
def m_frame():
if val.get() and not options_frame.placed:
print('Showing Frame ..')
options_frame.pack()
else:
print('Toggling Frame ..')
options_frame.toggle_view()
root = Tk()
root.geometry('300x400+500+600')
root.title('Testing Hiding Frames ..')
options_frame = OptionsView(200, 300, root)
val = BooleanVar(value = False)
Checkbutton(text = 'View more Options ..', var = val, command = m_frame).place(x=root.winfo_height()/2, y=root.winfo_width()/2)
try: root.mainloop()
except e: showerror('Error!', 'It seems there\'s a problem ..', str(e))
Ofcourse you can also modify the length and the x axis of the main window if you want to be more realistic ..

Categories

Resources