PyQt - Custom Widget Disappears when added with Alignment - python

I'm having an issue where a custom widget I am making appears just fine when added to a QGridLayout without specifying an alignment, but when I specify an Alignment it disappears.
Here is the custom widget:
class OutlineLabel(QWidget):
def __init__(self, text, parent=None):
QWidget.__init__(self, parent)
self.text = text
def paintEvent(self, event=None):
painter = QPainter(self)
path = QPainterPath()
pen = QPen();
font = QFont("Helvetica", 14, 99, False)
brush = QBrush(QtCore.Qt.white)
pen.setWidth(1);
pen.setColor(QtCore.Qt.black);
painter.setFont(font);
painter.setPen(pen);
painter.setBrush(brush)
path.addText(0, 20, font, self.text);
painter.drawPath(path);
Here is an example where it will display just fine:
app = QApplication(sys.argv)
wnd = QWidget()
wnd.resize(400,400)
grid = QGridLayout()
test = OutlineLabel("hello")
grid.addWidget(test, 0, 0)
wnd.setLayout(grid)
wnd.show()
sys.exit(app.exec_())
But if I change it to the following, it no longer shows:
app = QApplication(sys.argv)
wnd = QWidget()
wnd.resize(400,400)
grid = QGridLayout()
test = OutlineLabel("hello")
grid.addWidget(test, 0, 0, QtCore.Qt.AlignHCenter)
wnd.setLayout(grid)
wnd.show()
sys.exit(app.exec_())
Is there some information I need to set in the OutlineLabel class to work correctly with Alignments?

It could be a problem with size hint (your widget is empty).
If you reimplement the sizeHint() method to return a valid QSize, your widget should appear:
def sizeHint( self ):
return QSize( 200, 200 )

Related

QToolBar size and alignment with hidden actions

