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_())
Related
So Basically I Am newbie In PyQt5, I found that the PyQt5 support stylesheets!
So I just thought Of The implementing that drop a list when i user hover on QCombobox.
Any Answer Or Suggestion Is Accepted!
Can it be?
import sys
from PyQt5.QtWidgets import (QWidget, QHBoxLayout,
QComboBox, QApplication)
from PyQt5.QtGui import QIcon
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
hbox = QHBoxLayout()
combo = QComboBox(self)
combo.addItem('Disk')
combo.setItemIcon(0, QIcon('disk.png'))
combo.addItem('Web')
combo.setItemIcon(1, QIcon('web.png'))
combo.addItem('Computer')
combo.setItemIcon(2, QIcon('computer.png'))
hbox.addWidget(combo)
hbox.setSpacing(20)
self.setContentsMargins(20, 20, 20, 20)
self.setLayout(hbox)
self.setGeometry(300, 300, 250, 180)
self.setWindowTitle('QComboBox')
self.show()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Stylesheets in Qt do not allow programmatical access to widget functions (with some exceptions for widget properties, which must be used with care, though).
So, if you thought you could use stylesheets to show the popup on hover, you can't.
In order to do that, you must subclass the QComboBox and reimplement its enterEvent(), otherwise install an event filter. In both cases, you need to call showPopup():
class Example(QWidget):
def initUI(self):
# ...
combo.installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QEvent.Enter:
source.showPopup()
return super().eventFilter(source, event)
Note that if you want to filter other widgets and different event types, it's better to create a reference for the object(s) and check that the source of the event is actually what you need:
class Example(QWidget):
def initUI(self):
# ...
self.combo = QComboBox()
self.combo.installEventFilter(self)
# ...
def eventFilter(self, source, event):
if source == self.combo and event.type() == QEvent.Enter:
source.showPopup()
return super().eventFilter(source, event)
About the customization with stylesheets, see the examples on customizing QComboBox, but I also warmly suggest you to study both the stylesheet syntax and reference documentation.
Also note that you should always ask one question per post, if you have more different questions, create a post for each of them.
I have QDialog that will extract information from it. I added setAttribute(QtCore.Qt.WA_DeleteOnClose) to make sure to clean up the GUI, but QDialog and its attributes being deleted before extracting them. Error: wrapped C/C++ object of type QLineEdit has been deleted.
How can I maintain QDialog, until I get information I need, then destroy on Accep or reject (like deletelater())?
If the solution is to set setAttribute(QtCore.Qt.WA_DeleteOnClose)==False, How can I effectively delete the QDialog after accept or reject, to make GUI is not increasing in size?
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Dialog(QtWidgets.QDialog):
def __init__(self, name, parent=None):
super(Dialog, self).__init__(parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
#
self.title = QtWidgets.QLabel('name')
self.val = QtWidgets.QLineEdit()
self.val.setText("{} is my infor".format(name))
hbox = QtWidgets.QHBoxLayout()
hbox.addWidget(self.title)
hbox.addWidget(self.val)
#
QBtn = QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
self.buttonBox = QtWidgets.QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
#
self.layout = QtWidgets.QVBoxLayout()
self.layout.addLayout(hbox)
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
def main():
app = QtWidgets.QApplication(sys.argv)
ex = Dialog('text')
if ex.exec_() == QtWidgets.QDialog.Accepted:
myinformation = ex.val.text()
print(myinformation)
if __name__ == '__main__':
main()
If you want to get information from the dialog after closing it then you should not use self.setAttribute(QtCore.Qt.WA_DeleteOnClose) since at the moment you want to get the information the dialog will be destroyed.
On the other hand it is not necessary to use deleteLater() since "ex" is a local variable that will be destroyed so there is no memory leak.
I have communications working between similar widgets from imported child widgets and the parent main window and widgets. However, I am stumped when it comes to a QGraphicsScene widget imported as a module and sub-widget. I have put some simplified files below. So, QGraphicsView (from the QGraphicsScene) will be the actual widget I need to emit and signal events to other QWidgets inside the main window.
If I have all classes in one file, it works but if I have the classes as separate modules, I get "does not have attribute" errors, specifically in the simple version here for QGraphicsScene .viewport
Attribute Error "self.graphicsView.viewport().installEventFilter(self)"
I guess that the composite graphics widget is actually now a QWidget and I am not initialising the imported module functions/attributes for the QGraphicsView element. Thing is, I want it to exist that way so I can separate the GUI elements and functions of different modules. The others I have used so far have been straightforward QWidget to QWidget signals derived from QObjects, so work fine, but I haven't been able to achieve the same with the imported QGraphicsScene to QWidgets since it errors when it tries to reach QGraphicsView within the main window. Again, all fine if all classes exist in one large file.
Any kind person can point out my error here? How can I separate the module scripts to behave the same way as the single script?
Working single script:
# QWidgetAll.py
from PySide import QtGui, QtCore
class GraphicsView(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.graphicsView = QtGui.QGraphicsView(self)
self.graphicsLabel = QtGui.QLabel("Graphics View within QWidget")
self.graphicsView.setMouseTracking(True)
self.graphicsView.viewport().installEventFilter(self)
self.edit = QtGui.QLineEdit(self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.graphicsLabel)
layout.addWidget(self.edit)
layout.addWidget(self.graphicsView)
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.MouseMove and
source is self.graphicsView.viewport()):
pos = event.pos()
self.edit.setText('x: %d, y: %d' % (pos.x(), pos.y()))
return QtGui.QWidget.eventFilter(self, source, event)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = GraphicsView()
window.show()
window.resize(200, 100)
sys.exit(app.exec_())
The same file as separate modules. qWidgetView.py errors with attribute error:
# qWidgetView.py
from PySide import QtGui, QtCore
from qGraphicView import GraphicsView
class WidgetView(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.graphicsView = GraphicsView()
self.graphicsView.setMouseTracking(True)
self.graphicsView.viewport().installEventFilter(self)
self.edit = QtGui.QLineEdit(self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.edit)
layout.addWidget(self.graphicsView)
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.MouseMove and
source is self.graphicsView.viewport()):
pos = event.pos()
self.edit.setText('x: %d, y: %d' % (pos.x(), pos.y()))
return QtGui.QWidget.eventFilter(self, source, event)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = WidgetView()
window.show()
window.resize(200, 100)
sys.exit(app.exec_())
with imported qGraphicView.py module:
# qGraphicView.py
from PySide import QtGui, QtCore
class GraphicsView(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.graphicsView = QtGui.QGraphicsView(self)
self.graphicsLabel = QtGui.QLabel("Graphics View within QWidget")
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.graphicsLabel)
layout.addWidget(self.graphicsView)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = GraphicsView()
window.show()
window.resize(200, 100)
sys.exit(app.exec_())
You need to filter events for the QGraphicsView that is a child widget of the GraphicsView class, because you only want mouse-moves on the graphics-view itself, not the whole container widget. So I would suggest something like this:
The qGraphicView.py module:
class GraphicsView(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.graphicsView = QtGui.QGraphicsView(self)
self.graphicsView.setMouseTracking(True)
self.graphicsLabel = QtGui.QLabel("Graphics View within QWidget")
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.graphicsLabel)
layout.addWidget(self.graphicsView)
def viewport(self):
return self.graphicsView.viewport()
The qWidgetView.py module:
class WidgetView(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.graphicsView = GraphicsView()
self.graphicsView.viewport().installEventFilter(self)
self.edit = QtGui.QLineEdit(self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.edit)
layout.addWidget(self.graphicsView)
I'm trying to remove a QGraphicsItem from a QGraphicsItemGroup. When calling removeFromGroup, the item is removed (of course). However, it's then no longer visible in the Scene. I have to call Scene.addItem(item) in order for it to appear again. This is apparently something you shouldn't do (I'm given a warning for doing that). But I can't seem to find another workaround.
Here's a minimal example:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.view = QGraphicsView()
self.scene = QGraphicsScene()
self.view.setScene(self.scene)
self.setCentralWidget(self.view)
def add_group(scene):
group = QGraphicsItemGroup()
text = QGraphicsTextItem()
text.setPlainText("I'm visible")
group.addToGroup(text)
scene.addItem(group)
# After this, text is no longer in group. However, it is no longer visible.
group.removeFromGroup(text)
assert not text in group.childItems()
# But text is still in scene.
assert text.scene() == scene
# this works (i.e. text becomes visible again). However, it also produces a
# warning: QGraphicsScene::addItem: item has already been added to this scene.
# The docs also advice against it.
scene.addItem(text)
# According to the docs, I thought this might work, but it gives me a TypeError.
# text.setParentItem(0)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
add_group(main.scene)
main.show()
sys.exit(app.exec_())
Tips and hints are very welcome.
The QGraphicsTextItem can never be parented to a scene, because it's parent must be a QGraphicsItem (which QGraphicsScene does not inherit).
When the QGraphicsTextItem is created, it's parent is None. Its parent is set to group when it is added to it (QGraphicsItemGroup is a subclass of QGraphicsItem), then set back to None when it's removed from group.
Calling scene.addItem() is actually a NO-OP. Qt checks whether scene is the same as text.scene(), and if it is, it prints the warning and returns without doing anything.
The fact that it seems to "work" in some circumstances, is just an artifact of python's garbage-collecting mechanism.
If your test is re-cast in a more realistic way, the QGraphicsTextItem remains visible after removal from the group:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.view = QGraphicsView(self)
self.scene = QGraphicsScene(self.view)
self.view.setScene(self.scene)
self.setCentralWidget(self.view)
self.group = QGraphicsItemGroup()
self.text = QGraphicsTextItem()
self.text.setPlainText("I'm visible")
self.group.addToGroup(self.text)
self.scene.addItem(self.group)
self.group.removeFromGroup(self.text)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
The problem is that text is deleted since you don't have any reference to it after you remove it from the group, try this:
...
text = QGraphicsTextItem()
scene.text = text #just to keep the reference, ideally should be self.text = text
...
Now you don need scene.addItem(text)
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.