Item in qTableWidget won't update after a "return" key pressed - pyqt4

I have an already filled qTableWidget and I would like to change the content of one cell.
So I double-click on this cell, the previous text become highlighted, I enter a new text and I hit the "return" key.
I catch the "key pressed" signal and the connected slot contains a function that prints the content of the modified cell.
# Override qTableWidget class to catch event
class MyQTableWidget(PyQt4.QtGui.QTableWidget):
def __init__(self, parent=None):
super(MyQTableWidget, self).__init__(parent)
def keyPressEvent(self, event):
super(MyQTableWidget, self).keyPressEvent(event)
key = event.key()
if key == PyQt4.QtCore.Qt.Key_Return or key == PyQt4.QtCore.Qt.Key_Enter:
self.emit(PyQt4.QtCore.SIGNAL("returnPressed"))
...
# Connect the "keypress" signal
self.tableWidget_casesList.connect(self.tableWidget_casesList, PyQt4.QtCore.SIGNAL("returnPressed"), self.renameCase)
...
# Print the modified text of the cell
def renameCase(self):
# Get the new text entered by user
newCaseName = str(self.tableWidget_casesList.item(self.tableWidget_casesList.currentRow(), 1).text())
print("New case's name : {0:s}".format(newCaseName))
Pb : The printed text is the original one, not the modified one. I have to hit the "return" key again to get the new text printed…
Is there a specific action to do to force the update of the qTableWidget before executing the slot function ?

Ok I see your point.
As you have suggested, I have used an existing signal (cellChanged) and it works just as I expect it.
self.tableWidget_casesList.connect(self.tableWidget_casesList, PyQt4.QtCore.SIGNAL("cellChanged(int,int)"), self.renameCase)
...
def renameCase(self, row, col):
if col == 1:
# Get the new text entered by user
newCaseName = str(self.tableWidget_casesList.item(row, 1).text())
print("New case's name : {0:s}".format(newCaseName))
I have added a test on the column value in order to apply this signal on all rows of the first column only...
Thanks for your help ekhumoro !

Related

Return old value to combobox with dynamic search and autocompletion