I'm adding a toolbar to a widget, and dynamically hiding actions (they are also used in context menus). I'm encountering some problem with alignment/size. In the image below, Test 1 is "correct", and I want the other two to have only the "C" button, right-aligned (as in Test 3, but without the extra left space).
The code shows my attempt. Test 2 simply uses .setVisible(False) on the action A and B, while Test 3 does the same and adds a couple of empty QLabel on the left (if I add only one), the C does not end up right-aligned. It looks like QToolBar wants a minimum of 3 buttons or something.
Any idea how to fix this?
#!/usr/bin/env python
from PyQt4.QtGui import *
import sys
class MainWindow(QMainWindow):
def __init__(self):
super(QMainWindow, self).__init__()
stylesheet = 'QToolButton{padding: 0; margin: 0} QToolBar{border: 1px solid black}'
layout = QVBoxLayout()
a = QAction('A', self)
b = QAction('B', self)
c = QAction('C', self)
toolbar = QToolBar()
toolbar.addAction(a)
toolbar.addAction(b)
toolbar.addAction(c)
toolbar.setStyleSheet(stylesheet)
hbox = QHBoxLayout()
hbox.addWidget(QLabel('Test 1'))
hbox.addStretch(1)
hbox.addWidget(toolbar)
group = QGroupBox()
group.setLayout(hbox)
layout.addWidget(group)
a = QAction('A', self)
b = QAction('B', self)
c = QAction('C', self)
toolbar = QToolBar()
toolbar.addAction(a)
toolbar.addAction(b)
toolbar.addAction(c)
a.setVisible(False)
b.setVisible(False)
toolbar.setStyleSheet(stylesheet)
hbox = QHBoxLayout()
hbox.addWidget(QLabel('Test 2'))
hbox.addStretch(1)
hbox.addWidget(toolbar)
group = QGroupBox()
group.setLayout(hbox)
layout.addWidget(group)
a = QAction('A', self)
b = QAction('B', self)
c = QAction('C', self)
toolbar = QToolBar()
toolbar.addWidget(QLabel(''))
toolbar.addWidget(QLabel(''))
toolbar.addAction(a)
toolbar.addAction(b)
toolbar.addAction(c)
a.setVisible(False)
b.setVisible(False)
toolbar.setStyleSheet(stylesheet)
hbox = QHBoxLayout()
hbox.addWidget(QLabel('Test 3'))
hbox.addStretch(1)
hbox.addWidget(toolbar)
group = QGroupBox()
group.setLayout(hbox)
layout.addWidget(group)
layout.addStretch(1)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.show()
app = QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec_())
To dynamically hide actions in a QToolbar, you cannot use setVisible(False) on the action since it will only "hide" the action from view but the button is inherently still there which is why you have the extra white space. Even though the action is hidden, there is still padding from a hidden object which gives you the undesired empty space. Similarly, when you add empty QLabels, this "hidden" widget also leaves white space since the physical area of the widget is still there.
Considering this, a solution is to remove the action from the QToolbar using removeAction(). This way, the actual object will be removed from the layout so there is no white space. Here's an example that shows how to add and remove actions in a QToolbar using handlers. You can check what objects are in the Qtoolbar with .actions() and depending on what action you want to remove, you can simply pass this to removeAction(). I'll leave it up to you to implement error handling when there are no more actions to add or no more actions to remove.
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
class MainWindow(QMainWindow):
def __init__(self):
super(QMainWindow, self).__init__()
self.stylesheet = 'QToolButton{padding: 0; margin: 0} QToolBar{border: 1px solid black}'
self.main_layout = QGridLayout()
self.layout = QVBoxLayout()
self.a = QAction('A', self)
self.b = QAction('B', self)
self.c = QAction('C', self)
self.action_table = {1: self.a,
2: self.b,
3: self.c
}
self.test1_toolbar = QToolBar()
self.test1_toolbar.addAction(self.a)
self.test1_toolbar.addAction(self.b)
self.test1_toolbar.addAction(self.c)
self.test1_toolbar.setStyleSheet(self.stylesheet)
self.test1_hbox = QHBoxLayout()
self.test1_hbox.addWidget(QLabel('Test 1'))
self.test1_hbox.addStretch(1)
self.test1_hbox.addWidget(self.test1_toolbar)
self.test1_group = QGroupBox()
self.test1_group.setLayout(self.test1_hbox)
self.layout.addWidget(self.test1_group)
self.test2_toolbar = QToolBar()
self.test2_toolbar.addAction(self.a)
self.test2_toolbar.addAction(self.b)
self.test2_toolbar.addAction(self.c)
self.test2_toolbar.setStyleSheet(self.stylesheet)
self.test2_add_button = QPushButton('Add')
self.test2_add_button.clicked.connect(self.test2_add_action)
self.test2_remove_button = QPushButton('Remove')
self.test2_remove_button.clicked.connect(self.test2_remove_action)
self.test2_hbox = QHBoxLayout()
self.test2_hbox.addWidget(QLabel('Test 2'))
self.test2_hbox.addStretch(1)
self.test2_hbox.addWidget(self.test2_toolbar)
self.test2_group = QGroupBox()
self.test2_group.setLayout(self.test2_hbox)
self.layout.addWidget(self.test2_group)
self.test3_toolbar = QToolBar()
self.test3_toolbar.addAction(self.a)
self.test3_toolbar.addAction(self.b)
self.test3_toolbar.addAction(self.c)
self.test3_toolbar.setStyleSheet(self.stylesheet)
self.test3_hbox = QHBoxLayout()
self.test3_hbox.addWidget(QLabel('Test 3'))
self.test3_hbox.addStretch(1)
self.test3_hbox.addWidget(self.test3_toolbar)
self.test3_group = QGroupBox()
self.test3_group.setLayout(self.test3_hbox)
self.layout.addWidget(self.test3_group)
self.layout.addStretch(1)
self.widget = QWidget()
self.main_layout.addLayout(self.layout,0,0,1,1)
self.main_layout.addWidget(self.test2_add_button,0,1,1,1)
self.main_layout.addWidget(self.test2_remove_button,0,2,1,1)
self.widget.setLayout(self.main_layout)
self.setCentralWidget(self.widget)
self.show()
def test2_add_action(self):
objects = len(self.test2_toolbar.actions())
self.test2_toolbar.addAction(self.action_table[objects + 1])
def test2_remove_action(self):
objects = len(self.test2_toolbar.actions())
self.test2_toolbar.removeAction(self.action_table[objects])
app = QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec_())

In PyQt5 right widget is shorter than the rest

