When you have a QListView and you click on an item in view, the default behaviour is to cancel selection of (deselect) any existing selected item(s), and then set the item clicked to selected and also to "current item/index".
How might I change things so that clicking like this has no effect on selection, but does set the clicked item to "current item/index"?
NB experimentation shows that the view's selectionChanged slot is called before the view's clicked signal fires. One workaround could therefore be to record the deselected items (available in the selectionChanged slot) and apply selection again to these items on detecting that an (instant) click has been fired. But this would be clunky: is a more elegant way available?
Thanks to musicamante's comment I was able to devise what I wanted:
def mousePressEvent(self, event):
if self.selectionModel() and event.modifiers() == QtCore.Qt.NoModifier:
pos = event.pos()
index = self.indexAt(pos)
if index.isValid():
self.selectionModel().setCurrentIndex(index, QtCore.QItemSelectionModel.Current)
return
super().mousePressEvent(event)
... allowing Ctrl-click, Shift-click, etc. to behave as normal, but tweaking the normal behaviour for unmodified click.
Related
I'm making a user-interface in PYQT 5. It includes a QGroupBox containing several QRadioButton. When there is a toggled event I check the buttons and do some stuff. The problem is that there are two toggled events because one button is toggled on and one off so my code is always running twice.
I have been looking for an event from the QGroupBox instead. That should only happen once instead of twice when i toggle a radiobutton.
def __init__(self):
self.radioButton1.toggled.connect(self.update_stimulus)
self.radioButton2.toggled.connect(self.update_stimulus)
self.radioButton3.toggled.connect(self.update_stimulus)
def update_stimulus(self):
if self.radioButton1.isChecked():
print('1')
if self.radioButton2.isChecked():
print('2')
if self.radioButton3.isChecked():
print('3')
# Do stuff based on button positions
I tried using
self.groupBox.toggled.connect(self.update_stimulus)
But that only works if the groupbox itself is toggled. Is there any way to get a signal from the groupbox when one of the radiobuttons changes or do I have to find some way of doing it with the indivdual signals from the radiobuttons?
As #ekhumoro explains, you can add the checked parameter to the function and do your processing only if it's True:
def update_stimulus(self, checked):
if not checked:
return
if self.radioButton1.isChecked():
print('1')
if self.radioButton2.isChecked():
print('2')
if self.radioButton3.isChecked():
print('3')
Keep in mind, though, that if you want to have different radio button "groups" within the same groupbox, none of this will work properly as they will all be considered as part of the same single group: all Qt buttons (widgets that inherit QAbstractButton: QPushButton, QToolButton, QCheckBox and QRadioButton) have an autoExclusive property which is off by default except from radio buttons. This property makes all button that belong to the same parent widget automatically exclusive.
If you need different groups within the same parent, the solution is to use a QButtonGroup, which extends the exclusive functionality by limiting the membership of each button to a specific group.
def __init__(self):
# ...
self.buttonGroupA = QtWidgets.QButtonGroup()
self.buttonGroupA.addButton(self.radioButton1)
self.buttonGroupA.addButton(self.radioButton2)
self.buttonGroupA.addButton(self.radioButton3)
self.buttonGroupA.buttonToggled[QtWidgets.QAbstractButton, bool].connect(self.update_stimulusA)
self.buttonGroupB = QtWidgets.QButtonGroup()
self.buttonGroupB.addButton(self.radioButton4)
self.buttonGroupB.addButton(self.radioButton5)
self.buttonGroupB.addButton(self.radioButton6)
self.buttonGroupB.buttonToggled[QtWidgets.QAbstractButton, bool].connect(self.update_stimulusB)
def update_stimulusA(self, btn, checked):
if not checked:
return
# do something with group A
def update_stimulusB(self, btn, checked):
if not checked:
return
# do something with group B
Creation of a button group is also possible from Designer: just select at least two buttons that will be members of the same group, right click on one of them, go to the "Assign to button group" sub menu and select "New button group". To add a button to an existing group, just use the same context menu and choose the group you want to add that button to.
I have been looking for a treeView widget option where I can select only a single row at a time by clicking it and then de-select it by clicking on it again. I found how to make it so that you can select only one row at a time, but I was unable to find an option that allowed u to click the selected row to de-select it.
Does anyone know how this can be done? Any help would be appreciated.
You can set a custom binding to deselect the item if it's currently selected. If your binding returns the string break, it will stop event propagation and thus prevent the default behavior for double-click.
...
self.tree = ttk.Treeview(...)
self.tree.bind("<1>", self.on_click)
...
def on_click(self, event):
selection = self.tree.selection()
item = self.tree.select_row(event.y)
if item in selection:
self.tree.selection_remove(item)
return "break"
Unfortunately, the only built-in pattern to toggle the selection state of a treeview item is by selecting a different item but you can do a hack of your own.
I'm trying to schedule a mouse press event for a QComboBox. I was wondering if there is any way to schedule a mouse press event on the initial QComboBox click -- the click that brings up the list of items to select. I've already used the currentIndexChanged(int) signal to call a function once the user selects one of the items from the drop down menu, but I'm trying to refresh my QComboBox list with new entries once the user clicks on it. (I have a feeling that this approach may be misguided, but I guess that's another question.)
I've tried making a QComboBox subclass with def mousePressEvent(self, e), but it doesn't seem to do anything. I've also tried def mousePressEvent(self, e) in the QtGui.QWidget class that holds my QComboClass object but, unsurprisingly, that only captures mouse presses for the QtGui.QWidget.
Your current approach is misguided. Even if you could get it working, it would fail whenever the list was opened via the keyboard.
The correct way to do this is to override showPopup:
class ComboBox(QtGui.QComboBox):
def showPopup(self):
self.insertItem(0, 'Added')
super(ComboBox, self).showPopup()
I have dialog:
It contains many flat QPushButtons, QTextEdit and another QPushButton. After click on 'Get list' we can see list of checked buttons in QTextEdit.
My question is how to get this functionality in some smart way. Right now I'm checking every button:
if self.ui.bq6o.isChecked():
cards.append("Q6o")
if self.ui.bk2o.isChecked():
cards.append("K2o")
if self.ui.bq3o.isChecked():
cards.append("Q3o")
if self.ui.bt7s.isChecked():
cards.append("T7s")
if self.ui.bq4o.isChecked():
cards.append("Q4o")
if self.ui.bt4s.isChecked():
cards.append("T4s")
if self.ui.b98o.isChecked():
cards.append("98o")
if self.ui.bjto.isChecked():
cards.append("JTo")
if self.ui.btt.isChecked():
cards.append("TT")
if self.ui.bq7o.isChecked():
cards.append("Q7o")
[...]
Obviously I can't like code like that. I was looking for some widget "button matrix" like, but without luck. I will be grateful for advises.
All the buttons should be children of the same widget, probably the dialog itself. Just get a handle to that widget to get all the child buttons, then loop through them and if they're checked, included their text.
parent = dialog # or whatever
cards = [widget.text() for widget in parent.children() if isinstance(widget, QPushButton) and widget.isChecked()]
You may need to include some code in the if statement to exclude the "Get List" button, or any other pushbuttons in your dialog that could be set to "checked" but shouldn't be included in cards list.
As #Brendan suggested in the other question, you could loop through them in a single list comprehension. But one other approach is to connect each buttons toggled signal to a slot that allows them to register when they are checked.
# somewhere in your class
self.checkedList = set()
def buttonChecked(self, checked):
button = self.sender()
if checked:
self.checkedList.add(button)
else:
if button in self.checkedList:
self.checkedList.remove(button)
# when you create a new button
button.toggled.connect(self.buttonChecked)
This would let you always have a set of just the checked buttons, which are self reporting. Otherwise, you would have to track them under their parent and loop to find out which are checked each time.
Update
Here is a another version that combines #Brendans loop and my signal suggestion. This might help in a situation where your buttons are a bit more spread out across your UI as opposed to be all under a single parent... but first assuming them all under a parent...
parent = dialog
for widget in parent.children():
if isinstance(widget, QPushButton):
widget.toggled.connect(self.buttonChecked)
You could repeat this in your __init__() for all the locations of your buttons and get them all registered to the slot.
I've got a QComboBox with a custom list object.
The custom list object has a custom mousePressEvent so that when the user click on one of the circles with a +/- (a twisty), the list is expanded/collapsed.
When I use the list with the combo box, when the user clicks on a twisty, the list is expanded/collapsed, but the selection is changed, and the list is hidden. How can I filter this so that when the user click on a twisty, the selection is not changed, and the list not hidden.
Additional screenshots
All of the nodes collapsed:
List hidden:
QT has a eventFilter that "captures" QEvent.MouseButtonRelease. So what I have done is installed my own eventFilter that filters QEvent.MouseButtonRelease events if the user click on a node.
In my list object I have the following method:
def mousePressEvent (self, e):
self.colapse_expand_click = False
if <user clicked node>:
colapse_expand_node()
e.accept ()
self.colapse_expand_click = True
The mousePressEvent runs before mouseReleaseEvent.
Then in the custom combobox, I filter the event:
class RevisionSelectorWidget(QtGui.QComboBox):
def __init__(self, parent = None):
QtGui.QComboBox.__init__(self, parent)
self.log_list = RevisionSelectorLogList(self)
self.setView(self.log_list)
self.log_list.installEventFilter(self)
self.log_list.viewport().installEventFilter(self)
def eventFilter(self, object, event):
if event.type() == QtCore.QEvent.MouseButtonRelease:
if self.log_list.colapse_expand_click:
return True
return False
Off the top of my head, you could subclass QComboBox and override hideEvent(QHideEvent) (inherited from QWidget)
def hideEvent(self, event):
if self.OkToHide():
event.accept()
else:
event.ignore()
Your screenshot looks like an interesting use of a combo box, I'm curious as to why you haven't used a TreeView style control instead of a list?
Edit (Mar 14 2009):
I looked at the Qt source code and it looks like when the keyboard and mouse events are captured, that as soon as qt has decided to emit the "activated(int index)" signal, "hidePopup()" has been called.
So apart from rewriting their event filter code, another option is to connect the "activated(int index)" or "highlighted(int index)" signal to a slot that can call "showPopup()" which would re-raise the list items. If you get a nasty disappear/appear paint issue you may have to get Qt to delay the paint events while the popup is visible.
Hope that helps!