How do i add multiple events to a binding in tkinter - python-3.x

I want to bind a widget to a single function so when its clicked and the "r" key is being pressed at the same time it gets called.
i have tryed <Button-1-R> and got the error "_tkinter.TclError: extra characters after detail in binding"
widged.bind("<Button-1>",function)

If you want to bind combinations, you can do so with a single bind by concatenating the events. You can optionally add whitespace between each event.
For example, to bind a click followed by pressing the "r" key you can do it like so:
widget.bind("<ButtonPress-1> <r>", function)
If you want the reverse -- the letter "r" followed by a click, just reverse them. However, you may have difficulties depending on your system since some systems have an autorepeat for keys.
widget.bind("<r><ButtonPress-1>", function).
It's important to know that tkinter processes events literally. For example, if you click the button and then press "r", the binding will fire. If you press "r" again the binding won't fire since it isn't immediately preceeded by a click.
It's unclear exactly what you're trying to accomplish, but the other solution is to set a flag in the handler for one event (either the click or the key), and check for the flag in the other.
For example:
def set_flag(value):
global flag
flag = True
def function(event):
if flag:
... process the event here ...
widget.bind("<ButtonPress-1>", lambda event: set_flag(True))
widget.bind("<ButtonRelease-1>", lambda event: set_flag(False))
widget.bind("<r>", function)

You can capture 3 events: <Button-1>, <r> and <ButtonRelease-1>. Fire your function only when both button 1 and r is triggered.
import tkinter as tk
root = tk.Tk()
entry = tk.Entry(root)
entry.insert(0,"Left click and press R")
entry.pack()
class Bindings:
def __init__(self):
self.but_1 = False
entry.bind("<Button-1>", self.mouse_clicked)
entry.bind("<r>", self.r_clicked)
entry.bind("<ButtonRelease-1>", self.mouse_release)
def mouse_release(self,event):
self.but_1 = False
def mouse_clicked(self,event):
self.but_1 = True
print ("Mouse button 1 clicked")
def r_clicked(self,event):
if self.but_1:
print ("Both keys clicked")
self.but_1 = False
else:
print ("Key R pressed")
Bindings()
root.mainloop()

Related

Python tkinter text widget 'Modified' event doesn't seem to fire correctly

I wanted to monitor when the text in a tkinter Text widget was modified so that a user could save any new data they had entered. Then on pressing 'Save' I wanted to reset this.
I bound the Text widget's <<Modified>> event to a function so that making any changes to the text would update the 'Save' button from 'disabled' to 'normal' state. After hitting the Save button I ran a function which reset the modified flag and disabled the Save button again until further changes were made.
But I found that it seemed to only fire the event once. Hitting Save didn't reset the button to a 'disabled' state, and editing the text didn't seem to affect the Save button's state either after the first time.
Below is a minimal example to show how the flag doesn't seem to be reset.
EXAMPLE
import tkinter as tk
root = tk.Tk()
def text_modified(event=None):
status_label.config(text="Modified = True")
def reset():
if not text_widget.edit_modified():
return
status_label.config(text="Modified = False")
text_widget.edit_modified(False)
text_widget = tk.Text(root, width=30, height=5)
text_widget.pack()
text_widget.bind("<<Modified>>", text_modified)
status_label = tk.Label(root, text="Modified = False")
status_label.pack()
reset_btn = tk.Button(root, text="Reset", command=reset)
reset_btn.pack()
root.mainloop()
SOLUTION
It turns out that binding the <<Modified>> event to a function means that the function will run not when the Text widget text is changed, but whenever the modified flag is changed - whether it changes to True or to False. So my Save button was saving the data, disabling itself, and resetting the modified flag to False, and this flag change fired the <<Modified>> event, which was bound to a function which un-disabled the Save button again.
Here's a minimal example which shows what's going on. We just need to adjust the function we've bound the <<Modified>> event to so that it deals with modified being False as well:
import tkinter as tk
root = tk.Tk()
def modified_flag_changed(event=None):
if text_widget.edit_modified():
status_label.config(text="Modified = True")
print("Text modified")
else:
print("Modified flag changed to False")
def reset():
if not text_widget.edit_modified():
print("Doesn't need resetting")
return
status_label.config(text="Modified = False")
text_widget.edit_modified(False)
print('Reset')
text_widget = tk.Text(root, width=30, height=5)
text_widget.pack()
text_widget.bind("<<Modified>>", modified_flag_changed)
status_label = tk.Label(root, text="Modified = False")
status_label.pack()
reset_btn = tk.Button(root, text="Reset", command=reset)
reset_btn.pack()
root.mainloop()

Checkbutton command binding to wrong values when instantiated in loop

When I click on a checkbutton in my project, it is not executing the correct functionality. The project can be found at https://github.com/shitwolfymakes/Endless-Sky-Mission-Builder/ (indev branch)
I am building an application using tkinter, and am working on a function to dynamically place ttk.Entry objects next to ttk.Checkbutton objects, and then link them together.
I have already rewritten this function a few times, and even added a special case for when self.numMandatory is 0, but nothing has worked.
This is taken from guiutils.py, line 323.
# add the optional fields
for i in range(self.numMandatory, self.numFields):
print(self.rowNum)
self.listEntryStates.append(BooleanVar())
self.listEntryData.append(StringVar())
self.listEntryData[-1].set(self.listDefaultEntryData[i])
self.listEntries.append(ttk.Entry(self, textvariable=self.listEntryData[-1], state=DISABLED, style="D.TEntry"))
self.listEntries[-1].grid(row=self.rowNum, column=1, sticky="ew")
#print(self.listEntryStates[-1])
#print(self.listEntries)
self.listCheckbuttons.append(ttk.Checkbutton(self, onvalue=1, offvalue=0, variable=self.listEntryStates[-1],
command=lambda: self.cbValueChanged(self.listEntryStates[-1],
[self.listEntries[-1]])))
self.listCheckbuttons[-1].grid(row=self.rowNum, column=2, sticky="e")
print(self.listCheckbuttons[-1].__str__(), end=" is bound to: ")
print(self.listEntries[-1].__str__(), self.listEntryStates[-1])
self.rowNum += 1
# end for
This is taken from guiutils.py, line 349
def cbValueChanged(self, entryState, modifiedWidgets):
for widget in modifiedWidgets:
print("The value of %s is:" % widget, end="\t\t")
print(entryState.get())
if type(widget) is str:
break
elif entryState.get() is True:
widget.config(state='enabled', style='TEntry')
elif entryState.get() is False:
widget.config(state='disabled', style='D.TEntry')
#end for
#end cbValueChanged
In the main window, when I scroll down and click "add trigger", the new window appears properly. But when I click on the checkbutton next to the Entry that says "[<base#>]", that entry should be enabled by cbValueChanged.
For some reason, when the loop to add the optional fields runs, the command= section binds only the last entry in self.listEntries (but the entry it's binding each checkbutton to isn't created until the very last time through the loop)
I'm not sure where else I could ask a question like this, and I know this is asking more than most questions. If there is any more information you need, I'll be happy to provide it.
You ~~can't~~ edit: shouldn't use lambda in a loop. Frankly you shouldn't use it at all. Use functools.partial or make a real closure.
from functools import partial
self.listCheckbuttons.append(ttk.Checkbutton(self, onvalue=1, offvalue=0, variable=self.listEntryStates[-1],
command=partial(self.cbValueChanged,self.listEntryStates[-1],[self.listEntries[-1]])))

Tkinter - How to trace expanding list of variables

What I am trying to do track when any values in a list of StringVar change, even when the list is expanding. Any additions to the list before the trace statement will result in the callback. But any additions afterward, such as when pressing a button, will not cause any callback.
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root)
frame.grid(row=0)
L = []
def add_entry(event):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
add = tk.Button(frame,text='add Entry',command='buttonpressed')
add.grid(row=0)
add.bind('<Button-1>',add_entry)
for i in range(2):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
for i in L:
i.trace('w',lambda *arg:print('Modified'))
root.mainloop()
Modifying the first two Entry's prints out Modified, but any Entry's after the trace is run, such as the ones produced when a button is pressed, will not.
How do I make it so that trace method will run the callback for the entire list of variables even if the list is expanded?
Simple suggestion, change your add_entry function to something like this:
def add_entry(event):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
L[len(L)-1].trace('w',lambda *arg:print('Modified'))
Extra suggestions:
This add = tk.Button(frame,text='add Entry',command='buttonpressed') is assigning a string to command option, means it will try to execute that string when button is clicked(which will do nothing). Instead, you can assign your function add_entry to command option and it will call that function when button is clicked and you can avoid binding Mouse Button1 click to your Button(Note: No need to use argument event in function when using like this). Read more here
Python supports negative indexing of List, so you can call L[-1] to retrieve the last element in the list instead of calling L[len(L)-1]).
Once you change your add_entry function as suggested, you can reduce your code to
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root)
frame.grid(row=0)
L = []
def add_entry():
global L
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
L[-1].trace('w',lambda *arg:print('Modified'))
add = tk.Button(frame,text='add Entry',command=add_entry)
add.grid(row=0)
for i in range(2):
add_entry()
root.mainloop()

Use return key and a button at the same time in tkinter