I am doing a small application with GUI in Python using PyQt5 but for some reason my right widget appears to be shorter than the rest
As you can see QGridLayout is a little bit shorter. I tried other layouts but it's all the same. This really bothers me. Anybody know the reason?
Here is my code. I post the simplified version for convenience but nonetheless it illustrates my problem.
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self._x = 300
self._y = 300
self._width = 1000
self._height = 600
self.init_ui()
def init_ui(self):
self.init_main_layout()
self.setGeometry(self._x, self._y, self._width, self._height)
self.show()
def init_main_layout(self):
# central widget
self.main_splitter = QSplitter(Qt.Horizontal)
# canvas and tabwidget in the middle
figure = plt.figure()
canvas = FigureCanvas(figure)
plt_toolbar = NavigationToolbar(canvas, self)
mid_splitter = QSplitter(Qt.Vertical)
layout = QVBoxLayout()
layout.addWidget(plt_toolbar)
layout.addWidget(canvas)
pl = QWidget()
pl.setLayout(layout)
mid_splitter.addWidget(pl)
tabs = QTabWidget()
tab1 = QWidget()
formGroupBox = QGroupBox("")
layout = QFormLayout()
formGroupBox.setLayout(layout)
hor_layout = QHBoxLayout()
hor_layout.addWidget(formGroupBox)
tab1.setLayout(hor_layout)
mid_splitter.addWidget(tabs)
# the right one
right_part = QGridLayout()
right_part.addWidget(QLabel('Text'), 1, 0)
right_part.addWidget(QComboBox(), 1, 1, 1, 5)
right_part.addWidget(QPushButton(), 1, 6)
tree = QFileSystemModel()
tree_widget = QTreeView()
tree_widget.setModel(tree)
right_part.addWidget(tree_widget, 2, 0, -1, -1)
# TODO right part is shorter than the rest
right_part_widget = QWidget()
right_part_widget.setLayout(right_part)
self.main_splitter.addWidget(QListWidget())
self.main_splitter.addWidget(mid_splitter)
self.main_splitter.addWidget(right_part_widget)
self.main_splitter.setSizes([self._width * 0.2, self._width * 0.4, self._width * 0.4])
self.setCentralWidget(self.main_splitter)
self.statusBar().showMessage('Test')
def main(args):
app = QtWidgets.QApplication(args)
main_window = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
import sys
main(sys.argv)
right_part.setContentsMargins(0,0,0,0) worked for me as Heike suggested

Pyqt QSplitter not visualizing correctly

