How to hide QComboBox items instead of clearing them out - python

I can't find a way to hide QComboBox items. So far the only way to filter its items out is to delete the existing ones (with .clear() method). And then to rebuild the entire QComboBox again using its .addItem() method.
I would rather temporary hide the items. And when they are needed to unhide them back.
Is hide/unhide on QCombobox items could be accomplished?

In case someone still looking for an answer:
By default, QComboBox uses QListView to display the popup list and QListView has the setRowHidden() method:
qobject_cast<QListView *>(comboBox->view())->setRowHidden(0, true);
Edit: fix code according to #Tobias Leupold's comment.
Edit: Python version:
# hide row
view = comboBox.view()
view.setRowHidden(row, True)
# disable item
model = comboBox.model()
item = model.item(row)
item.setFlags(item.flags() & ~Qt.ItemIsEnabled)
# enable item
view.setRowHidden(row, false)
item.setFlags(item.flags() | Qt.ItemIsEnabled)

To build on what #kef answered:
(excuse the C++ on the python question)
By default the QComboBox will use a QListView for the view, thus you can do the following:
QListView* view = qobject_cast<QListView *>(combo->view());
Q_ASSERT(view != nullptr);
view->setRowHidden(row, true);
The one drawback with the above is, that even though the item will be hidden from the popup, the user can still select it using the mouse wheel. To overcome this add the following for the hidden row:
QStandardItemModel* model = qobject_cast<QStandardItemModel*>(combo->model());
Q_ASSERT(model != nullptr);
QStandardItem* item = model->item(row);
item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
With the above the row will be hidden and the user will not be able to scroll to it with the mouse wheel.
To unhide it, just do the reverse:
view->setRowHidden(row, false);
item->setFlags(item->flags() | Qt::ItemIsEnabled);

You can use the removeItem() method to remove an item from the QComboBox.
void QComboBox::removeItem ( int index )
Removes the item at the given index from the combobox. This will update the current index if the index is removed.
This function does nothing if index is out of range.
If you don't know the index, use the findText() method.
There are no hide/unhide methods for QComboBox items.

Althought there is no direct way to hide the item of the QComboBox, but you can use QComboBox::setItemData and set the size to (0,0) to hide the item of QComboBox:
comboBox->setItemData(row, QSize(0,0), Qt::SizeHintRole);

Use the setVisible() to alter the visibility of your object:
.setVisible(False) # Not Visible
.setVisible(True) # Visible

To show the item again:
comboBox->setItemData(row, QVariant(), Qt::SizeHintRole);
Note: changing the SizeHintRole doesn't work on OS X.

I came across this thread after getting frustrated with a lack of hide functionality that would keep the item indexing etc.
I got it to work based on #Kef and #CJCombrink answers. This is basically just a python translation.
I had a problem with qobject_cast.
Solved it by setting .setView(QListView()) to the QComboBox.
combo=QComboBox()
combo.setView(QListView())
hide:
combo.view().setRowHidden(rowindex,True)
tmp_item=combo.model().item(rowindex)
tmp_item.setFlags(tmp_item.flags() & ~Qt.ItemIsEnabled)
unhide:
combo.view().setRowHidden(rowindex,False)
tmp_item=combo.model().item(rowindex)
tmp_item.setFlags(tmp_item.flags() | Qt.ItemIsEnabled)
I decided to subclass the QComboBox and add the hide functionality. Bellow is an use example for testing.
You are welcome to use it. I make no assurances.
import sys
from PySide2.QtWidgets import *
from PySide2.QtCore import *
from PySide2.QtGui import *
#subclassed QComboBox with added hide row functionality
class ComboBox_whide(QComboBox):
def __init__(self):
super().__init__()
self.setView(QListView())#default self.view() is a QAbstractItemView object which is missing setRowHidden, therefore a QListView needs to be set
def hide_row_set(self,row,value=True):
"""sets the row accesibility
value=True hides the row"""
self.view().setRowHidden(row,value)#hides the item from dropdown, however the item is stil accesible by moving down with arrow keys or mouse wheel. The following disables solves that
tmp_item=self.model().item(row)
if value:#hide -> disable
tmp_item.setFlags(tmp_item.flags() & ~Qt.ItemIsEnabled)
else:#enable
tmp_item.setFlags(tmp_item.flags() | Qt.ItemIsEnabled)
def hide_row_toggle(self,row):
"""toggles the row accesibility"""
if self.view().isRowHidden(row):#is hidden, therefore make available
self.hide_row_set(row,False)
else:#is not hidden, therefore hide
self.hide_row_set(row,True)
class Main(QMainWindow):
def __init__(self):
super().__init__()
cwidg=QWidget()
clayer=QVBoxLayout()
cwidg.setLayout(clayer)
self.setCentralWidget(cwidg)
#button for testing
self.btn=QPushButton('Button')
self.btn.setCheckable(True)
clayer.addWidget(self.btn)
#subclassed QComboBox
self.combo=ComboBox_whide()
for n in range(3):#add 3 items with tooltips
self.combo.addItem('item%i'%n)
self.combo.setItemData(n,'tip%i'%n,Qt.ToolTipRole)
clayer.addWidget(self.combo)
#button test function - choose either or for testing
self.btn.clicked.connect(self.btn_clicked)
#uncomment for add/remove example self.btn.clicked.connect(self.remove_add_item)
def btn_clicked(self):
self.combo.hide_row_toggle(1)
def remove_add_item(self):# here for naive comparison and to show why removing and adding is not ok
if self.combo.count()==3:
self.combo.removeItem(1)
else:
self.combo.addItem('new')#new "item1" withouth the ToolTip
if __name__ == '__main__':
app = QApplication.instance()
if app is None:#Pyside2 ipython notebook check
app = QApplication(sys.argv)
main = Main()
main.show()
app.exec_()

Related

PyQt QRadioButton toggled singal callback execute after delete [duplicate]

There are some examples of removing specific items from a layout, but I can not find anything on simply deleting everything from a frame.
Using pyqt designer, I have created a frame. Then using pyuic4 the file is converted to python. In the main program, some layouts, items, and widgets are dynamically inserted to the frame. However, I dont actually keep track of all the items. On a button refresh, I want to delete everything contained in the frame and populate it again.
My question is, is there a simple way to delete everything that is contained in a frame, including layouts, widgets, and items.
As of now, I can do:
for i in range(len(MyResourceFrame.children())):
MyResourceFrame.children()[i].deleteLater()
However, I have code directly under that, and after the first qframe population, clicking on repopulate give the error that a frame is already there, which then removes all items. The second click on repopulate works. Does this have something to do with "Later" wanting to be out of scope first or is that just a name?
The deleteLater slot will just schedule the object for deletion. That is, the object won't be deleted until control returns to the event loop (which will usually mean after the currently executing function has returned).
If you want to delete an object immediately, use the sip module. This should allow you delete a layout and all its contained widgets like this:
PyQt5:
from PyQt5 import sip
...
class Window(QtWidgets.QMainWindow):
...
def populateFrame(self):
self.deleteLayout(self.frame.layout())
layout = QtWidgets.QVBoxLayout(self.frame)
...
def deleteLayout(self, layout):
if layout is not None:
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.deleteLater()
else:
self.deleteLayout(item.layout())
sip.delete(layout)
PyQt4:
import sip
...
class Window(QtGui.QMainWindow):
...
def populateFrame(self):
self.deleteLayout(self.frame.layout())
layout = QtGui.QVBoxLayout(self.frame)
...
def deleteLayout(self, layout):
if layout is not None:
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.deleteLater()
else:
self.deleteLayout(item.layout())
sip.delete(layout)
The problem was the layout predefined inside your QFrame. If you remove it in QtDesigner your frame will appear correctly with the first click.

How PyQt5 cursor above QListWidget item pops WhatsThis documentation?