I have a reimplemented comboBox that performs dynamic search and autocompletion (code isn't mine). The problem is when I type something, that doesn't match any value in combobox list and press enter - I receive an empty string. But I wish to receive instead an old value, that was in combobox before I started to type other value. Could anybody help me with that?
Also I want to ask the meaning of 2 strings in ExtendedComboBox class (as long as code isn't mine):
inside function on_completer_activated there is expression if text: ; I can't understand what does it mean, because I always write the whole expression (like if text == True: or something like that)
I don't understand the meaning of [str] in line self.activated[str].emit(self.itemText(index)). I have never seen this kind of construction in pyqt when something in square brackets comes directly after a signal.
code:
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class ExtendedComboBox(QtWidgets.QComboBox):
def __init__(self, parent=None):
super(ExtendedComboBox, self).__init__(parent)
self.setFocusPolicy(QtCore.Qt.StrongFocus)
self.setEditable(True)
# add a filter model to filter matching items
self.pFilterModel = QtCore.QSortFilterProxyModel(self)
self.pFilterModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.pFilterModel.setSourceModel(self.model())
# add a completer, which uses the filter model
self.completer = QtWidgets.QCompleter(self.pFilterModel, self)
# always show all (filtered) completions
self.completer.setCompletionMode(QtWidgets.QCompleter.UnfilteredPopupCompletion)
self.setCompleter(self.completer)
# connect signals
self.lineEdit().textEdited.connect(self.pFilterModel.setFilterFixedString)
self.completer.activated.connect(self.on_completer_activated)
# on selection of an item from the completer, select the corresponding item from combobox
def on_completer_activated(self, text):
if text:
index = self.findText(text)
self.setCurrentIndex(index)
self.activated[str].emit(self.itemText(index))
# on model change, update the models of the filter and completer as well
def setModel(self, model):
super(ExtendedComboBox, self).setModel(model)
self.pFilterModel.setSourceModel(model)
self.completer.setModel(self.pFilterModel)
# on model column change, update the model column of the filter and completer as well
def setModelColumn(self, column):
self.completer.setCompletionColumn(column)
self.pFilterModel.setFilterKeyColumn(column)
super(ExtendedComboBox, self).setModelColumn(column)
class ComboBox_Model(QtCore.QAbstractListModel):
def __init__(self, data_list = [], parent = None):
super(ComboBox_Model, self).__init__()
self.data_list = data_list
def rowCount(self, parent):
return len(self.data_list)
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
row = index.row()
value = self.data_list[row]
return value
if role == QtCore.Qt.EditRole:
row = index.row()
value = self.data_list[row]
return value
class Mainwindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.combobox = ExtendedComboBox()
self.layout_1 = QtWidgets.QHBoxLayout()
self.layout_1.addWidget(self.combobox)
self.setLayout(self.layout_1)
data = ['some text to display', 'other text to display', 'different text']
self.model = ComboBox_Model(data)
self.combobox.setModel(self.model)
if __name__ == '__main__':
app = QtWidgets.QApplication([])
application = Mainwindow()
application.show()
sys.exit(app.exec())
When a combobox is set as editable, by default allows insertion of non existing items at the bottom of the current model when pressing return. Since the model used in that code is not editable, when pressing return with unrecognized text the combobox is unable to add the new item (and select it), which results in setting the index to -1.
You can connect to the embedded QLineEdit returnPressed signal and check whether the current index is valid or not; this is possible because the signal is also previously connected to the combobox insertion, so when you receive the signal the combo has already tried to add the new item and eventually set the (possibly) invalid index.
In order to store the previous index, just connect to the currentIndexChanged() and save it as long as it's greater or equal to 0.
class ExtendedComboBox(QtWidgets.QComboBox):
def __init__(self, parent=None):
# ...
self.lineEdit().returnPressed.connect(self.returnPressed)
self.currentIndexChanged.connect(self.storePreviousIndex)
self.previousIndex = self.currentIndex()
def storePreviousIndex(self, index):
if index >= 0:
self.previousIndex = index
def returnPressed(self):
if self.currentIndex() < 0 or self.currentText() != self.itemText(self.currentIndex()):
self.setCurrentIndex(self.previousIndex)
Note that the second comparison in returnPressed is to add compatibility to the default internal model, in case setModel() is not called and the insertion policy is NoInsert.
About the two final questions:
the if statement checks if the condition is true or not, or, if you want, the condition is not false, as in "not nothing" (aka, False, 0, None); you can do some experiments with simple statements to better understand: if True:, if 1:, if 'something': will all result as valid conditions, while if False:, if 0: or if '': not.
some signals have multiple signatures for their arguments, meaning that the same signal can be emitted more than once, each time with different types of arguments; for example the activated signal of QComboBox is emitted twice, the first time as int with the new current index, then with the new current text; whenever you want to connect to (or emit) an overload that is not the default one, you need to specify the signature in brackets. In the case above, the signal is explicitly emitted for the str signature only (I don't know why the int was not, though). Note that overloaded signals are being gradually removed in Qt (in fact, the [str] signature of activated() is considered obsolete since Qt 5.14).

How to ensure that the "bind" order is not skipped in tkinter?

I am trying to create a game using tkinter in which the players enter their names into an entry widget.
After entering their name, the user should press enter to call the function "player_names", which would ideally save the players name in a list, delete the text in the entry widget and then continue to the next loop (i.e player).
The script seems to be ignoring the bind and and moving straight to the line "self.name_entry.destroy()". How do I ensure that the script waits for the command before continuing?
def initialise_game(self, num_of_players):
self.players_list = []
for i in range(num_of_players):
player_num = i+1
self.name_label = tk.Label(self.bg_label, text='What is the name'
' of Player ' + str(player_num) + '?')
self.name_label.grid(row=0, padx=200, pady=120)
self.name_entry = tk.Entry(self.bg_label)
self.name_entry.grid(row=1, padx=200, pady=0)
self.name_entry.bind('<Return>', self.player_names)
self.name_entry.destroy()
def player_names(self, event):
self.players_list.append(self.name_entry.get())
self.name_entry.delete(0, 'end')
Entry doesn't work like input() it will not stop code and wait till you put text and press enter. GUI creates Entry and it executes code after Entry at once. You have bind to assign function which will be executed whey you press Enter and this function should get value from Entry, and replace widgets (or only text in Label). It also should remove Entry after last player so you have to count how many times function was executed (or how many players you already have - self.player_num)
I didn't test this code but it should works.
def initialise_game(self, num_of_players):
self.players_list = []
# remeber values in class variables, not local one
self.num_of_players = num_of_players
self.player_num = 0
# create only one Label - and change text in it
self.name_label = tk.Label(self.bg_label,
self.name_label.grid(row=0, padx=200, pady=120)
# create only one Entry and assign function `self.player_names`
self.name_entry = tk.Entry(self.bg_label)
self.name_entry.grid(row=1, padx=200, pady=0)
self.name_entry.bind('<Return>', self.player_names)
# set text for first player
self.player_num += 1
self.name_label["text"] = 'What is the name of Player {} ?'.format(player_num)
def player_names(self, event):
# get player's name from Entry
self.players_list.append(self.name_entry.get())
self.name_entry.delete(0, 'end')
# set text for next player or destroy Entry after last player
self.player_num += 1
if self.player_num <= self.num_of_players:
self.name_label["text"] = 'What is the name of Player {} ?'.format(player_num)
else:
self.name_label.destroy()
self.name_entry.destroy()
The same way it works in other GUIs (not only in tkinter) and other languages (not only in Python)
My idea of what are you trying to acomplish shuld be the same, to take an input from the player one by one until all player have added a name.
The ploblem with your code is the for, it needs to complite without an input from the player (tecnicly), insted i opted for the update mecanics that tkinter have, wen a value changes in a winget the winget will update itself and i bind to the button a change value from a increment.
If you want to reuse i , reference it a the start of the def the same wey
This example works, the input of initialise_game is the same as before, replacing the conde in the middle will work just fine, except if your code uses .grid() then replace ask_name.pack()
import tkinter as tk
root = tk.Tk()
root.bg_label = tk.Frame(root)
# Added code, top and bottom are for testing
#--------------------------------------------------------------------------------
root.players_list = [] # Global variabe witch can be accesd by all funcions
i = 1 # This global value is to keep a clear reference on the count of windows
def initialise_game(self, num_of_players):
ask_name = tk.Frame(self)
L = tk.Label(ask_name, text=('What is the name of Player ' + str(i) + '?'))
L.grid(row=0, padx=200, pady=120)
E = tk.Entry(ask_name)
def modify_entry(NULL):
root.players_list.append(E.get())
E.delete(0, 'end')
global i # Change the "search" of values to outside the funtion
i += 1
L.config(text=('What is the name of Player ' + str(i) + '?'))
if (i > num_of_players): # Close the Frame wen all were answered
root.players_list.append(E.get())
ask_name.destroy()
E.bind('<Return>', modify_entry)
E.grid(row=1, padx=200, pady=0)
ask_name.pack() # You can replace it with grin , this moves the Label + Entry were u want
#--------------------------------------------------------------------------------
initialise_game(root.bg_label, 3)
root.bg_label.pack()
root.mainloop()

Python 3 Jump Tables

I am trying to figure out how to create a basic jump table, so I can better understand different ways of creating menus in Python 3.5.6. Here is what I have so far:
def command():
selection = input("Please enter your selection: ")
return selection
def one():
print ("you have selected menu option one")
def two():
print ("you have selected menu option two")
def three():
print ("you have selected menu option three")
def runCommand(command):
jumpTable = 0
jumpTable[command]()
jumpTable = {}
jumpTable['1'] = one
jumpTable['2'] = two
jumpTable['3'] = three
def main():
command()
runCommand(command)
if __name__ == "__main__":
main()
As far as I understand, a jump table is simply a way of making a menu selection and calling a specific function associated with that numerical value, taken in by my "command" function. Within the jumpTable, you assign the function to call.
I am getting " File "omitted", line 16, in runCommandjumpTableone
TypeError: 'int' object is not subscriptable
All I want to do is have the user enter a number - 1, 2 or 3 and have that function run. when I get this basic functionality down, I will expand the menu to show the options and be more clear. I just need to get the darn thing to run!
Yes, I am aware of other ways to create menus (IF/ELIF/ELSE) I am just trying to nail this one down!
Thank you in advance!
You are quite close. The only issue is that you are trying to access the command before creating the jumpTable. And I am also not sure why you are setting the variable to 0 first (that's why you get the int is not subscriptible error). So, this is the right order:
def runCommand(command):
jumpTable = {}
jumpTable['1'] = one
jumpTable['2'] = two
jumpTable['3'] = three
jumpTable[command]()
By the way, if you are always creating the same jumpTable, you could create it once, outside the function and simply call jumpTable[command]() in your main function.
Another problem: you should store the value you get from the user and pass that to the next function like this:
cmd = command()
runCommand(cmd)
, or simply pipe the two functions together like this:
runCommand(command())
"""
Based on the original question, the following will.
Add a menu to a console application to manage activities.
Run a selected function.
Clear the output
Display the menu again or exit if done is selected
"""
import sys
from os import system
def display_menu(menu):
"""
Display a menu where the key identifies the name of a function.
:param menu: dictionary, key identifies a value which is a function name
:return:
"""
for k, function in menu.items():
print(k, function.__name__)
def one():
print("you have selected menu option one")
input("Press any Enter to return to menu.")
system('cls') # clears stdout
def two():
print("you have selected menu option two")
input("Press any Enter to return to menu.")
system('cls') # clears stdout
def three():
print("you have selected menu option three")
input("Press any Enter to return to menu.")
system('cls') # clears stdout
def done():
system('cls') # clears stdout
print("Goodbye")
sys.exit()
def main():
# Create a menu dictionary where the key is an integer number and the
# value is a function name.
functions_names = [one, two, three, done]
menu_items = dict(enumerate(functions_names, start=1))
while True:
display_menu(menu_items)
selection = int(
input("Please enter your selection number: ")) # Get function name
selected_value = menu_items[selection] # Gets the function name
selected_value() # add parentheses to call the function
if __name__ == "__main__":
main()

tkinter GUI design: managing variables from multiple widgets/toolbars

{Edit: the answer by Bryan Oakley in the suggested duplicate question enter link description here a) fires a response on change to the array variable (arrayvar.trace mode="w"), and I need it triggered on FocusOut, as described in my original question; b) works for Python 2, but I'm having trouble converting it to work in Python 3.5. I'm currently using his and pyfunc's answers as leads and trying to figure out a similar solution using a FocusOut event.}
I am working on a tkinter GUI that lets a user select a particular type of calculation, using a pair of radio button lists. Based on the selections, a tool bar is populated with multiple modular entry widgets, one for each variable the calculation requires. The goal is to have the numerical entry values passed to the model, which will return data to be graphed on a canvas or matplotlib widget.
My question is: what typical strategy is used for gathering and continually refreshing values from multiple widgets, in order to update displays and to pass them on to the model? The trick here is that there will be a large number of possible calculation types, each with their own toolbar. I'd like the active toolbar to be "aware" of its contents, and ping the model on every change to a widget entry.
I think the widgets and the toolbar would have to be classes, where the toolbar can query each widget for a fresh copy of its entry values when a change is detected, and store them as some collection that is passed to the model. I'm not entirely sure how it can track changes to the widgets. Using a "validate='focusout' " validation on the entry widget (e.g. as in
this validation reference )
suggests itself, but I already use "validate='key' " to limit all entries to numbers. I don't want to use "validate=all" and piggyback onto it because I don't want to continually ask the model to do a lengthy calculation on every keypress.
I'm new to GUI programming, however, so I may be barking up the wrong tree. I'm sure there must be a standard design pattern to address this, but I haven't found it.
Below is a screenshot of a mockup to illustrate what I want the GUI to do. The Task radiobutton controls which secondary button menu appears below. The selection in the second menu populates the top toolbar with the necessary entry widgets.
The following code does (mostly) what I want. The ToolBar frame objects will store the values from its contained widgets, and call the appropriate model as needed. The VarBox objects are Entry widgets with extra functionality. Hitting Tab or Return refreshes the data stored in the ToolBar dictionary, tells the ToolBar to send data to the model, and shifts focus to the next VarBox widget.
from tkinter import *
# Actual model would be imported. "Dummy" model for testing below.
def dummy_model(dic):
"""
A "dummy" model for testing the ability for a toolbar to ping the model.
Argument:
-dic: a dictionary whose values are numbers.
Result:
-prints the sum of dic's values.
"""
total = 0
for value in dic.values():
total += value
print('The total of the entries is: ', total)
class ToolBar(Frame):
"""
A frame object that contains entry widgets, a dictionary of
their current contents, and a function to call the appropriate model.
"""
def __init__(self, parent=None, **options):
Frame.__init__(self, parent, **options)
self.vars = {}
def call_model(self):
print('Sending to dummy_model: ', self.vars)
dummy_model(self.vars)
class VarBox(Frame):
"""
A customized Frame containing a numerical entry box
Arguments:
-name: Name of the variable; appears above the entry box
-default: default value in entry
"""
def __init__(self, parent=None, name='', default=0.00, **options):
Frame.__init__(self, parent, relief=RIDGE, borderwidth=1, **options)
Label(self, text=name).pack(side=TOP)
self.widgetName = name # will be key in dictionary
# Entries will be limited to numerical
ent = Entry(self, validate='key') # check for number on keypress
ent.pack(side=TOP, fill=X)
self.value = StringVar()
ent.config(textvariable=self.value)
self.value.set(str(default))
ent.bind('<Return>', lambda event: self.to_dict(event))
ent.bind('<FocusOut>', lambda event: self.to_dict(event))
# check on each keypress if new result will be a number
ent['validatecommand'] = (self.register(self.is_number), '%P')
# sound 'bell' if bad keypress
ent['invalidcommand'] = 'bell'
#staticmethod
def is_number(entry):
"""
tests to see if entry is acceptable (either empty, or able to be
converted to a float.)
"""
if not entry:
return True # Empty string: OK if entire entry deleted
try:
float(entry)
return True
except ValueError:
return False
def to_dict(self, event):
"""
On event: Records widget's status to the container's dictionary of
values, fills the entry with 0.00 if it was empty, tells the container
to send data to the model, and shifts focus to the next entry box (after
Return or Tab).
"""
if not self.value.get(): # if entry left blank,
self.value.set(0.00) # fill it with zero
# Add the widget's status to the container's dictionary
self.master.vars[self.widgetName] = float(self.value.get())
self.master.call_model()
event.widget.tk_focusNext().focus()
root = Tk() # create app window
BarParentFrame = ToolBar(root) # holds individual toolbar frames
BarParentFrame.pack(side=TOP)
BarParentFrame.widgetName = 'BarParentFrame'
# Pad out rest of window for visual effect
SpaceFiller = Canvas(root, width=800, height=600, bg='beige')
SpaceFiller.pack(expand=YES, fill=BOTH)
Label(BarParentFrame, text='placeholder').pack(expand=NO, fill=X)
A = VarBox(BarParentFrame, name='A', default=5.00)
A.pack(side=LEFT)
B = VarBox(BarParentFrame, name='B', default=3.00)
B.pack(side=LEFT)
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