I want to write a program for my biology class... I want to integrate the function that you can type something in the Entry bar and then you can use the button or click the return key. I've the problem, that I just can click the button. Everything else don't work. Here is my code (in a simple form):
from tkinter import *
import tkinter as tk
# Main Graphic User Interface
root = Tk()
root.title("Genetic Translator")
root.geometry("300x175")
root.resizable(0,0)
# Solid Label "Information for Input"
s_label2 = Label(root, text = "\nInput Tripplet which decodes for an amino acid:\n")
s_label2.pack()
# Entry Bar
trip = Entry(root)
trip.pack()
# Function for setting focus on entry bar
trip.focus_set()
# Dictionary
output = {"GCX":"Alanine [Ala]"}
# Dict Function Function (Trans: trip -in- AS)
def dict_function1():
global o_screen
o_screen.configure(text=(output.get(trip.get().upper(),"Unknown tripplet!")))
# Bind the Return Key for Input
trip.bind("<Return>", dict_function1)
# Space Label 1
space_label1 = Label(root)
space_label1.pack()
# Button "Confirm"
mainbutton = Button(root, text = "Confirm", command = dict_function1)
mainbutton.pack()
# Space Label 2
space_label2 = Label(root)
space_label2.pack()
# Output Screen
o_screen = Label(root)
o_screen.pack()
# Mainloop function for Interface Options
root.mainloop()
Thank you for helping me.
When you press return key it will send event as argument to dict_function1 and when you click on the button nothing is send.
add argument to dict_function1 with None as default value.
def dict_function1(event=None)
Function assigned to button is called without arguments but assigned by bind is called with argument - event information - so your function have to receive that argument
def dict_function1(event=None): # None for "command="
--
<Return> binded to Entry will work only if Entry is focused, but not when Button is focused. If you bind <Return> to root then <Return> will work in both situations.
You neglected to say what "don't work" means. When I run your code from IDLE, enter 3 letters, and hit return, I get the following
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Programs\Python35\lib\tkinter\__init__.py", line 1549, in __call__
return self.func(*args)
TypeError: dict_function1() takes 0 positional arguments but 1 was given
The issue is that when tk calls a 'command', it does not pass any arguments, but when it calls a function bound to an event, it passes an event argument. So add an optional parameter to the function.
def dict_function1(event=None):
It works for me, except for the error message when the Enter key is pressed which you don't provide, so that may or may not be the problem. It is easily fixed, but "everything else don't work" is way too vague to help you with. See "Capturing keyboard events" at http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm You should also include code if the Entry value is not found in the dictionary. Finally, you import Tkinter twice in the first 2 statements in the program. Choose one or the other.
from tkinter import *
# Main Graphic User Interface
root = Tk()
root.title("Genetic Translator")
root.geometry("300x175")
root.resizable(0,0)
# Solid Label "Information for Input"
s_label2 = Label(root, text = "\nInput Tripplet which decodes for an amino acid:\n")
s_label2.pack()
# Entry Bar
trip = Entry(root)
trip.pack()
# Function for setting focus on entry bar
trip.focus_set()
# Dictionary
output = {"GCX":"Alanine [Ala]"}
# Dict Function Function (Trans: trip -in- AS)
def dict_function1(arg=None): ## capture the event from the Return key
##global o_screen
o_screen.configure(text=(output.get(trip.get().upper(),"Unknown tripplet!")))
# Bind the Return Key for Input
trip.bind("<Return>", dict_function1)
# Space Label 1
space_label1 = Label(root)
space_label1.pack()
# Button "Confirm"
mainbutton = Button(root, text = "Confirm", command = dict_function1)
mainbutton.pack()
# Space Label 2
space_label2 = Label(root)
space_label2.pack()
# Output Screen
o_screen = Label(root)
o_screen.pack()
# Mainloop function for Interface Options
root.mainloop()

PyQt: How to get QTableWidgetItem contents while item is being edited?

In the end, the problem I'm trying to solve is that of someone editing a field in a QTableWidget and then clicking "OK" before hitting the enter key or changing focus out of the table cell.
Default behavior seems to be to ignore this cell, as it hasn't "committed".
Here's a quick example:
#!/usr/bin/env python
import sys
import pprint
from PyQt4 import QtCore,QtGui
class Dialog(QtGui.QDialog):
def __init__(self,parent=None):
super(Dialog,self).__init__(parent)
self.table = QtGui.QTableWidget(5,2)
button_box = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.table)
layout.addWidget(button_box)
self.setLayout(layout)
def accept(self):
ret = {}
for i in range(self.table.rowCount()):
k = self.table.item(i,0)
v = self.table.item(i,1)
if not k:
continue
if k.text().isEmpty():
continue
if not v:
v = QtGui.QTableWidgetItem("")
ret[str(k.text())] = str(v.text())
pprint.pprint(ret)
def main():
app = QtGui.QApplication(sys.argv)
main = Dialog()
main.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
In this example, if I enter a in the first cell then b in the second cell; then click "OK" without first hitting the enter key or changing focus, I will see printed:
{'a': ''}
When I want to see:
{'a': 'b'}
An idea I had was to treat the cell like a QLineEdit and use textChanged to see when the user was typing, and then, behind the scenes, setItem of the cell with each key stroke -- the idea being that the data in the cell is always up to date. I attempted this by using QStyledItemDelegate (below) so that it edits like a QLineEdit (which has a textChanged signal). This works to some degree, as I can print out the changes from the delegate itself, but I can't seem to get the textChanged signal anywhere it's useful (in other words, the dialog doesn't see this, therefore it can't setItem in the table).
class LineEditDelegate(QtGui.QStyledItemDelegate):
textChanged = QtCore.pyqtSignal(str)
def createEditor(self, parent, option, index):
editor = QtGui.QLineEdit(parent)
editor.textChanged.connect(self.textChanged)
return editor
But that's not doing the trick.
I also tried emitting a commitData signal when the QLineEdit's textChanged fires, but that also has not helped.
Is there a way to get cell contents while the cell is still being edited?

Resources