I'm working with Python 2.7 and a Tkinter GUI on a Win7 machine.
There are situations where I want to completely override the normal default behavior of the Tab key, but only so long as certain conditions exist. After that I want to revert to the default behavior. (Note that at the moment I'm interested in the Tab key but I may at some point need to do this for other keys as well.)
The code snippet below (not my actual app, just a stripped-down sample) gives me the full-override that I want, but it has the side effect of "permanently" eliminating the default behavior once I do the unbind, rendering the Tab key ineffective:
import Tkinter as tk
#Root window
root = tk.Tk()
tabBlock = ''
#Tab override handler
def overrideTab(*args):
global tabBlock
if (ctrlChk4.get()==1):
tabBlock = root.bind_all('<Tab>',stopTab)
else:
root.unbind('<Tab>',tabBlock)
def stopTab(*args):
print 'Tab is overridden'
#Control variable
ctrlChk4 = tk.IntVar()
ctrlChk4.trace('w',overrideTab)
#GUI widgets
fra1 = tk.Frame(root)
chk1 = tk.Checkbutton(fra1,
text='First checkbutton')
chk2 = tk.Checkbutton(fra1,
text='Second checkbutton')
chk3 = tk.Checkbutton(fra1,
text='Third checkbutton')
chk4 = tk.Checkbutton(fra1,
text='Tab override',
variable=ctrlChk4)
fra1.grid(row=0,column=0,sticky=tk.W,padx=10,pady=10)
chk1.grid(row=0,column=0,sticky=tk.W,padx=(10,0),pady=(5,0))
chk2.grid(row=1,column=0,sticky=tk.W,padx=(10,0),pady=(5,0))
chk3.grid(row=2,column=0,sticky=tk.W,padx=(10,0),pady=(5,0))
chk4.grid(row=3,column=0,sticky=tk.W,padx=(10,0),pady=(5,0))
tk.mainloop()
I've tried variations of doing a bind instead of a bind_all, and also setting the add parameter of the binding method to 1 or '+'. These variations all give me the same result: they let me get back the default behavior once I do the unbind but they also permit the default behavior to continue while the bind is in effect.
I've scoured various online sources for a way to "save and recover" the original binding, or to "non-destructively" do a full override of the default behavior, but no luck on either count.
Is there any way to accomplish what I'm trying to do?
EDIT: When it comes to the Tab key, I know that I can mimic/replace the original default behavior with
root.focus_get().tk_focusNext().focus_set()
...but this is also a generic question. If I needed to override a key -- any key -- in in the context of a certain module (say, one that contained my own custom class for my own custom-tweaked Tkinter widget), but then revert to the binding/behavior of that key as it was in the calling module, how could I do it? Is it possible?
Okay, I tried it out and got an answer for you. What I suggested in my comment works. In summary, return "break" when you want to override it. Don't when you don't. Use bind instead of bind_all. And, if you're not already doing so in your args, factor in the event parameter, as it won't work properly otherwise. You're actually still binding something to the key, but it only overrides the default functionality in some the specified circumstance. Here's the code (adapted for both Python 2.x and 3.x):
import sys
if sys.version_info[0]<3:
import Tkinter as tk
else:
import tkinter as tk
#Root window
root = tk.Tk()
#Tab override handler
def overrideTab(*args):
root.bind('<Tab>', stopTab)
def stopTab(event=None, *args):
if ctrlChk4.get()==1:
print('Tab is overridden')
return "break"
else:
print('Tab is not overridden') #Note that it still prints this.
#Control variable
ctrlChk4 = tk.IntVar()
ctrlChk4.trace('w',overrideTab)
#GUI widgets
fra1 = tk.Frame(root)
chk1 = tk.Checkbutton(fra1,
text='First checkbutton')
chk2 = tk.Checkbutton(fra1,
text='Second checkbutton')
chk3 = tk.Checkbutton(fra1,
text='Third checkbutton')
chk4 = tk.Checkbutton(fra1,
text='Tab override',
variable=ctrlChk4)
fra1.grid(row=0,column=0,sticky=tk.W,padx=10,pady=10)
chk1.grid(row=0,column=0,sticky=tk.W,padx=(10,0),pady=(5,0))
chk2.grid(row=1,column=0,sticky=tk.W,padx=(10,0),pady=(5,0))
chk3.grid(row=2,column=0,sticky=tk.W,padx=(10,0),pady=(5,0))
chk4.grid(row=3,column=0,sticky=tk.W,padx=(10,0),pady=(5,0))
tk.mainloop()
Related
I'm using python 2.7 with Tkinter (new to Tkinter:))
I have UI with list of 20 checkboxes
once I click on one checkbox, all checkboxes are being checked, instead of one.
In code below you'll see 2 line (once with #)
with # -when click on checkbox only one is checked which is ok
without # -when click on one, all are being checked
The problem is that I want to know the status of each checkbox if checed or not and I have to define var=Intvar in order to "get" it status
Any suggestion?
Thanks in advance
Below is relevant def
def suites_checkbox_create(self):
ExcelWorkBook1 = open_workbook(config.UI_Suites_Location + 'STD_SUITES.xlsx', on_demand=True)
First_Sheet1 = ExcelWorkBook1.sheet_by_index(0)
plushight = 110
suitesList=[]
self.CheckboxList=[]
for name in (First_Sheet1._cell_values):
if name[3] == "General Name":
continue
else:
suitesList.append(name[3])
for index, name in enumerate(suitesList):
self.var=IntVar
#self.CheckboxList.append(Checkbutton(self.app, text=name)) # using this, i can check once checkbox a time
self.CheckboxList.append(Checkbutton(self.app, text=name, variable=self.var)) # with this, once i check once checkbox, all checkboxes(20) are bing checked
self.CheckboxList[index].place(y=plushight)
plushight += 20
The reason this happens is because you've given all of your Checkbutton widgets the same variable for their variable attribute.
Meaning that as soon as one of the Checkbutton widgets is ticked self.var is given a value of 1 which means that all of the Checkbutton widgets have a value of 1 which equates to them having been selected.
In short, whenever one is ticked it updates the value of all the other's because they have the same variable used to store their value.
See this in the example below:
from tkinter import *
root = Tk()
var = IntVar()
for i in range(10):
Checkbutton(root, text="Option "+str(i), variable = var).pack()
root.mainloop()
To resolve this you need to use a different variable for each Checkbutton, like the below:
from tkinter import *
root = Tk()
var = []
for i in range(10):
var.append(IntVar())
Checkbutton(root, text="Option "+str(i), variable = var[i]).pack()
root.mainloop()
I'm trying to do a GUI with Tkinter (python 2.7) that finds what serial COM's are in use and then the user can choose one to communicate with, much like Arduino's IDE in "Tools -> Port". I have two main problems: 1º When I insert the COM ports in the drop down menu, no matter which label I choose, always returns the higher COM number one. I've got an idea why but still can't solve it. 2º I don't know how to save the chosen label and use it later.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re
from Tkinter import *
import tkMessageBox
from serial import *
import serial
def find_ports(): #find all active COM's
active_ports = []
for number in range(10):
try:
verify = serial.Serial('COM'+str(number))
active_ports.append((number, verify.portstr))
verify.close()
except serial.SerialException:
pass
return active_ports
def chooseCom(index): #choose COM by clicking on a label
choosedPort = portMenu.entrycget(index, "label")
print choosedPort
pass
numPorts = find_ports()
root = Tk()
# -------------------------- Main Frames --------------------------
toolbar = Frame(root)
data = Frame(root)
# -------------------------- Menu --------------------------
menu = Menu()
root.config(menu = menu)
subMenu = Menu(menu, tearoff = 0)
menu.add_cascade(label="Ports", menu = subMenu)
portMenu = Menu(subMenu, tearoff = 0)
for i,item in enumerate(numPorts):
portMenu.add_command(label=str(item[-1]), command = lambda: chooseCom(i))
serialPort = someVAr # someVar => Store the choosed label
baudRate = 9600
ser = Serial(serialPort , baudRate, timeout=0, writeTimeout=0)
subMenu.add_cascade(label="Ports", menu = portMenu)
root.mainloop()
For the 1º problem, I think that is the 'i' variable that stays with a high number by the end of the 'for'. The following code works instead of the 'for' loop:
portMenu.add_command(label="COM1", command = lambda: chooseCom(0))
portMenu.add_command(label="COM2", command = lambda: chooseCom(1))
unfortunately it doesn't work for me because I'll run this in different computers and I can't garantee that the COM ports will be the same.
The second problem is that I want to save the chosen label in some variable (someVar) and use it later to configure my Serial connection in:
serialPort = someVAr # someVar => Store the choosed label
baudRate = 9600
ser = Serial(serialPort , baudRate, timeout=0, writeTimeout=0)
Thank you.
Use partial instead of lambda (it's only there for backward compatibility).
from functools import partial
portMenu.add_command(label=str(item[-1]), command=partial(chooseCom, i))
Also, it is good to get in the habit of not using single letters than can look like numbers, i, l, O.
I would Strongly Suggest that you learn classes before going any further as it eliminates a lot of problems. Use a data attribute to store it, so it is visible anywhere within the class http://www.diveintopython.net/object_oriented_framework/userdict.html#fileinfo.userdict.init.example
import sys
if sys.version_info[0] < 3:
import Tkinter as tk ## Python 2.x
else:
import tkinter as tk ## Python 3.x
class StoreEntry():
def __init__(self, root):
""" shows how to store something in a variable, in this case
something entered in an Entry, and then print the stored
value some time later, in this case when the Exit Button
is pressed
"""
self.root=root
self.entered="**" #default=nothing entered
tk.Label(root, text="Enter Something").grid(row=0, column=0)
self.entry_1 = tk.Entry(root, width=10, bg="lightblue")
self.entry_1.grid(row=0, column=1, sticky="W")
tk.Button(root, text="return text entered", bg="lightgreen",
command=self.print_entry).grid(row=2, column=0)
tk.Button(root, text="Exit", bg="orange",
command=self.exit_this).grid(row=3, column=0)
def exit_this(self):
print "last text entered was", self.entered
self.root.quit()
def print_entry(self):
## store in a data attribute
self.entered=self.entry_1.get()
print self.entered
root=tk.Tk()
SE=StoreEntry(root)
root.mainloop()
This question is about programming in Python 2.7.x
I wanted to code a programme where there are two functions exist: one of those is a method to get input from the user, and the other one is to show the input. Both are supposed to be done in GUI. Let's call the first function as GET TEXT function, and the second as SHOW TEXT function; my strategy is to open a GUI, show a text box, and put a button to go to SHOW TEXT function. Then, the first line of the SHOW TEXT function is to close the window opened by the GET TEXT function, get the value of the input text, and print it in another GUI.
So, I tried doing this,
from Tkinter import *
import tkMessageBox
def texttobeenteredhere():
application = Tk()
textbox = Text(application)
textbox.pack()
submitbutton = Button(application, text="OK", command=showinputtext)
submitbutton.pack()
application.mainloop()
def showinputtext():
application.quit()
thetext = textbox.get()
print "You typed", thetext
texttobeenteredhere()
I got errors that I could not comprehend, but I hope you get my idea even though my explanation could be really bad. Please suggest a solution to my problem, where the GET TEXT function and SHOW TEXT function have to exist separately in the code.
EDIT:
Thanks Josselin for introducing the syntax class in python. What I actually wanted to say was, I want the programme to open a window to get input from the user, and then close the window, and finally open another window to show the input text. I am honestly new to this, but through my prior knowledge and guessing, I tried to modify the code to meet my expectation.
import Tkinter as tk
global passtext
class application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.textbox = tk.Text(self)
self.textbox.pack()
self.submitbutton = tk.Button(self, text="OK", command=self.showinputtext)
self.submitbutton.pack()
self.mainloop()
def showinputtext(self):
self.thetext = self.textbox.get("1.0", "end-1c")
print "You typed:", self.thetext
self.destroy()
class showtext(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.setthetext = tk.StringVar()
self.setthetext.set(passtext)
self.showthetext = tk.Label(self, textvariable=self.setthetext)
self.showthetext.pack()
self.submitbutton = tk.Button(self, text="OK", command=self.destroy)
self.submitbutton.pack()
self.mainloop()
# Launch the GUI
app = application()
# Access the entered text after closing the GUI
passtext = app.thetext
text = showtext()
My English can sometimes be not understandable, but this question is answered. Thank you very much.
There are 2 main problems in your code:
First, in your showinputtext function, you want to access elements of your GUI, but they are not defined within the scope of the function.
Second, when reading the content of a tk.Text widget, the .get() method takes 2 arguments (see this link).
To fix the first problem, the best is to define your application as a class, with an inner function taking the class instance self as input argument, such that application widgets can be called within the function.
Code:
import Tkinter as tk
class application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.textbox = tk.Text(self)
self.textbox.pack()
self.submitbutton = tk.Button(self, text="OK", command=self.showinputtext)
self.submitbutton.pack()
self.mainloop()
def showinputtext(self):
self.thetext = self.textbox.get("1.0", "end-1c")
print "You typed:", self.thetext
self.destroy()
# Launch the GUI
app = application()
# Access the entered text after closing the GUI
print "you entered:", app.thetext
I have the following problem with this easy script:
from Tkinter import *
root = Tk()
while 1:
pass
I think, after the 2nd line everyone would expect a Tkinter window would appear. But it does not!
If I put this line into the Python console (without the endless-while-loop), it works.
[I wanted to add an image here, but as I'm new I'm to allowed to :-(]
But running the script (double-clicking on the *.py file in the Windows Explorer) results only in an empty Python console!
Background:
Actually I want to use Snack for Python. This is based on Tkinter. That means I have to create an Tk() instance first. Everything works fine in the Python console. But I want to write a bigger program with at least one Python script thus I cannot type the whole program into the console everytime :-)
I have installed Python 2.7 and Tcl/Tk 8.5 (remember: it works in the console)
EDIT: So here's my solution:
First, I create a class CSoundPlayer:
from Tkinter import*
import tkSnack
class CSoundPlayer:
def __init__(self, callbackFunction):
self.__activated = False
self.__callbackFunction = callbackFunction
self.__sounds = []
self.__numberOfSounds = 0
self.__root = Tk()
self.__root.title("SoundPlayer")
tkSnack.initializeSnack(self.__root)
def __mainFunction(self):
self.__callbackFunction()
self.__root.after(1, self.__mainFunction)
pass
def activate(self):
self.__activated = True
self.__root.after(1, self.__mainFunction)
self.__root.mainloop()
def loadFile(self, fileName):
if self.__activated:
self.__sounds.append(tkSnack.Sound(load=fileName))
self.__numberOfSounds += 1
# return the index of the new sound
return self.__numberOfSounds - 1
else:
return -1
def play(self, soundIndex):
if self.__activated:
self.__sounds[soundIndex].play()
else:
return -1
Then, the application itself must be implemented in a class thus the main() is defined when handed over to the CSoundPlayer() constructor:
class CApplication:
def __init__(self):
self.__programCounter = -1
self.__SoundPlayer = CSoundPlayer(self.main)
self.__SoundPlayer.activate()
def main(self):
self.__programCounter += 1
if self.__programCounter == 0:
self.__sound1 = self.__SoundPlayer.loadFile("../mysong.mp3")
self.__SoundPlayer.play(self.__sound1)
# here the cyclic code starts:
print self.__programCounter
CApplication()
As you can see, the mainloop() is called not in the constructor but in the activate() method. This is because the CApplication won't ever get the reference to CSoundPlayer object because that stucks in the mainloop.
The code of the class CApplication itself does a lot of overhead. The actual "application code" is placed inside the CApplication.main() - code which shall be executed only once is controlled by means of the program counter.
Now I put it to the next level and place a polling process of the MIDI Device in the CApplication.main(). Thus I will use MIDI commands as trigger for playing sound files. I hope the performance is sufficient for appropriate latency.
Have you any suggestions for optimization?
You must start the event loop. Without the event loop, tkinter has no way to actually draw the window. Remove the while loop and replace it with mainloop:
from Tkinter import *
root = Tk()
root.mainloop()
If you need to do polling (as mentioned in the comments to the question), write a function that polls, and have that function run every periodically with after:
def poll():
<do the polling here>
# in 100ms, call the poll function again
root.after(100, poll)
The reason you don't need mainloop in the console depends on what you mean by "the console". In IDLE, and perhaps some other interactive interpreters, tkinter has a special mode when running interactively that doesn't require you call mainloop. In essence, the mainloop is the console input loop.
I'm doing my own tweak on the technique from this post, using a canvas vs. a text widget to get some finer control over the scrolling behavior. My code appears below.
All is working as I want it but for some reason the trace I'm using to track the checkbutton values isn't working. No errors of any kind show up in the console window. But I don't get the expected printed message (from _cbWasClicked) when I click one any of the checkbuttons. As best I can tell the method is just never invoked.
I know it's got to be a simple and obvious bug but I'm stumped. I've used print statements to confirm that the 100 IntVars get instantiated as expected. Then I deliberately misspelled the method name in the .trace and this time it generated an error. So when I yank those diagnostic tweaks all should be working.... it just isn't. Can someone tell me what I'm missing?
Environment is Python 2.7 on Windows 7.
import Tkinter as tk
class myCheckList(tk.Frame):
def __init__(self, root, *args, **kwargs):
tk.Frame.__init__(self, root, *args, **kwargs)
self.root = root
self.vsb = tk.Scrollbar(self, orient="vertical")
self.canvas = tk.Canvas(self, width=200, height=290,
relief=tk.GROOVE,bd=3,
scrollregion=(0,0,0,2020),
yscrollcommand=self.vsb.set,
yscrollincrement=20)
basecolor = self.canvas.cget('background')
self.vsb.config(command=self.canvas.yview)
self.canvas.grid(row=0,column=0,sticky=tk.NSEW,padx=(0,0),pady=0)
self.vsb.grid(row=0,column=1,sticky=tk.NS,padx=(0,0),pady=0)
for i in range(100):
cbv = tk.IntVar()
cbv.trace('w',self._cbWasClicked)
cb = tk.Checkbutton(self, background=basecolor,
variable=cbv,
text="Checkbutton #%s" % i)
self.canvas.create_window(5,20*i+5,anchor=tk.NW,window=cb)
self.canvas.bind_all('<MouseWheel>',
lambda event: self.canvas.yview_scroll(-1*event.delta/120, tk.UNITS))
def _cbWasClicked(self,*args):
print 'checkbox clicked'
if __name__ == "__main__":
root = tk.Tk()
myCheckList(root).grid(row=0,column=0,sticky=tk.W,padx=0,pady=0)
root.mainloop()
Found it, after much wrestling and experimenting. It turns out that the trace works perfectly when I add a couple of lines to the class's __init__:
self.status = []
...and then, inside the loop...
self.status.append((cb,cbv))
...which tells me that garbage collection is the culprit. By creating a list and storing the object references in it, they couldn't be garbage-collected and so the .trace remains effective.
First off, you should prepend self. to cbv and cb within the FOR cycle.
Secondly, even then it is going to work only for the very last checkbox, because with each iteration you overwrite the variable cbv again and again.
As a workaround I used a list of vaiables (self.li) generated one step before the cycle. This way you can link each checkbox to its own variable:
self.li = ['cbv' + str(i) for i in range(100)]
for i in range(100):
self.li[i] = tk.IntVar()
self.cb = tk.Checkbutton(self, background=basecolor,
variable=self.li[i],
text="Checkbutton #%s" % i)
self.li[i].trace('w', self._cbWasClicked)
self.canvas.create_window(5,20*i+5,anchor=tk.NW,window=self.cb)
...
This code worked fine for me.
You will then need to identify each checkbox somehow. You can do it using the internal variable name which is passed as the first param to the callback function in the trace method (What are the arguments to Tkinter variable trace method callbacks?):
def _cbWasClicked(self, name, *args):
print('checkbox %s clicked:' % name)
In the output you'll get something like this:
checkbox PY_VAR10 clicked:
checkbox PY_VAR99 clicked:
checkbox PY_VAR0 clicked: