QComboBox replacing edit text if case differs from existing item - python

I'm having a problem with QComboBox not allowing me to change the edit
text to anything existing item of differing case.
Example code is below. What I'd like to do is enter 'one' into a combo
box already containing the item 'One' without the side effect of the
text being changed to 'One'. Currently it's changed back to 'One' as
soon as the combo box loses focus.
Disabling AutoCompletionCaseSensitivity works, but it has the side
effect of not being useful (Doesn't eg. show completions for 'one').
I've also tried overriding the focusOutEvent of QComboBox and
restoring the correct text, but then things like copy-paste don't
work. Changing the completer hasn't helped any either.
The fact combo boxes behave this way is detrimental to my app. If
anyone has any ideas (or I missed something obvious), please let me
know.
I'm using Qt 4.6.2 and PyQt 4.7.2 on Ubuntu 10.04, but have
experienced this on other distros/Qt versions above 4.5.
Thanks and Regards
Example Code:
from PyQt4.QtGui import *
from PyQt4.QtCore import SIGNAL, Qt
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
combo = QComboBox()
combo.setEditable(True)
combo.addItems(['One', 'Two', 'Three'])
lineedit = QLineEdit()
layout = QVBoxLayout()
layout.addWidget(combo)
layout.addWidget(lineedit)
self.setLayout(layout)
app = QApplication([])
widget = Widget()
widget.show()
app.exec_()

from PyQt4.QtGui import *
from PyQt4.QtCore import SIGNAL, Qt, QEvent
class MyComboBox(QComboBox):
def __init__(self):
QComboBox.__init__(self)
def event(self, event):
if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Return:
self.addItem(self.currentText())
return QComboBox.event(self, event)
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
combo = MyComboBox()
combo.setEditable(True)
combo.addItems(['One', 'Two', 'Three'])
lineedit = QLineEdit()
layout = QVBoxLayout()
layout.addWidget(combo)
layout.addWidget(lineedit)
self.setLayout(layout)
app = QApplication([])
widget = Widget()
widget.show()
app.exec_()
The only issue with this is that it will allow adding duplicates to your combobox.
I tried adding a self.findText(...) to the if statement but even Qt.MatchExactly | Qt.MatchCaseSensitive
would match "bla", "bLa" and "BLA".
I guess you'll find out.

Related

PyQt5 How to apply different QStyles to different widgets?

I am trying to make multiple widgets with different styles from the built in styles provided by QStyleFactory, but when I run my code they all look the same. How can I fix this?
from PyQt5 import QtWidgets, QtCore, QtGui
import sys
class Demo(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.container = QtWidgets.QWidget()
self.setCentralWidget(self.container)
self.layout = QtWidgets.QVBoxLayout()
self.container.setLayout(self.layout)
self.btn = QtWidgets.QPushButton("button")
self.lw = QtWidgets.QListWidget()
self.lw.addItems(["one", "two", "three"])
self.layout.addWidget(self.btn)
self.layout.addWidget(self.lw)
self.resize(400, 150)
self.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
widgets = []
for style_name in QtWidgets.QStyleFactory.keys():
demo = Demo()
demo.setWindowTitle(style_name)
style = QtWidgets.QStyleFactory.create(style_name)
demo.setStyle(style)
widgets.append(demo)
sys.exit(app.exec_())
An important aspect of setting a QStyle on widgets (instead of setting it on the whole application) is reported in the QWidget.setStyle() documentation:
Setting a widget's style has no effect on existing or future child widgets.
So, what's happening is that you're setting the style on the QMainWindow only, while the children will always use the QApplication style.
What you could try to do is to manually set the style for the existing children:
for style_name in QtWidgets.QStyleFactory.keys():
demo = Demo()
demo.setWindowTitle(style_name)
style = QtWidgets.QStyleFactory.create(style_name)
demo.setStyle(style)
for child in demo.findChildren(QtWidgets.QWidget):
child.setStyle(style)
widgets.append(demo)
In any case, the above approach has a drawback: any new children created after setting the style will still inherit the QApplication style. The only way to avoid this is to watch for childEvent() by (recursively) installing an event filter on the parent, and set the styles accordingly; note that you need to watch for StyleChange events too.
class ChildEventWatcher(QtCore.QObject):
def __init__(self, parentWidget):
super().__init__()
self.parentWidget = parentWidget
self.parentWidget.installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.ChildAdded and isinstance(event.child(), QtWidgets.QWidget):
event.child().installEventFilter(self)
event.child().setStyle(self.parentWidget.style())
for child in event.child().findChildren(QtWidgets.QWidget):
child.installEventFilter(self)
child.setStyle(self.parentWidget.style())
elif event.type() == QtCore.QEvent.StyleChange and source == self.parentWidget:
for child in self.parentWidget.findChildren(QtWidgets.QWidget):
child.setStyle(self.parentWidget.style())
return super().eventFilter(source, event)
class Demo(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
# this *must* be created before adding *any* child
self.childEventWatcher = ChildEventWatcher(self)
# ...
Also remember another important aspect the documentation warns about:
Warning: This function is particularly useful for demonstration purposes, where you want to show Qt's styling capabilities. Real applications should avoid it and use one consistent GUI style instead.
While the above code will do what you're expecting, installing an event filter on all child QWidgets is not a good thing to do, especially if you only need to do the style change (which is something that should normally be done just once, possibly at the start of the program). Considering the warning about using different styles, I highly suggest you to do this exactly as suggested: for demonstration purposes only.
You can set it on the application.
from PyQt5 import QtWidgets, QtCore, QtGui
import sys
class Demo(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.container = QtWidgets.QWidget()
self.setCentralWidget(self.container)
self.layout = QtWidgets.QVBoxLayout()
self.container.setLayout(self.layout)
self.btn = QtWidgets.QPushButton("button")
self.lw = QtWidgets.QListWidget()
self.lw.addItems(["one", "two", "three"])
self.layout.addWidget(self.btn)
self.layout.addWidget(self.lw)
self.resize(400, 150)
self.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
win = Demo()
app.setStyle(QtWidgets.QStyleFactory.create("Windows")) # Set style theme on app
sys.exit(app.exec_())

Notification when QDockWidget's tab is clicked?

I need to execute a block of code when the user clicks on the tab of a tabbified QDockWidget. So far I've been doing this via a hack using the "visibilityChanged" event but this is now causing issues (for example, if I have several tabbified dock widgets and I drag one out so that it is floating, the tabbified one underneath will fire its "visibilityChanged" event which I will mistakenly interpret as the user clicking the tab). How can I receive proper notification when a user clicks on a QDockWidgets' tab? I've experimented with the "focusInEvent" of QDockWidget but it doesn't seem to fire when the tab is clicked.
When you use tabifyDockWidget() method QMainWindow creates a QTabBar, this is not directly accessible but using findChild() you can get it, and then use the tabBarClicked signal
from PyQt4 import QtCore, QtGui
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
first_dock = None
for i in range(10):
dock = QtGui.QDockWidget("title {}".format(i), self)
dock.setWidget(QtGui.QTextEdit()) # testing
self.addDockWidget(QtCore.Qt.TopDockWidgetArea, dock)
if first_dock:
self.tabifyDockWidget(first_dock, dock)
else:
first_dock = dock
dock.raise_()
tabbar = self.findChild(QtGui.QTabBar, "")
tabbar.tabBarClicked.connect(self.onTabBarClicked)
def onTabBarClicked(self, index):
tabbar = self.sender()
text = tabbar.tabText(index)
print("index={}, text={}".format(index, text))
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

PyQt4 signals and slots - QToolButton

In PyQt4 I have a main window which when the settings button is clicked opens the settings dialog
from PyQt4 import QtCore, QtGui
import ui_Design, ui_Settings_Design
class MainDialog(QtGui.QMainWindow, ui_Design.Ui_arbCrunchUI):
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
self.setupUi(self)
self.settingsBtn.clicked.connect(lambda: self.showSettings())
def showSettings(self):
dialog = QtGui.QDialog()
dialog.ui = SettingsDialog()
dialog.ui.setupUi(dialog)
dialog.exec_()
The above works and my SettingsDialog is displayed but I cant get the setPageIndex to work
class SettingsDialog(QtGui.QDialog, ui_Settings_Design.Ui_SettingsDialog):
def __init__(self, parent=None):
super(SettingsDialog, self).__init__(parent)
self.setupUi(self)
self.bookSettingsBtn.clicked.connect(self.setPageIndex)
#QtCore.pyqtSlot()
def setPageIndex(self):
print 'selected'
self.settingsStackedWidget.setCurrentIndex(0)
The bookSettingsBtn is a QToolButton
self.bookSettingsBtn = QtGui.QToolButton(self.navigationFrame)
And the settingsStackedWidget is a QStackedWidget
self.settingsStackedWidget = QtGui.QStackedWidget(SettingsDialog)
At this point I am pretty confused on signals and slots and nothing I have read clears this up - if anyone can point out what I am doing wrong above and also potentially direct me to a good (beginners) guide / tutorial on signals and slots it would be greatly appreciated
I would also like to know how to make setPageIndex work as follows:
def setPageIndex(self, selection):
self.settingsStackedWidget.setCurrentIndex(selection)
I'm not sure why you're doing the following, but that's the issue:
def showSettings(self):
dialog = QtGui.QDialog()
dialog.ui = SettingsDialog()
dialog.ui.setupUi(dialog)
dialog.exec_()
SettingsDialog itself is a proper QDialog. You don't need to instantiate another QDialog.
Right now, you're creating an empty QDialog and then populate it with the same ui as SettingsDialog (i.e. setupUi(dialog)), then you show this dialog. But... The signal connection is for SettingsDialog, and the dialog you're showing doesn't have that.
Basically, you don't need that extra QDialog at all. The following should be enough:
def showSettings(self):
dialog = SettingsDialog()
dialog.exec_()
Ok. So here is an example how you pass an argument to a slot
from functools import partial
# here you have a button bookSettingsBtn:
self.bookSettingsBtn = QtGui.QPushButton("settings")
self.bookSettingsBtn.clicked.connect(partial(self.setPageIndex, self.bookSettingsBtn.text()))
#pyqtSlot(str) # this means the function expects 1 string parameter (str, str) 2 string parameters etc.
def setPageIndex(self, selection):
print "you pressed button " + selection
In your case the argument would be an int. Of course you have to get the value from somewhere
and then put it in the partial part as the argument (here I just used the text of the button),
but you can use int, bool etc. Just watch the slot signature.
Here is a tutorial that helped me:
http://zetcode.com/gui/pyqt4/
I hope this helps.
Hey here I have a fully running example (just copy paste it in a python file and run it):
Maybe this helps you. It's a small example but here you see how it works.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from functools import partial
class MyForm(QMainWindow):
def __init__(self, parent=None):
super(MyForm, self).__init__(parent)
button1 = QPushButton('Button 1')
button2 = QPushButton('Button 2')
button1.clicked.connect(partial(self.on_button, button1.text()))
button2.clicked.connect(partial(self.on_button, button1.text()))
layout = QHBoxLayout()
layout.addWidget(button1)
layout.addWidget(button2)
main_frame = QWidget()
main_frame.setLayout(layout)
self.setCentralWidget(main_frame)
#pyqtSlot(str)
def on_button(self, n):
print "Text of button is: " + str(n)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
form = MyForm()
form.show()
app.exec_()
So I dont really understand why but changing the way the settingsDialog is called from the MainWindow has fixed my problem. I guess the parent window needed to be specified??:
class MainDialog(QtGui.QMainWindow, ui_Design.Ui_arbCrunchUI):
....
def showSettings(self):
self.settingsDialog = QtGui.QDialog(self)
self.settingsDialog.ui = SettingsDialog(self)
self.settingsDialog.ui.show()
class SettingsDialog(QtGui.QDialog, ui_Settings_Design.Ui_SettingsDialog):
def __init__(self, parent=None):
super(SettingsDialog, self).__init__(parent)
self.setupUi(self)
self.bookSettingsBtn.clicked.connect(partial(self.setPageIndex, 1))
#QtCore.pyqtSlot(int)
def setPageIndex(self, selection):
self.settingsStackedWidget.setCurrentIndex(selection)

How can I create a read-only combobox in Pyqt?

I have a comboBox with items and I just want to display them without being able to select any of them.I searched in Qt Designer but I can't find the right property. Any ideas ?
You cannot do it in the QtDesigner you have to connect the currentIndexChanged signal with a function that will revert the old value whatever the user selects:
Example:
import sys
from PyQt4 import QtGui, QtCore
class MainWidget(QtGui.QWidget):
def __init__(self):
super(MainWidget, self).__init__()
# Create a combo and set the second item to be selected
self.combo = QtGui.QComboBox()
self.combo.addItems(['foo', 'bar', 'baz'])
self.combo.setCurrentIndex(1)
# Connect the combo currentIndexChanged signal
self.combo.activated.connect(self.on_combo_change)
# Setup layout
self.layout = QtGui.QVBoxLayout()
self.layout.addWidget(self.combo)
self.setLayout(self.layout)
def on_combo_change(self, index):
# Whatever the user do, just ignore it and revert to
# the old value.
self.combo.setCurrentIndex(1)
app = QtGui.QApplication(sys.argv)
mw = MainWidget()
mw.show()
app.exec_()
QComboBox.setEditable(False) should do it: http://pyqt.sourceforge.net/Docs/PyQt4/qcombobox.html#setEditable

change text of lineEdit when a radio button is selected in pyqt

I have two radioButtons in the form made using qt designer, i am now programming in pyqt. i wish to change the text of lineEdit to "radio 1" when radioButton 1 is selected and "radio 2" when the radioButton 2 is selected. how can I achieve this?
Here's a simple example. Each QRadioButton is connected to it's own function. You could connect them both to the same function and manage what happens through that, but I thought best to demonstrate how the signals and slots work.
For more info, take a look at the PyQt4 documentation for new style signals and slots. If connecting multiple signals to the same slot it's sometimes useful to use the .sender() method of a QObject, although in the case of QRadioButton it's probably easier to just check the .isChecked() method of your desired buttons.
import sys
from PyQt4.QtGui import QApplication, QWidget, QVBoxLayout, \
QLineEdit, QRadioButton
class Widget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.widget_layout = QVBoxLayout()
self.radio1 = QRadioButton('Radio 1')
self.radio2 = QRadioButton('Radio 2')
self.line_edit = QLineEdit()
self.radio1.toggled.connect(self.radio1_clicked)
self.radio2.toggled.connect(self.radio2_clicked)
self.widget_layout.addWidget(self.radio1)
self.widget_layout.addWidget(self.radio2)
self.widget_layout.addWidget(self.line_edit)
self.setLayout(self.widget_layout)
def radio1_clicked(self, enabled):
if enabled:
self.line_edit.setText('Radio 1')
def radio2_clicked(self, enabled):
if enabled:
self.line_edit.setText('Radio 2')
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = Widget()
widget.show()
sys.exit(app.exec_())

Categories