pyQt5 : issue updating editable ComboBox - python

I have a problem with this piece of code.
projList is a variable of type list. self.projPicker is an instance of QComboBox.
self.projPicker.addItems(projList)
self.projPicker.currentTextChanged.connect(self.itemListChange)
def itemListChange(self,value):
self.projPathLbl.setText("Project : " + value)
def itemListUpdate(self):
comboItems = []
for item in range (self.projPicker.count()):
comboItems.append(self.projPicker.itemText(item))
print(comboItems)
With this code, when I add text in in the combobox the self.projPathLbl is updated with the itemListChange() function each time I type a character .
My problem is, it doesn't work the same way with the itemListUpdate().
With this function, I need to hit the Return key to the update to be effective.
How can I update my self.projPathLbl label the same way I update my comboItems list ( validating it with the Return key )?

Maybe one way to do that would be to implement QComboBox as a custom class with an implemented keypress event like here: PyQt Connect to KeyPressEvent
In the keypress implementation you can filter the enter key and emit your signal, which you can connect to the itemListChange slot.

Related

How to manage shift+return key presses [duplicate]

I have an element, editorBox which is of the PyQt5 element type QPlainTextEdit. My target goal is to call a function when the hotkey Shift + Return is pressed, and my goal with this function is that it will also insert text into the editorBox element (this isn't the part I'm stressed about, it's fairly easy to do with the .insertPlainText() method).
I've done searching, and the closest result I could find was to use QShortcut & QKeySequence paired together like so:
# Initialize the QShortcut class
self.keybindShiftEnter = QShortcut(QKeySequence("Shift+Return"), self)
# Connect the shortcut event to a lambda which executes my function
self.keybindShiftEnter.activated.connect(lambda: self.editorBox.insertPlainText("my text to insert"))
For clarification, I have tried using other characters in the QKeySequence constructor, such as Ctrl+b, and I've had success with it. Oddly enough, only the combination Shift+Return doesn't work for me.
I've analyzed a problem with it in relation to my bug. Some of the posts I've viewed:
This is for triggering a button, not a QPlainTextEdit.
Best thing I've tried, almost worked up until I tried Shift+Return
Any solution with keyPressEvent wouldn't work, because keys other than Shift+Enter wouldn't be typed into the editorBox
Solved my own problem:
# ... editorBox Initialization code ...
self.editorBox.installEventFilter(self)
# Within App class
def eventFilter(self, obj, event):
if obj is self.editorBox and event.type() == QEvent.KeyPress:
if isKeyPressed("return") and isKeyPressed("shift"):
self.editorBox.insertPlainText("my text to insert")
return True
return super(App, self).eventFilter(obj, event)
What I'm doing here is setting a filter function- basically, every time a key is pressed (any key, including space/backspace/etc) it will call the eventFilter function. The first if statement makes sure that the filter will only pass through if it is a key stroke (not entirely sure if this part is necessary, I don't think clicks trigger the function). Afterwards, I utilize the isKeyPressed function (a renamed version of the keyboard module's is_pressed function) to detect if the current key is held down. With the and operator, I can use this to make keybind combos.

Execute Key Sequence on Button Click

I'm trying to create an Undo and Redo Button inside a GUI App using PyQt5 and Python 3.7.
When the Undo and Redo Buttons are clicked, the Key Sequences "Ctrl+Z" and "Ctrl+Y" should be executed respectively. Ive superficially gone through the documentation of QShortCut and QKeySequence but they seem to be designed for detecting key sequences and not triggering them. So how do I implement these buttons?
As per eyllanesc's comment, I'm adding this to better explain what I am trying to achieve.
self.undoButton = self.findChild(QtWidgets.QPushButton, 'undoButton')
self.undoButton.clicked.connect(self.undoButtonPressed)
self.anyPlainTextEdit = self.findChild(QtWidgets.QPlainTextEdit, 'anyPlainTextEdit')
# Function to Execute Key Sequence
def undoButtonPressed(self):
# Execute Ctrl+Z Key Sequence
I'm wondering if this is even possible.
If not, should I maintain Previous and current values of the PlainTextArea in separate variables and set the value of the PlainTextArea accordingly?
You don't have to launch the shortcut to enable redo or undo, just call the slot redo() and undo() when the buttons are pressed:
self.undoButton.clicked.connect(self.anyPlainTextEdit.undo)
self.redoButton.clicked.connect(self.anyPlainTextEdit.redo)

PyQt : get a value from combobox when a button pressed

I use qt designer and convert it from *.ui to *.py, I want to make application for send and receive serial data,,
i use combobox to alow user for setting serial comŲ©unication
self.ui.comboBox_2.addItems(['2400','4800','9600','19200'])
my question is how can i get value from combobo_2 to fill serial buadrate when I click a button
this is my code
self.connect(self.ui.comboBox_2, QtCore.SIGNAL('activated(QString)'),ser.baudRate())
and get an error
File "mainw.py", line 18, in press_2 self.connect(self.ui.comboBox_2,
QtCore.SIGNAL('activated(QString)'),ser.baudRate()) AttributeError:
'Serial' object has no attribute 'baudRate'
Your question about using a button to get the value from the combo box is different than what you are currently doing which is using a signal directly from when an value in the combo box was selected.
Your error is related to something else, it looks like in your signal you are calling a function "ser.baudRate()" but you have to pass in a function object, as it will pass in whatever "ser.buadRate()" returns. Which probably isn't a function. I'm not sure what that function returns. In any case, here is some ideas:
Using a button
If you want to use a button, then you would write something like this:
self.connect(self.ui.myButton, QtCore.SIGNAL('clicked()'), self.updateBaudRate)
def updateBaudRate(self):
# get value from combo box
rate = str(self.ui.comboBox_2.currentText()) # convert to string otherwise you will get a QString which is sometimes not friendly with other tools
ser.baudRate(rate)
Using the combo box signal
self.connect(self.ui.comboBox_2, QtCore.SIGNAL('currentIndexChanged(QString)'), self.updateBaudRate)
def updateBaudRate(self, rate):
ser.baudRate(str(rate)) # again convert to string as it my not accept a QString
You could use partial from the functools module or use a lambda instead of writing a function for the signal, but this is just for example.
You probably also want to use the "currentIndexChanged" signal instead of "activated" as "currentIndexChanged" will only emit when the value has changed, otherwise it will signal even if the user didn't select a different value in the combo box.

PyQt - How to connect multiple signals to the same widget

[ ]All1 [ ]All2
[ ]checkbox1A [ ]checkbox1B
[ ]checkbox2A [ ]checkbox2B
Based on the chart above, a few things need to happen:
The All checkboxes only affect the on/off of the column it resides in, and checks on/off all the checkboxes in that column.
All checkboxes work in pairs, so if checkbox1A is on/off, checkbox1B needs to be on/off
If an All checkbox is checked on, and then the user proceeds to check off one or more checkbox in the column, the All checkbox should be unchecked, but all the checkboxes that are already checked should remain checked.
So really this is more like a chain reaction setup. If checkbox All1 is on, then chieckbox1A and 2A will be on, and because they are on, checkbox1B and 2B are also on, but checkbox All2 remains off. I tried hooking up the signals based on this logic, but only the paired logic works 100%. The All checkbox logic only works 50% of the time, and not accurately, and there's no way for me to turn off the All checkbox without turning all already checked checkboxes off.
Really really need help ... T-T
Sample code:
cbPairKeys = cbPairs.keys()
for key in cbPairKeys:
cbOne = cbPairs[key][0][0]
cbTwo = cbPairs[key][1][0]
cbOne.stateChanged.connect(self.syncCB)
cbTwo.stateChanged.connect(self.syncCB)
def syncCB(self):
pairKeys = cbPairs.keys()
for keys in pairKeys:
cbOne = cbPairs[keys][0][0]
cbOneAllCB = cbPairs[keys][0][4]
cbTwo = cbPairs[keys][1][0]
cbTwoAllCB = cbPairs[keys][1][4]
if self.sender() == cbOne:
if cbOne.isChecked() or cbTwoAllCB.isChecked():
cbTwo.setChecked(True)
else:
cbTwo.setChecked(False)
else:
if cbTwo.isChecked() or cbOneAllCB.isChecked():
cbOne.setChecked(True)
else:
cbOne.setChecked(False)
EDIT
Thanks to user Avaris's help and patience, I was able to reduce the code down to something much cleaner and works 100% of the time on the 1st and 2nd desired behavior:
#Connect checkbox pairs
cbPairKeys = cbPairs.keys()
for key in cbPairKeys:
cbOne = cbPairs[key][0][0]
cbTwo = cbPairs[key][1][0]
cbOne.toggled.connect(cbTwo.setChecked)
cbTwo.toggled.connect(cbOne.setChecked)
#Connect allCB and allRO signals
cbsKeys = allCBList.keys()
for keys in cbsKeys:
for checkbox in allCBList[keys]:
keys.toggled.connect(checkbox.setChecked)
Only need help on turning off the All checkbox when the user selectively turns off the modular checkboxes now
If I'm understanding your data structure, I have a solution. Correct me if I'm wrong: allCBList is a dict (confusing name! :) ). Its keys are the all* checkboxes. And a value allCBList[key] is a list with checkboxes associated with that all checkbox. For your example structure it'll be something like this:
{ All1 : [checkbox1A, checkbox1B],
All2 : [checkbox2A, checkbox2B]}
Then what you need to is this: when a checkbox is toggled and it is in checked state, then you need to check the All* checkbox if all the other checkboxes are in checked state. Otherwise it will be unchecked.
for key, checkboxes in allCBList.iteritems():
for checkbox in checkboxes:
checkbox.toggled.connect(lambda checked, checkboxes=checkboxes, key=key: key.setChecked(checked and all(checkbox.isChecked() for checkbox in checkboxes))
I guess, this statement requires a bit of explanation:
lambda checked, checkboxes=checkboxes, key=key:
lambda creates the callable that is connected to the signal. toggled passes checkbox status, and it will be passed to checked variable. checkboxes=checkboxes and key=key parts pass the current values to checkboxes and key parameters of the lambda. (You need this because of the closure in lambdas)
Next comes:
key.setChecked(...)
We are setting the checked state of key which is the appropriate All* checkbox. And inside this:
checked and all(checkbox.isChecked() for checkbox in checkboxes)
all is True if everything inside is True, where we check every checkboxs state. And this will return True if all are checked (i.e. isChecked() returns True).
checked and ... part is there to short-circuit the all. If the current checkbox turns unchecked, then we don't need to check others. All* would be unchecked.
(PS: By the way, you don't need to get .keys() of a dict to iterate over keys. You can just iterate over the dict and it will iterate over its keys.)
Edit: Just to avoid chain reaction with All* checkboxes toggled by clicking any sub-checkboxes, it's necessary to change the signal for All* checkboxes to clicked, instead of toggled. So, the All* check boxes will affect other below them only in the case of user interaction.
In the end, your modified code will be:
# Connect checkbox pairs
# you just use the values
# change 'itervalues' to 'values' if you are on Python 3.x
for cbPair in cbPairs.itervalues():
cbOne = cbPair[0][0]
cbTwo = cbPair[1][0]
cbOne.toggled.connect(cbTwo.setChecked)
cbTwo.toggled.connect(cbOne.setChecked)
# Connect allCB and allRO signals
# change 'iteritems' to 'items' if you are on Python 3.x
for key, checkboxes in allCBList.iteritems():
for checkbox in checkboxes:
key.clicked.connect(checkbox.setChecked)
checkbox.toggled.connect(lambda checked, checkboxes=checkboxes, key=key: key.setChecked(checked and all(checkbox.isChecked() for checkbox in checkboxes))
Your problem is that your checkboxes are connecting the toggled signal and toggling their state in your connected slots so the signal is emitted again (so the slots are executed again...) and you get unpredictable results. Obviously that is not your wanted behavior. You can fix it in several ways:
by disconnecting the signals at the beginning of the slots and connecting them again at the end
by using some clever code that controls the re-emission of signals (I think this is what Avari's code does in a very compact way, but I'm not completely sure)
by using a clicked signal because it is not re-emitted when the checkbox state changes
Which approach you follow is up to you. The following code uses the third approach:
self.cbPair = {}
self.cbPair['0'] = (QtGui.QCheckBox('all1', parent),
QtGui.QCheckBox('all2', parent))
self.cbPair['1'] = (QtGui.QCheckBox('1a', parent),
QtGui.QCheckBox('1b', parent))
self.cbPair['2'] = (QtGui.QCheckBox('2a', parent),
QtGui.QCheckBox('2b', parent))
for v in self.cbPair.values():
for cb in v:
cb.clicked.connect(self.updateCB)
def updateCB(self):
cb = self.sender()
is_checked = cb.isChecked()
id = str(cb.text())
try:
# Update a whole column
column = int(id[-1]) - 1
rows = ('1', '2')
except ValueError:
# Update a row and the headers row
rows = (id[0], )
column = {'a': 1, 'b': 0}.get(id[-1])
if not is_checked:
for c in (0, 1):
self.cbPair['0'][c].setChecked(is_checked)
for r in rows:
self.cbPair[r][column].setChecked(is_checked)
Note that I'm using the checkboxes text as a UID from wich row and colum values are calculated. If you want to use different text labels for your checkboxes you may need to set the UIDs as attributes to every checkbox.

How to add event listener to dynamic QTableWidgetItem in PyQt4?

I am a Python and Qt newbie...
I am working on a GUI program that shows a list of data using QTableWidget at the beginning.
The rows are inserted into the table using the setItem() method. They are QTableWidgetItem objects.
Now I want to allow users to click-select a certain row (the QTableWidgetItem), and my program will populate a secondary QTableWidget.
I have learnt that there is a thing called Signal and Slot. Am I going to use that?
There are also examples of using installEventFilter(), but it is not appropiate for the QTableWidgetItem.
The easiest way for this would just be to use the itemClicked-signal of the QTableWidget:
def handle_item_clicked(tableItem):
#the callback, called when an item is clicked, with the item as an argument
#populate the secondary table in some way
#register the callback with the QTableWidget
yourTableWidget.itemClicked.connect(handle_item_clicked)
You could also use the currentItemChanged signal which gives you the current and previous selection (e.g. so you can clear or hide your secondary widget if the user deselects an item, in which case the current item will be None).
Qt doesn't have "event listeners". As you mentioned, you do such things by connecting signals to slots.
For example:
QObject.connect(myTableWidget, QtCore.SIGNAL("clicked()"), self.foobar)
This will cause the method self.foobar() to be called when you click on myTableWidget. The foobar function could retrieve which item is selected and populate the second widget based on that.
Take note that in the last argument of connect, you don't use the parentheses (foobar()), just the method name!

Categories

Resources