I'm trying to incorporate a QSplitter. The code works perfectly from a functionality standpoint, but the QSplitter itself doesn't appear correctly under the default PyQt style... possibly because it is itself embedded within a vertical splitter. This is confusing for the user.
If you uncomment out the line (and thus change the default PyQt style), the QSplitter visualizes correctly only when hovered over... however, I also don't want this other style.
Can anyone provide any guidance on this matter?
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
hbox = QHBoxLayout(self)
L_layout = QGridLayout()
R_layout = QGridLayout()
L_widgets = QWidget()
L_widgets.setLayout(L_layout)
R_widgets = QWidget()
R_widgets.setLayout(R_layout)
topleft = QFrame()
topleft.setFrameShape(QFrame.StyledPanel)
btn1 = QPushButton('btn1')
bottom = QFrame()
bottom.setFrameShape(QFrame.StyledPanel)
textedit = QTextEdit()
L_layout.addWidget(topleft, 0, 0, 1, 1)
L_layout.addWidget(btn1, 1, 0, 1, 1)
R_layout.addWidget(textedit)
splitter1 = QSplitter(Qt.Horizontal,frameShape=QFrame.StyledPanel,frameShadow=QFrame.Plain)
splitter1.addWidget(L_widgets)
splitter1.addWidget(R_widgets)
splitter1.setStretchFactor(1,1)
splitter2 = QSplitter(Qt.Vertical)
splitter2.addWidget(splitter1)
splitter2.addWidget(bottom)
hbox.addWidget(splitter2)
self.setLayout(hbox)
#QApplication.setStyle(QStyleFactory.create('Cleanlooks'))
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('QSplitter demo')
self.show()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
EDIT: This is apparently a known macOS bug. When viewed on Linux, the bar of splitter1 has the same look as splitter2. I'll leave this topic open in case anyone else knows of a suitable workaround for mac.
Because the QPushButton has default minimum size, when you want to move splitter to left,
the button has reached its minimum size. So you can not move left anymore, otherwise the left will will collapse.
So if you want the left showing as you want, you can set the minimum size off button widget.
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
hbox = QHBoxLayout(self)
L_layout = QGridLayout()
R_layout = QGridLayout()
L_widgets = QWidget()
L_widgets.setLayout(L_layout)
R_widgets = QWidget()
R_widgets.setLayout(R_layout)
topleft = QFrame()
topleft.setFrameShape(QFrame.StyledPanel)
btn1 = QPushButton('btn1')
btn1.setMinimumWidth(1) # For example : set the minimum width to 1, then you can move left until the btn1 width is 1
bottom = QFrame()
bottom.setFrameShape(QFrame.StyledPanel)
textedit = QTextEdit()
L_layout.addWidget(topleft, 0, 0, 1, 1)
L_layout.addWidget(btn1, 1, 0, 1, 1)
R_layout.addWidget(textedit)
splitter1 = QSplitter(Qt.Horizontal,frameShape=QFrame.StyledPanel,frameShadow=QFrame.Plain)
splitter1.addWidget(L_widgets)
splitter1.addWidget(R_widgets)
splitter1.setStretchFactor(1,1)
splitter2 = QSplitter(Qt.Vertical)
splitter2.addWidget(splitter1)
splitter2.addWidget(bottom)
hbox.addWidget(splitter2)
self.setLayout(hbox)
#QApplication.setStyle(QStyleFactory.create('Cleanlooks'))
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('QSplitter demo')
self.show()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Getting the tops of side-by-side widgets to align using PySide

