I'm having an issue combining PyQt4 scrollbars (on the MainWindow) with embedded scenes - everything works fine until I resize my window, after which my scenes begin to travel with the scrollbar off the page.
Below is the simplified, full code to illustrate this behavior (with two screenshots attached afterward to explicitly showcase this interaction):
import sys, os
from pyface.qt import QtGui, QtCore
os.environ['ETS_TOOLKIT'] = 'qt4'
from traits.api import HasTraits,Instance,on_trait_change
from traitsui.api import View,Item
from mayavi import mlab
from mayavi.core.ui.api import MayaviScene, MlabSceneModel, SceneEditor
class Mayavi_Scene(HasTraits):
scene = Instance(MlabSceneModel, ())
#on_trait_change('scene.activated')
def update_scene(self):
Mayavi_Scene.fig1 = mlab.figure(1, bgcolor=(.5,.5,.5))
self.scene.mlab.clf(figure=Mayavi_Scene.fig1)
testPlot = mlab.test_contour3d()
view = View(Item('scene', editor = SceneEditor(scene_class=MayaviScene),
height=300, width=300, show_label=False),
resizable=True,
)
class P1(QtGui.QWidget):
def __init__(self, parent=None):
super(P1, self).__init__(parent)
layout = QtGui.QGridLayout(self)
layout.setContentsMargins(20,20,20,20)
layout.setSpacing(10)
self.label_edge1 = QtGui.QLabel('')
self.label_edge1.setMargin(5)
self.label_edge1.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Sunken)
layout.addWidget(self.label_edge1, 0, 0, 10, 10)
self.label_edge1.show()
self.label_avgVol = QtGui.QLabel('Test')
self.label_avgVol.setMargin(5)
self.label_avgVol.setFrameStyle(QtGui.QFrame.Box | QtGui.QFrame.Sunken)
self.label_avgVol.setAlignment(QtCore.Qt.AlignCenter | QtCore.Qt.AlignVCenter)
layout.addWidget(self.label_avgVol, 0, 0, 1, 10)
self.label_avgVol.show()
self.viz1 = Mayavi_Scene()
self.ui1 = self.viz1.edit_traits(parent=self, kind='subpanel').control
layout.addWidget(self.ui1, 1, 1, 1, 9)
class P2(QtGui.QWidget):
def __init__(self, parent=None):
super(P2, self).__init__(parent)
layout = QtGui.QGridLayout(self)
layout.setContentsMargins(20,20,20,20)
layout.setSpacing(10)
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setGeometry(50, 50, 500, 500)
tab1 = P1(self)
tab2 = P2(self)
self.tabs = QtGui.QTabWidget(self)
self.tabs.resize(250,150)
self.tabs.addTab(tab1, 'Page 1')
self.tabs.addTab(tab2, 'Page 2')
self.setWindowTitle('SCROLLBAR ERROR EXAMPLE')
self.groupscroll = QtGui.QHBoxLayout()
self.groupscrollbox = QtGui.QGroupBox()
self.MVB = QtGui.QVBoxLayout()
self.MVB.addWidget(self.tabs)
scroll = QtGui.QScrollArea()
widget = QtGui.QWidget(self)
widget.setLayout(QtGui.QHBoxLayout())
widget.layout().addWidget(self.groupscrollbox)
scroll.setWidget(widget)
scroll.setWidgetResizable(True)
self.groupscrollbox.setLayout(self.MVB)
self.groupscroll.addWidget(scroll)
self.setCentralWidget(scroll)
self.show()
if __name__ == '__main__':
app = QtGui.QApplication.instance()
w = MainWindow()
sys.exit(app.exec_())
Before Reframing
After Reframing
Edit: .gif to show effect (notice how object gets covered up by its own frame containing it at the end instead of moving with it; it's like the 3d scene object isn't being notified that everything else around it is changing):
I modified the place where the scroll bars are added.
import sys, os
from pyface.qt import QtGui, QtCore
os.environ['ETS_TOOLKIT'] = 'qt4'
from traits.api import HasTraits,Instance,on_trait_change
from traitsui.api import View,Item
from mayavi import mlab
from mayavi.core.ui.api import MayaviScene, MlabSceneModel, SceneEditor
class Mayavi_Scene(HasTraits):
scene = Instance(MlabSceneModel, ())
#on_trait_change('scene.activated')
def update_scene(self):
Mayavi_Scene.fig1 = mlab.figure(1, bgcolor=(.5,.5,.5))
self.scene.mlab.clf(figure=Mayavi_Scene.fig1)
testPlot = mlab.test_contour3d()
view = View(Item('scene', editor = SceneEditor(scene_class=MayaviScene),
height=300, width=300, show_label=False),
resizable=True,
)
class P1(QtGui.QWidget):
def __init__(self, parent=None):
super(P1, self).__init__(parent)
layout = QtGui.QGridLayout(self)
layout.setContentsMargins(20,20,20,20)
layout.setSpacing(10)
self.label_edge1 = QtGui.QLabel('')
self.label_edge1.setMargin(5)
self.label_edge1.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Sunken)
layout.addWidget(self.label_edge1, 0, 0, 10, 10)
self.label_edge1.show()
self.label_avgVol = QtGui.QLabel('Test')
self.label_avgVol.setMargin(5)
self.label_avgVol.setFrameStyle(QtGui.QFrame.Box | QtGui.QFrame.Sunken)
self.label_avgVol.setAlignment(QtCore.Qt.AlignCenter | QtCore.Qt.AlignVCenter)
layout.addWidget(self.label_avgVol, 0, 0, 1, 10)
self.label_avgVol.show()
self.groupscroll = QtGui.QHBoxLayout()
self.groupscrollbox = QtGui.QGroupBox()
self.viz1 = Mayavi_Scene()
self.ui1 = self.viz1.edit_traits(parent=self, kind='subpanel').control
self.MVB = QtGui.QVBoxLayout()
self.MVB.addWidget(self.ui1)
scroll = QtGui.QScrollArea()
widget = QtGui.QWidget(self)
widget.setLayout(QtGui.QHBoxLayout())
widget.layout().addWidget(self.groupscrollbox)
scroll.setWidget(widget)
scroll.setWidgetResizable(True)
self.groupscrollbox.setLayout(self.MVB)
self.groupscroll.addWidget(scroll)
layout.addWidget(scroll, 1, 1, 1, 9)
class P2(QtGui.QWidget):
def __init__(self, parent=None):
super(P2, self).__init__(parent)
layout = QtGui.QGridLayout(self)
layout.setContentsMargins(20,20,20,20)
layout.setSpacing(10)
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setGeometry(50, 50, 500, 500)
tab1 = P1(self)
tab2 = P2(self)
self.tabs = QtGui.QTabWidget(self)
self.tabs.resize(250,150)
self.tabs.addTab(tab1, 'Page 1')
self.tabs.addTab(tab2, 'Page 2')
self.setWindowTitle('SCROLLBAR ERROR EXAMPLE')
self.setCentralWidget(self.tabs)
self.show()
if __name__ == '__main__':
app = QtGui.QApplication.instance()
w = MainWindow()
sys.exit(app.exec_())
In that condition, the scroll bars are inside the tab and not ouside. Then you get something like :
But I'm not sure if this is what you wanted.
Related
Here is my sample code,i want to add a push button to each and every row of the list view.I am not found any method to set the widget to model.Can any one please help me how to add widget for each and every row of the list view.Thank you in advance.
Given below is my code:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyCustomWidget(QWidget):
def __init__(self,parent=None):
super(MyCustomWidget, self).__init__(parent)
self.row = QHBoxLayout()
self.row.addWidget(QPushButton("add"))
self.setLayout(self.row)
class Dialog(QtGui.QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent=parent)
vLayout = QtGui.QVBoxLayout(self)
hLayout = QtGui.QHBoxLayout()
self.lineEdit = QtGui.QLineEdit()
hLayout.addWidget(self.lineEdit)
self.filter = QtGui.QPushButton("filter", self)
hLayout.addWidget(self.filter)
self.list = QtGui.QListView(self)
vLayout.addLayout(hLayout)
vLayout.addWidget(self.list)
self.model = QtGui.QStandardItemModel(self.list)
codes = [
'windows',
'windows xp',
'windows7',
'hai',
'habit',
'hack',
'good'
]
self.list.setModel(self.model)
for code in codes:
item = QtGui.QStandardItem(code)
self.model.appendRow(item)
self.list.setIndexWidget(item.index(), QtGui.QPushButton("button"))
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
w = Dialog()
w.show()
sys.exit(app.exec_())
You have to create a custom widget where you must set the button on the right side with a layout.
import sys
from PyQt4 import QtCore, QtGui
class CustomWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(CustomWidget, self).__init__(parent)
self.button = QtGui.QPushButton("button")
lay = QtGui.QHBoxLayout(self)
lay.addWidget(self.button, alignment=QtCore.Qt.AlignRight)
lay.setContentsMargins(0, 0, 0, 0)
class Dialog(QtGui.QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent=parent)
vLayout = QtGui.QVBoxLayout(self)
hLayout = QtGui.QHBoxLayout()
self.lineEdit = QtGui.QLineEdit()
hLayout.addWidget(self.lineEdit)
self.filter = QtGui.QPushButton("filter", self)
hLayout.addWidget(self.filter)
self.list = QtGui.QListView(self)
vLayout.addLayout(hLayout)
vLayout.addWidget(self.list)
self.model = QtGui.QStandardItemModel(self.list)
codes = [
'windows',
'windows xp',
'windows7',
'hai',
'habit',
'hack',
'good'
]
self.list.setModel(self.model)
for code in codes:
item = QtGui.QStandardItem(code)
self.model.appendRow(item)
self.list.setIndexWidget(item.index(), CustomWidget())
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
w = Dialog()
w.show()
sys.exit(app.exec_())
I want to make GUI like below with PyQt5, but I can't find an example to help me.
I searched for "change layout on qwidget" and "tab pane with no title bar" and "card layout" without luck. How can I make this with PyQt5?
You have to use a QStackedLayout (or a QStackedWidget) that changes pages when the buttons are pressed. And the first page should have the buttons. I have also implemented the back() method that returns to the initial page, that slot must be invoked when the Change button is pressed:
from functools import partial
from PyQt5 import QtCore, QtWidgets
class CardWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(CardWidget, self).__init__(parent)
self._layout = QtWidgets.QStackedLayout(self)
button_widget = QtWidgets.QWidget()
self.btn_lay = QtWidgets.QFormLayout(button_widget)
self._layout.addWidget(button_widget)
def add_widget(self, text, widget):
self._layout.addWidget(widget)
btn = QtWidgets.QPushButton(text)
self.btn_lay.addRow(btn)
btn.clicked.connect(partial(self._layout.setCurrentWidget, widget))
#QtCore.pyqtSlot()
def back(self):
self._layout.setCurrentIndex(0)
class Widget(QtWidgets.QWidget):
backSignal = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.le1 = QtWidgets.QLineEdit()
self.le2 = QtWidgets.QLineEdit()
button = QtWidgets.QPushButton("Change")
button.clicked.connect(self.backSignal)
flay = QtWidgets.QFormLayout()
flay.addRow("Value 1:", self.le1)
flay.addRow("Value 2:", self.le2)
lay = QtWidgets.QVBoxLayout(self)
lay.addLayout(flay)
lay.addWidget(button)
def create_label():
label = QtWidgets.QLabel(
"Some Other Components",
alignment=QtCore.Qt.AlignCenter
)
label.setStyleSheet("background-color:blue;")
return label
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
c = CardWidget()
for i in range(3):
w = Widget()
w.backSignal.connect(c.back)
c.add_widget("Want to Change value {}".format(i+1), w)
p = QtWidgets.QWidget()
lay = QtWidgets.QGridLayout(p)
lay.addWidget(create_label(), 0, 0, 1, 2)
lay.addWidget(c, 1, 0)
lay.addWidget(create_label(), 1, 1)
lay.setColumnStretch(0, 1)
lay.setColumnStretch(1, 1)
lay.setRowStretch(0, 1)
lay.setRowStretch(1, 1)
p.resize(640, 480)
p.show()
sys.exit(app.exec_())
I've a Widget, which wants to display Images with QLabel and QCheckBox. 4 classes are created each contains some information to be put on the final screen.
Class Grid align and grid images, text and checkboxes.
After script running get current screen. No images appear in present widget.
Where are images?
Desired screen
The code
import sys, os
from PyQt5 import QtCore, QtGui, QtWidgets
iconroot = os.path.dirname(__file__)
class ImageWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ImageWidget, self).__init__(parent)
self.hlay = QtWidgets.QHBoxLayout(self)
buckling_ilabel = QtWidgets.QLabel(self)
pixmap = QtGui.QPixmap(os.path.join(iconroot, "images/fixed-fixed.png"))
buckling_ilabel.resize(150, 150)
buckling_ilabel.setPixmap(pixmap.scaled(buckling_ilabel.size(), QtCore.Qt.KeepAspectRatio))
self.hlay.addWidget(buckling_ilabel)
buckling_blabel = QtWidgets.QLabel(self)
pixmap = QtGui.QPixmap(os.path.join(iconroot, "images/pinned-pinned.png"))
buckling_blabel.resize(150, 150)
buckling_blabel.setPixmap(pixmap.scaled(buckling_blabel.size(), QtCore.Qt.KeepAspectRatio))
self.hlay.addWidget(buckling_blabel)
buckling_clabel = QtWidgets.QLabel(self)
pixmap = QtGui.QPixmap(os.path.join(iconroot, "images/fixed-free.png"))
buckling_clabel.resize(150, 150)
buckling_clabel.setPixmap(pixmap.scaled(buckling_clabel.size(), QtCore.Qt.KeepAspectRatio))
self.hlay.addWidget(buckling_clabel)
buckling_dlabel = QtWidgets.QLabel(self)
pixmap = QtGui.QPixmap(os.path.join(iconroot, "images/fixed-pinned.png"))
buckling_dlabel.resize(150, 150)
buckling_dlabel.setPixmap(pixmap.scaled(buckling_dlabel.size(), QtCore.Qt.KeepAspectRatio))
self.hlay.addWidget(buckling_dlabel)
self.setLayout(self.hlay)
class EffLengthInfo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(EffLengthInfo, self).__init__(parent)
ilabel = QtWidgets.QLabel('Ley = 1.0 L\nLec = 1.0 L')
blabel = QtWidgets.QLabel('Ley = 0.699 L\nLec = 0.699 L')
clabel = QtWidgets.QLabel('Ley = 2.0 L\nLec = 2.0 L')
dlabel = QtWidgets.QLabel('Ley = 0.5 L\nLec = 0.5 L')
self.ilay = QtWidgets.QHBoxLayout()
self.ilay.addWidget(ilabel)
self.ilay.addWidget(blabel)
self.ilay.addWidget(clabel)
self.ilay.addWidget(dlabel)
class CheckInfo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(CheckInfo, self).__init__(parent)
icheck = QtWidgets.QCheckBox()
bcheck = QtWidgets.QCheckBox()
ccheck = QtWidgets.QCheckBox()
dcheck = QtWidgets.QCheckBox()
self.checklay = QtWidgets.QHBoxLayout()
self.checklay.addWidget(icheck, alignment=QtCore.Qt.AlignCenter)
self.checklay.addWidget(bcheck, alignment=QtCore.Qt.AlignCenter)
self.checklay.addWidget(ccheck, alignment=QtCore.Qt.AlignCenter)
self.checklay.addWidget(dcheck, alignment=QtCore.Qt.AlignCenter)
class Grid(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Grid, self).__init__(parent)
self.imagewidget = ImageWidget()
self.efflengthinfo = EffLengthInfo()
self.checkinfo = CheckInfo()
vlay = QtWidgets.QVBoxLayout(self)
vlay.addLayout(self.imagewidget.hlay)
vlay.addLayout(self.efflengthinfo.ilay)
vlay.addLayout(self.checkinfo.checklay)
self.setLayout(vlay)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
im = Grid()
im.show()
sys.exit(app.exec_())
The #S.Nick's solution works but it is not the correct one, besides that it does not explain the problem and it only indicates the error message that you should have provided.
The first mistake you have is that you do not have a consistent design, for example, even though a widget is a visual element, it does not only mean that it is only, it also has associated data, so I ask myself: Why did you create the ImageWidget class, the same thing? for EffLengthInfo and CheckInfo? Well it seems that just to create a layout, is it necessary to create a widget to just create a layout? No, because the widget is an unnecessary resource, that is, wasted memory. Therefore, I point out that S.Nick's solution is incorrect since he is inviting others to overlap a design error.
If your goal is just to create layouts then better use a function:
import sys, os
from PyQt5 import QtCore, QtGui, QtWidgets
iconroot = os.path.dirname(__file__)
def create_image_layout():
hlay = QtWidgets.QHBoxLayout()
for icon_name in ("images/fixed-fixed.png",
"images/pinned-pinned.png",
"images/fixed-free.png",
"images/fixed-pinned.png"):
label = QtWidgets.QLabel()
pixmap = QtGui.QPixmap(os.path.join(iconroot, icon_name))
label.resize(150, 150)
label.setPixmap(pixmap.scaled(label.size(), QtCore.Qt.KeepAspectRatio))
hlay.addWidget(label)
return hlay
def create_EffLengthInfo_layout():
hlay = QtWidgets.QHBoxLayout()
for text in ('Ley = 1.0 L\nLec = 1.0 L',
'Ley = 0.699 L\nLec = 0.699 L',
'Ley = 2.0 L\nLec = 2.0 L',
'Ley = 0.5 L\nLec = 0.5 L'):
label = QtWidgets.QLabel(text)
hlay.addWidget(label)
return hlay
def create_checkInfo_layout():
hlay = QtWidgets.QHBoxLayout()
for _ in range(3):
checkbox = QtWidgets.QCheckBox()
hlay.addWidget(checkbox, alignment=QtCore.Qt.AlignCenter)
return hlay
class Grid(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Grid, self).__init__(parent)
image_lay = create_image_layout()
efflengthinfo_lay = create_EffLengthInfo_layout()
checkinfo_lay = create_checkInfo_layout()
vlay = QtWidgets.QVBoxLayout(self)
vlay.addLayout(image_lay)
vlay.addLayout(efflengthinfo_lay)
vlay.addLayout(checkinfo_lay)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
im = Grid()
im.show()
sys.exit(app.exec_())
Why was it shown in the case of EffLengthInfo and CheckInfo "works"?
I point out that it works in quotes because it works but it is not the solution, going to the point, a layout is established in a layout and it is part of the widget and after that it can not be placed in another widget, and if we check ImageWidget we see that you have established two times the layout to the widget:
class ImageWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ImageWidget, self).__init__(parent)
self.hlay = QtWidgets.QHBoxLayout(self) # 1.
...
self.setLayout(self.hlay) # 2.
When you set a widget when building the layout, it is set in that widget.
When the widget sets the layout using the setLayout() method
Therefore you should throw the following error in the line vlay.addLayout(self.imagewidget.hlay):
QLayout::addChildLayout: layout "" already has a parent
Another solution is to set the widgets instead of the layouts:
class ImageWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ImageWidget, self).__init__(parent)
self.hlay = QtWidgets.QHBoxLayout(self)
...
# self.setLayout(self.hlay) # comment this line
class EffLengthInfo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(EffLengthInfo, self).__init__(parent)
...
self.ilay = QtWidgets.QHBoxLayout(self) # <-- add self
class CheckInfo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(CheckInfo, self).__init__(parent)
...
self.checklay = QtWidgets.QHBoxLayout(self) # <-- add self
class Grid(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Grid, self).__init__(parent)
self.imagewidget = ImageWidget()
self.efflengthinfo = EffLengthInfo()
self.checkinfo = CheckInfo()
vlay = QtWidgets.QVBoxLayout(self)
vlay.addWidget(self.imagewidget) # <-- change addLayout() to addWidget()
vlay.addWidget(self.efflengthinfo) # <-- change addLayout() to addWidget()
vlay.addWidget(self.checkinfo) # <-- change addLayout() to addWidget()
...
Another perspective that you might think, and this depends on the use you want to give the widget, is that you want an information unit to be an image, a text and a checkbox, so if it is correct to create a widget:
import sys, os
from PyQt5 import QtCore, QtGui, QtWidgets
iconroot = os.path.dirname(__file__)
class FooWidget(QtWidgets.QWidget):
def __init__(self, path_icon, text, checked=False, parent=None):
super(FooWidget, self).__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
pixmap = QtGui.QPixmap(os.path.join(iconroot, path_icon))
pixmap_label = QtWidgets.QLabel()
pixmap_label.resize(150, 150)
pixmap_label.setPixmap(pixmap.scaled(pixmap_label.size(), QtCore.Qt.KeepAspectRatio))
text_label = QtWidgets.QLabel(text)
checkbox = QtWidgets.QCheckBox(checked=checked)
lay.addWidget(pixmap_label)
lay.addWidget(text_label)
lay.addWidget(checkbox)
class Grid(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Grid, self).__init__(parent)
lay = QtWidgets.QHBoxLayout(self)
icons = ["images/fixed-fixed.png",
"images/pinned-pinned.png",
"images/fixed-free.png",
"images/fixed-pinned.png"]
texts = ["Ley = 1.0 L\nLec = 1.0 L",
"Ley = 0.699 L\nLec = 0.699 L",
"Ley = 2.0 L\nLec = 2.0 L",
"Ley = 0.5 L\nLec = 0.5 L"]
for path_icon, text in zip(icons, texts):
w = FooWidget(os.path.join(iconroot, path_icon), text)
lay.addWidget(w)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
im = Grid()
im.show()
sys.exit(app.exec_())
Line: vlay.addLayout (self.imagewidget.hlay)
error is: QLayout :: addChildLayout: layout "" already has a parent
Try it:
import sys, os
from PyQt5 import QtCore, QtGui, QtWidgets
iconroot = os.path.dirname(__file__)
class ImageWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ImageWidget, self).__init__(parent)
# self.hlay = QtWidgets.QHBoxLayout(self)
self.hlay = QtWidgets.QHBoxLayout() # < --- -self
buckling_ilabel = QtWidgets.QLabel(self)
pixmap = QtGui.QPixmap(os.path.join(iconroot, "images/logo.png"))
buckling_ilabel.resize(150, 150)
buckling_ilabel.setPixmap(pixmap.scaled(buckling_ilabel.size(), QtCore.Qt.KeepAspectRatio))
self.hlay.addWidget(buckling_ilabel)
buckling_blabel = QtWidgets.QLabel(self)
pixmap = QtGui.QPixmap(os.path.join(iconroot, "images/logo.png"))
buckling_blabel.resize(150, 150)
buckling_blabel.setPixmap(pixmap.scaled(buckling_blabel.size(), QtCore.Qt.KeepAspectRatio))
self.hlay.addWidget(buckling_blabel)
buckling_clabel = QtWidgets.QLabel(self)
pixmap = QtGui.QPixmap(os.path.join(iconroot, "images/logo.png"))
buckling_clabel.resize(150, 150)
buckling_clabel.setPixmap(pixmap.scaled(buckling_clabel.size(), QtCore.Qt.KeepAspectRatio))
self.hlay.addWidget(buckling_clabel)
buckling_dlabel = QtWidgets.QLabel(self)
pixmap = QtGui.QPixmap(os.path.join(iconroot, "images/logo.png"))
buckling_dlabel.resize(150, 150)
buckling_dlabel.setPixmap(pixmap.scaled(buckling_dlabel.size(), QtCore.Qt.KeepAspectRatio))
self.hlay.addWidget(buckling_dlabel)
# self.setLayout(self.hlay) # < ---
class EffLengthInfo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(EffLengthInfo, self).__init__(parent)
ilabel = QtWidgets.QLabel('Ley = 1.0 L\nLec = 1.0 L')
blabel = QtWidgets.QLabel('Ley = 0.699 L\nLec = 0.699 L')
clabel = QtWidgets.QLabel('Ley = 2.0 L\nLec = 2.0 L')
dlabel = QtWidgets.QLabel('Ley = 0.5 L\nLec = 0.5 L')
self.ilay = QtWidgets.QHBoxLayout()
self.ilay.addWidget(ilabel)
self.ilay.addWidget(blabel)
self.ilay.addWidget(clabel)
self.ilay.addWidget(dlabel)
class CheckInfo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(CheckInfo, self).__init__(parent)
icheck = QtWidgets.QCheckBox()
bcheck = QtWidgets.QCheckBox()
ccheck = QtWidgets.QCheckBox()
dcheck = QtWidgets.QCheckBox()
self.checklay = QtWidgets.QHBoxLayout()
self.checklay.addWidget(icheck, alignment=QtCore.Qt.AlignCenter)
self.checklay.addWidget(bcheck, alignment=QtCore.Qt.AlignCenter)
self.checklay.addWidget(ccheck, alignment=QtCore.Qt.AlignCenter)
self.checklay.addWidget(dcheck, alignment=QtCore.Qt.AlignCenter)
class Grid(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Grid, self).__init__(parent)
self.imagewidget = ImageWidget()
self.efflengthinfo = EffLengthInfo()
self.checkinfo = CheckInfo()
vlay = QtWidgets.QVBoxLayout(self)
vlay.addLayout(self.imagewidget.hlay) # < ---
# Error: QLayout::addChildLayout: layout "" already has a parent # < ---
vlay.addLayout(self.efflengthinfo.ilay)
vlay.addLayout(self.checkinfo.checklay)
self.setLayout(vlay)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
im = Grid()
im.show()
sys.exit(app.exec_())
I tried to make some simple local chatting app for practice. I make a button to send a message every time i push the button the message displayed but the scrollarea become more narrow and not expanded the scrollarea. Am I missing something or added something i shouldn't in my code? how can i fix this?
Here is my code:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Bubble(QLabel):
def __init__(self,text):
super(Bubble,self).__init__(text)
self.setContentsMargins(5,5,5,5)
def paintEvent(self, e):
p = QPainter(self)
p.setRenderHint(QPainter.Antialiasing,True)
p.drawRoundedRect(0,0,self.width()-1,self.height()-1,5,5)
super(Bubble,self).paintEvent(e)
class MyWidget(QWidget):
def __init__(self,text,left=True):
super(MyWidget,self).__init__()
hbox = QHBoxLayout()
label = Bubble(text)
if not left:
hbox.addSpacerItem(QSpacerItem(1,1,QSizePolicy.Expanding,QSizePolicy.Preferred))
hbox.addWidget(label)
if left:
hbox.addSpacerItem(QSpacerItem(1,1,QSizePolicy.Expanding,QSizePolicy.Preferred))
hbox.setContentsMargins(0,0,0,0)
self.setLayout(hbox)
self.setContentsMargins(0,0,0,0)
class Chatting(QWidget):
def __init__(self, parent=None):
super(Chatting, self).__init__(parent)
self.vbox = QVBoxLayout()
for _ in range(20):
self.vbox.addWidget(MyWidget("Left side"))
widget = QWidget()
widget.setLayout(self.vbox)
scroll = QScrollArea()
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll.setWidgetResizable(False)
scroll.setWidget(widget)
#Scroll Area Layer add
vLayout = QVBoxLayout(self)
vLayout.addWidget(scroll)
send = QPushButton('')
send.setIcon(QIcon('images/send.png'))
send.setStyleSheet("background-color:#d00001; width: 44px")
send.setIconSize(QSize(84,20))
send.clicked.connect(self.send_messages)
vLayout.addWidget(send)
self.setLayout(vLayout)
def send_messages(self):
self.vbox.addWidget(MyWidget('testing'))
The problem is simple you have to enable the widgetResizable property to True since that property allows the QScrollArea to calculate the size of the content.
scroll.setWidgetResizable(True)
On the other hand I have taken the time to improve your code and I show it below:
from PyQt4 import QtGui, QtCore
class Bubble(QtGui.QLabel):
def __init__(self, text):
super(Bubble,self).__init__(text)
self.setContentsMargins(5, 5, 5, 5)
def paintEvent(self, e):
p = QtGui.QPainter(self)
p.setRenderHint(QtGui.QPainter.Antialiasing, True)
p.drawRoundedRect(self.rect().adjusted(0, 0, -1, -1), 5, 5)
super(Bubble, self).paintEvent(e)
class MyWidget(QtGui.QWidget):
def __init__(self, text, left=True):
super(MyWidget,self).__init__()
lay = QtGui.QVBoxLayout(self)
lay.setContentsMargins(0, 0, 0, 0)
self.setContentsMargins(0, 0, 0, 0)
label = Bubble(text)
lay.addWidget(label, alignment= QtCore.Qt.AlignLeft if left else QtCore.Qt.AlignRight)
class Chatting(QtGui.QWidget):
def __init__(self, parent=None):
super(Chatting, self).__init__(parent)
widget = QtGui.QWidget()
self.vbox = QtGui.QVBoxLayout(widget)
self.vbox.addStretch()
self.scroll = QtGui.QScrollArea(widgetResizable=True)
self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.scroll.setWidget(widget)
#Scroll Area Layer add
send = QtGui.QPushButton(icon= QtGui.QIcon('images/send.png'))
send.setStyleSheet("background-color:#d00001; width: 44px")
send.setIconSize(QtCore.QSize(84,20))
send.clicked.connect(self.on_clicked)
vLayout = QtGui.QVBoxLayout(self)
vLayout.addWidget(self.scroll)
vLayout.addWidget(send)
def send_message(self, text, direction=True):
widget = MyWidget(text, direction)
self.vbox.insertWidget(self.vbox.count()-1, widget)
scroll_bar = self.scroll.verticalScrollBar()
# move to end
QtCore.QTimer.singleShot(10, lambda: scroll_bar.setValue(scroll_bar.maximum()))
#QtCore.pyqtSlot()
def on_clicked(self):
self.send_message("testing")
if __name__ == '__main__':
import sys
import random
app = QtGui.QApplication(sys.argv)
w = Chatting()
def test():
for _ in range(8):
w.send_message("testing", random.choice((True, False)))
QtCore.QTimer.singleShot(1000, test)
w.resize(240, 480)
w.show()
sys.exit(app.exec_())
I have the following PyQtGraph program, which makes a red square "move" when moving a slider:
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QSlider
from pyqtgraph import (
mkBrush,
mkPen,
GraphicsObject,
QtGui,
PlotWidget,
)
class SquareItem(GraphicsObject):
def __init__(self):
super().__init__()
self.position_picture = QtGui.QPicture()
def paint(self, p, *args):
p.drawPicture(0, 0, self.position_picture)
def boundingRect(self):
return QtCore.QRectF(-5, -5, 20, 10)
def update_position(self, x):
self.position_picture = QtGui.QPicture()
painter = QtGui.QPainter(self.position_picture)
painter.scale(1, -1)
painter.setBrush(mkBrush('r'))
painter.setPen(mkPen(None))
painter.drawRect(QtCore.QRectF(x, 0, 1, 1))
painter.end()
self.informViewBoundsChanged()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle('Micromouse maze simulator')
self.resize(600, 600)
frame = QtWidgets.QFrame()
layout = QtWidgets.QVBoxLayout(frame)
self.graphics = PlotWidget()
self.graphics.setAspectLocked()
self.item = SquareItem()
self.graphics.addItem(self.item)
self.slider = QSlider(QtCore.Qt.Horizontal)
self.slider.setSingleStep(1)
self.slider.setPageStep(10)
self.slider.setRange(0, 10)
self.slider.setTickPosition(QSlider.TicksAbove)
self.slider.valueChanged.connect(self.slider_value_changed)
self.slider.setValue(1)
layout.addWidget(self.graphics)
layout.addWidget(self.slider)
self.setCentralWidget(frame)
def slider_value_changed(self, value):
self.item.update_position(value)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
Everything seems to work fine, but if I zoom in/out and then move the slider again the square position is no longer updated (i.e.: the square is not re-drawn).
How can I fix that?
Updates
I am using a square to simplify the problem. In reality, I do not only change position, but I can also draw different shapes, so using setPos() is not really an option.
You should not update the painting if you want to change position, you should only use setPos(). the paint() function takes boundingRect() as a reference so when moving the graph you are moving it in that coordinate system instead of the coordinate system of PlotWidget.
class SquareItem(GraphicsObject):
def paint(self, p, *args):
p.setBrush(mkBrush('r'))
p.setPen(mkPen(None))
p.drawRect(self.boundingRect())
def boundingRect(self):
return QtCore.QRectF(0, 0, 1, 1)
def update_position(self, x):
self.setPos(x, 0)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle('Micromouse maze simulator')
self.resize(600, 600)
frame = QtWidgets.QFrame()
layout = QtWidgets.QVBoxLayout(frame)
self.graphics = PlotWidget()
self.graphics.setAspectLocked()
self.item = SquareItem()
self.graphics.addItem(self.item)
self.graphics.setRange(rect=QtCore.QRectF(-10, -10, 20, 20))
self.slider = QSlider(QtCore.Qt.Horizontal)
self.slider.setSingleStep(1)
self.slider.setPageStep(10)
self.slider.setRange(0, 10)
self.slider.setTickPosition(QSlider.TicksAbove)
self.slider.valueChanged.connect(self.slider_value_changed)
self.slider.setValue(1)
layout.addWidget(self.graphics)
layout.addWidget(self.slider)
self.setCentralWidget(frame)
def slider_value_changed(self, value):
self.item.update_position(value)
If you are not going to use signals it is advisable to use objects that inherit from QGraphicsItem instead of QGraphicsObject, for example you could use QGraphicsRectItem:
import sys
from PyQt5 import QtCore, QtWidgets
from pyqtgraph import (
mkBrush,
mkPen,
GraphicsObject,
QtGui,
PlotWidget,
)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle('Micromouse maze simulator')
self.resize(600, 600)
frame = QtWidgets.QFrame()
layout = QtWidgets.QVBoxLayout(frame)
self.graphics = PlotWidget()
self.graphics.setAspectLocked()
self.item = QtWidgets.QGraphicsRectItem(0, 0, 1, 1)
self.item.setBrush(mkBrush('r'))
self.item.setPen(mkPen(None))
self.graphics.addItem(self.item)
self.graphics.setRange(rect=QtCore.QRectF(-10, -10, 20, 20))
self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.slider.setSingleStep(1)
self.slider.setPageStep(10)
self.slider.setRange(0, 10)
self.slider.setTickPosition(QtWidgets.QSlider.TicksAbove)
self.slider.valueChanged.connect(self.slider_value_changed)
self.slider.setValue(1)
layout.addWidget(self.graphics)
layout.addWidget(self.slider)
self.setCentralWidget(frame)
def slider_value_changed(self, value):
self.item.setPos(value, 0)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
update:
If you want to redraw you should call update():
import sys
from PyQt5 import QtCore, QtWidgets
from pyqtgraph import (
mkBrush,
mkPen,
GraphicsObject,
QtGui,
PlotWidget,
)
class SquareItem(GraphicsObject):
colors = ['r', 'g', 'b', 'c', 'm', 'y', 'k', 'w', 'FF0', 'AA0', '0AA']
def __init__(self):
super().__init__()
self.mColor = SquareItem.colors[0]
def paint(self, p, *args):
p.setBrush(mkBrush(self.mColor))
p.setPen(mkPen(None))
p.drawRect(self.boundingRect())
def boundingRect(self):
return QtCore.QRectF(0, 0, 1, 1)
def update_draw(self, x):
self.mColor = SquareItem.colors[x]
self.update()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle('Micromouse maze simulator')
self.resize(600, 600)
frame = QtWidgets.QFrame()
layout = QtWidgets.QVBoxLayout(frame)
self.graphics = PlotWidget()
self.graphics.setAspectLocked()
self.item = SquareItem()
self.graphics.addItem(self.item)
self.graphics.setRange(rect=QtCore.QRectF(-10, -10, 20, 20))
self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.slider.setSingleStep(1)
self.slider.setPageStep(10)
self.slider.setRange(0, 10)
self.slider.setTickPosition(QtWidgets.QSlider.TicksAbove)
self.slider.valueChanged.connect(self.item.update_draw)
self.slider.setValue(1)
layout.addWidget(self.graphics)
layout.addWidget(self.slider)
self.setCentralWidget(frame)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())