How to give a definition for each item in QListWidget, so that the cursor changes to WhatsThis Cursor and pops an doc of explication
when the cursor is over it?
Something like this but for an item of QListWidget
I tried simply:
for i, def in zip(range(self.listWidget.count()), some_doc):
self.listWidget.item(i).setWhatsThis(def)
But the doc didn't show up
That appears to be the correct way to set the WhatsThis. However, you state that you want this to appear on hovering, which is instead the tooltip property.
solution:
for i, def in zip(range(self.listWidget.count()), some_doc):
self.listWidget.item(i).setToolTip(def)
If you want this behavior only in WhatsThis mode, you will have to subclass the QListWidgetItem and set the tooltip to only return when QtWidgets.QWhatsThis.inWhatsThisMode() is true.
If you really are just having problems with the WhatsThis property working on click, we will need to see more of your code and possibly a screenshot of your window. Because like I said, the loop at the beginning should work at just setting the normal property.

Lock a choice in PyQt4 QComboBox using a QCheckBox

I am beginning to write a GUI using PyQt4. This is my first experience with GUIs (and also oo-programming is somewhat new to me). Part of that GUI will be like 4 to 5 instances of QComboBox. As many choices are to be made, I want the user to be able to lock a choice, such that is not being changed unintenionally later. For one QComboBox I can solve the problem with this code that I wrote:
import sys
from PyQt4 import QtGui, QtCore
class MyGui(QtGui.QWidget):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.resize(250, 50)
# vertical layout for widgets
self.vbox = QtGui.QVBoxLayout()
self.setLayout(self.vbox)
# Create a combo box with some choices
self.combo_color = QtGui.QComboBox()
self.vbox.addWidget(self.combo_color)
items = 'Red Yellow Purple'.split()
self.combo_color.addItems(items)
self.connect(self.combo_color, QtCore.SIGNAL('activated(QString)'), self.use_choice)
# add a checkbox next to the combobox which (un-)locks the the combo-choice
self.checkbox_color = QtGui.QCheckBox('Lock Choice', self)
self.vbox.addWidget(self.checkbox_color)
self.connect(self.checkbox_color, QtCore.SIGNAL('stateChanged(int)'), self.lock_choice)
def use_choice(self, text):
# do something very useful with the choice
print 'The current choice is: {choice}'.format(choice=text)
def lock_choice(self):
if self.checkbox_color.isChecked():
self.combo_color.setEnabled(False)
print 'Choice {choice} locked'.format(choice=self.combo_color.currentText())
else:
self.combo_color.setEnabled(True)
print 'Choice unlocked'
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mygui = MyGui()
mygui.show()
app.exec_()
This code does what it should, but I am very unhappy with its design, because the method lock_choice is hard-coded to only lock the choice of the QComboBox combo_color. What if I now want to do the same thing for another QComboBox (say combo_name) and a second QCheckBox (say checkbox_name) which could be realized by appending the following code to the classes __init__(self) code block:
# create second combo box with some other choices
self.combo_name = QtGui.QComboBox()
self.vbox.addWidget(self.combo_name)
items = 'Bob Peter'.split()
self.combo_name.addItems(items)
self.connect(self.combo_name, QtCore.SIGNAL('activated(QString)'), self.use_choice)
# add a checkbox next to the combobox which (un-)locks the the combo-choice
self.checkbox_name = QtGui.QCheckBox('Lock Choice', self)
self.vbox.addWidget(self.checkbox_name)
self.connect(self.checkbox_name, QtCore.SIGNAL('stateChanged(int)'), self.lock_choice) # <-- obviously wrong, as it (un-)locks color choice at the moment
Both QComboBoxes can share the method use_choice() right now, but they cannot share the method lock_choice(), as both checkboxes lock the color-choice. I want the checkbox checkbox_name to lock the name-choice, without copy and pasting the current lock_choice()-method and switching the hardcoded combobox.
I am sure there is an easy way, like passing the target-combobox to the method, which i just don't know yet. Help would be appreciated!
Try partial functions-
from functools import partial
with the connect signal, pass the QWidget name:
self.connect(self.checkbox_color, QtCore.SIGNAL('stateChanged(int)'), partial(self.lock_choice, self.combo_color))
And in your method, you may add an argument.
We'll add an argument in the method like below, to handle the QWidget(QComboBox) that we'll pass through the partial function above.
def lock_choice(self, combos):
if combos.isEnabled(True):
combos.setEnabled(False)
print 'Choice {choice} locked'.format(choice=combos.currentText())
The simplest solution would be to use the toggled signal with the setDisabled slot:
self.checkbox_color.toggled.connect(self.combo_color.setDisabled)
(And note how much cleaner the new-style sytax is when making signal connections).
It's also worth pointing out that you can also use a lambda for making inline signal connections, like this:
self.checkbox_color.toggled.connect(
lambda checked: self.combo_color.setDisabled(checked))
This is probably the most idiomatic solution when there are no convenient signal/slot pairings (and of course partial functions can achieve more or less the same thing in different way).