In the code below, the top of the QTextEdit and QGraphicsView widgets are not aligned when using QHBoxLayout. However, if you comment out QTextEdit and uncomment the other QGraphicsView setup, the top of the widgets align perfectly. Here are my questions:
What causes this alignment issue to occur and how can it be fixed?
Are issues like this best avoided by using Qt Creator?
Is the whole QGraphicsView() --> QGraphicsScene() --> QWidget() necessary to place graphics next to other widgets?
import sys
from PySide.QtCore import *
from PySide.QtGui import *
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__()
# Create Widget1
widget1 = QTextEdit()
#widget1 = QWidget()
#view1 = QGraphicsView()
#scene1 = QGraphicsScene(0,0,200,500)
#view1.setScene(scene1)
#layout = QHBoxLayout()
#layout.addWidget(view1)
#widget1.setLayout(layout)
# Create Widget2
widget2 = QWidget()
view2 = QGraphicsView()
scene2 = QGraphicsScene(0,0,200,500)
view2.setScene(scene2)
layout = QHBoxLayout()
layout.addWidget(view2)
widget2.setLayout(layout)
# Layout of Side by Side windows
container = QWidget()
layout = QHBoxLayout()
layout.addWidget(widget1)
layout.addWidget(widget2)
container.setLayout(layout)
# Scroll Area Properties
scroll = QScrollArea()
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll.setWidgetResizable(False)
scroll.setWidget(container)
# Scroll Area Layer add
vLayout = QVBoxLayout(self)
vLayout.addWidget(scroll)
self.setLayout(vLayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = Widget()
dialog.show()
app.exec_()
The layouts have a default margin. So if one widget is in a layout, and its neighbour is not, they will not be aligned. To remove the default margin, you can do this:
layout = QHBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
However, in your example, the container widget and layout for the QGraphicsView aren't doing anything useful. So you could remove those, and along with some other simplifications, arrive at this:
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__()
widget1 = QTextEdit()
widget2 = QGraphicsView()
widget2.setScene(QGraphicsScene(0, 0, 200, 500, widget2))
container = QWidget()
layout = QHBoxLayout(container)
layout.addWidget(widget1)
layout.addWidget(widget2)
scroll = QScrollArea()
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll.setWidgetResizable(False)
scroll.setWidget(container)
vLayout = QVBoxLayout(self)
vLayout.addWidget(scroll)
Using Qt Designer is certainly very useful when experimenting with the layouts of a complex application. However, the code it generates is usually quite verbose compared with what you can achieve when coding by hand. For long-term maintainability, though, using Qt Designer seems the best option.

PySide QGraphicsView size

I have 2 issues with QGraphicsView.
I can't get the size of the QGraphicsView object. All methods I'm using is giving me values I wouldn't expect.
If I print out the mouse's position on the area's lower-right corner (scrollbars included), I get a random 400 value. After setting sceneRect to 500 I was expecting to get that back.
from PySide import QtGui, QtCore
class View(QtGui.QGraphicsView):
def __init__(self, parent = None):
super(View, self).__init__(parent)
self.setScene( QtGui.QGraphicsScene(self) )
self.setSceneRect( 0, 0, 500, 500 )
print self.viewport().width() # outputs 96
print self.width() # outputs 100
print self.rect() # outputs QRect(0, 0, 100, 30)
print self.size() # outputs QSize(100, 30)
def mouseMoveEvent(self, event):
print event.pos().toTuple() # prints (413, 423) at lower-right corner
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.resize(500, 500)
self.view = View(self)
hLayout = QtGui.QHBoxLayout()
hLayout.addWidget(self.view)
buttonsLayout = QtGui.QVBoxLayout()
buttonsLayout.setSpacing(0)
for i in range(10):
newButton = QtGui.QPushButton()
buttonsLayout.addWidget(newButton)
hLayout.addLayout(buttonsLayout)
self.tempButton = QtGui.QPushButton()
mainLayout = QtGui.QVBoxLayout()
mainLayout.addLayout(hLayout)
mainLayout.addWidget(self.tempButton)
self.setLayout(mainLayout)
def run(self):
self.show()
win = MainWindow()
win.run()
Thank you!
Regarding your first issue, I believe you are not getting the sizes you are expecting for two reasons:
You are not explicitly setting the size of the QGraphicsView widget to 500, but the QGraphicsScene instead.
You are fetching the sizes too early in the construction of your application, before the layout of the MainWindow has been properly painted.
Regarding your second issue, depending of what is desired, it is possible to use the method mapFromScene to get the position of the mouse event in regard to the QGraphicsScene instead of the QGraphicsView widget.
More specifically, this can be achieve in your code by:
Setting the size of the QGraphicsView widget with setFixedSize ;
Moving the "size-fetching" calls in the run method, after the MainWindow has been painted ;
Adding a mapToScene transformation on the mouseMoveEvent coordinate.
Below is the code that was modified accordingly to the points listed above:
from PySide import QtGui, QtCore
import sys
class View(QtGui.QGraphicsView):
def __init__(self, parent = None):
super(View, self).__init__(parent)
self.setScene(QtGui.QGraphicsScene(self) )
self.setSceneRect( 0, 0, 1000, 1000 )
self.setFixedSize(500, 500)
def mouseMoveEvent(self, event):
print
print self.mapToScene(event.pos()).toTuple()
# prints (1000, 1000) at lower-right corner
print event.pos().toTuple()
# prints (500, 500) at lower-right corner
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.view = View(self)
hLayout = QtGui.QHBoxLayout()
hLayout.addWidget(self.view)
buttonsLayout = QtGui.QVBoxLayout()
buttonsLayout.setSpacing(0)
for i in range(10):
newButton = QtGui.QPushButton()
buttonsLayout.addWidget(newButton)
hLayout.addLayout(buttonsLayout)
self.tempButton = QtGui.QPushButton()
mainLayout = QtGui.QVBoxLayout()
mainLayout.addLayout(hLayout)
mainLayout.addWidget(self.tempButton)
self.setLayout(mainLayout)
def run(self):
self.show()
print
print self.view.viewport().width() # outputs 485
print self.view.width() # outputs 500
print self.view.rect() # outputs QRect(0, 0, 500, 500)
print self.view.size() # outputs QSize(500, 500)
print self.view.sceneRect() #outputs QRect(0, 0, 1000, 1000)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
win = MainWindow()
win.run()
sys.exit(app.exec_())
With the code above, the value returned for the size of QGraphicView is 500x500, while it is 1000x1000 for the QGraphicsScene, as expected.

Categories

Resources