QComboBox within QTableWidget returns NoneType

In one of my applications I need to have a QComboBox inside a QTableWidget.
I wrote this code:
def on_addGoal_clicked(self, checked=False):
self.ui.listOfGoals.setRowCount(self.ui.listOfGoals.rowCount() + 1)
possible_goals = QtGui.QComboBox()
possible_goals.addItems(["greater_than", "maximize", "minimize" \
, "smaller_than", "between"])
self.ui.listOfGoals.setCellWidget(self.ui.listOfGoals.rowCount() - 1,
1, possible_goals)
and it correctly adds the QComboBox.
However, when I try to retrieve this QComboBox using self.ui.listOfGoals.item(r,1), a None is returned.
I'm still new to PyQt so I might have missed something here. Any suggestions?
Use the cellWidget method to retrieve a widget that was set with setCellWidget:
possible_goals = self.ui.listOfGoals.cellWidget(r, 1)

Can QListWidget be refreshed after an addition?

I'm adding an item to a QListWidget, and although I can get the specifics of the item back from the QListQidget, the new item never appears on the screen. Is it possible to refresh the QListWidget to have it update to display newer contents?
>>>myQListWidget.addItem("Hello")
>>>print self.myQListWidget.item(0).text()
Hello
I'm doing this in Python, but if you have the solution in C++ I can easily convert it.
Thanks!
--Erin
You can update the widget's view by calling update() or repaint(), the second function is asynchronous and forces widget to update immediately. But the QListWidget should update automatically after insertion without calling any extra functions, if not, then the problem could be that Qt can't process the paint events. Then you have to call QCoreApplication::processEvents(), but I am not sure if it is your problem.
Hmm... I'm not seeing that behavior.
import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
a = QtGui.QApplication(sys.argv)
w = QtGui.QListWidget()
w.setWindowTitle('example')
w.show()
w.addItem("test 1") # shows up
w.addItem("test 2") # also shows up
EDIT: Removed chevrons so the code can be copied/pasted
I saw similar behavior as user671110 with PyQt5 (5.9.2). In my case using the suggestion from this qt forum worked.
Using the adapted sample code from jkerian:
import sys
from PyQt5 import QtWidgets

a = QtWidgets.QApplication(sys.argv)

w = QtWidgets.QListWidget()
w.setWindowTitle('example')
w.show()
w.addItem("test 1") # Item 1 does NOT show up
w.repaint() # Item 1 does NOT show up
QtCore.QCoreApplication.processEvents()
 # Item 1 DOES show up

w.addItem("test 2") 
 # Item 2 does NOT show up
QtCore.QCoreApplication.processEvents()
 # Item 2 DOES show up

w.addItem("test 3") 
 # Item 3 does NOT show up
a.exec_() # All items shows up
This is kind of expected as the GUI is refreshed with event processing. Still THe doc mentioned that repaint() could be use to perform an asynchronous refresh but it does not seems to be working, or I use it wrongly.

Categories