How do you make a QGraphicsScene with a specified size and a QGraphicsView to monitor that scene with the same size?
This sounds like a stupid question but consider the following test code [1]
import sys
from PyQt4 import QtGui, QtCore
class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.scene = QtGui.QGraphicsScene(0, 0, 200, 200, self)
self.view = QtGui.QGraphicsView(self.scene, self)
#self.view.setMaximumSize(200, 200)
self.setCentralWidget(self.view)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
When I run this I get a little window with the QGraphicsView and no scroll bars. If I shrink the window scroll bars appear because I've made the view smaller than the specified size of the scene. If I enlarge the window the bars go away and the view resizes with the window. All of this makes sense.
Now I run the same code again but with the commented line (preceded by #) un-commented. The Window appears with the view inside, but the view has scroll bars. When I shrink the window the view also shrinks, but when I enlarge the window the view only enlarges to a certain size. This is not surprising. What is surprising is that with the view at it's maximum size, the scroll bars are still there. I don't understand this because the maximum size of the view is explicitly matched to the size of the scene in the code.
Why does this happen?
I am aware that other questions have explained how to force the scene and view to fit together but I want to understand what size view and scene I have actually made, hence my attempt to use explicitly specified sizes.
[1] I'm using PyQt, C++ users just read "self" as "this" and "import" as "#include"
EDIT: The accepted answer below is absolutely correct. I would just like to add for others who read this that if your view is in a layout you have to account for the layout margins as well. These can be explicitly set in the QtDesigner etc.
Ok, I worked it out for you!
The QGraphicsView (which subclasses QFrame) has a border. If you add the below line to your init method:
self.view.setFrameShape(QtGui.QFrame.NoFrame)
then you remove the 1px frame and it works as you expect!
Related
I am working on a GUI that at one point should display a long horizontal ROI of a camera feed. For that I am writing a widget consisting of an ImageView that is using a ViewBox and an ImageItem and then add that widget to a constrained space on a layout of the GUI. My problem is that the image from the camera is not visible upon opening the window but 'hidden' outside of the field of view of the widget (a little bit down). Upon resizing the window, it appears centered.
This is how it looks after resizing before that it is where the arrow indicates
This is a minimal version of the display widget in question:
class CameraViewerWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.viewport = GraphicsLayoutWidget()
self.view = self.viewport.addViewBox(lockAspect=False, enableMenu=True)
self.img = pg.ImageItem()
self.view.addItem(self.img)
self.imv = pg.ImageView(view=self.view, imageItem=self.img)
layout = self.layout()
layout.addWidget(self.imv)
def update_image(self, image, auto_range=True, auto_histogram_range=False):
self.imv.setImage(image, autoRange=auto_range, autoHistogramRange=auto_histogram_range)
It is then added to the GUI via addWidget() and update_image() is called repeatedly using a QTimer.
Obviously the auto_range argument does not help here. I also tried to use the setLimits() and setRange() methods of the ViewBox but that also did not change the situation. I am suspecting that as I add this Widget to a contrained size on the layout of the main GUI (maximumSize = 100) the ImageView does not know how much space it has but I am not sure how to test that and a bit confused by the different coordinate systems used here. Did anyone encounter similar issues or is able to see my error here?
Consider the following example,
import sys
from PyQt5 import QtCore, QtWidgets
class scrolldemo(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(scrolldemo, self).__init__(parent)
widget = QtWidgets.QWidget()
widget.setFixedSize(QtCore.QSize(500, 600))
scroll = QtWidgets.QScrollArea()
scroll.setWidget(widget)
self.setCentralWidget(scroll)
self.setWindowTitle("Scroll demo")
def main():
app = QtWidgets.QApplication(sys.argv)
ex = scrolldemo()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The widget has a size that is slightly too large vertically, which adds a vertical scroll bar. However, the vertical scroll bar takes up space that was taken up by the widget itself. This requires a horizontal scroll bar too, making the window look like this:
I'm okay with the vertical scroll bar, but horizontal scroll bars are ungainly. Observe that the extra space needed for the horizontal scroll bar is exactly the width of the vertical bar.
This can be worked around by overriding the sizeHint method to include the vertical scroll bar's width,
class ScrollAreaWithVerticalBar(QtWidgets.QScrollArea):
def sizeHint(self):
hint = super().sizeHint()
bar_width = self.verticalScrollBar().sizeHint().width()
return QtCore.QSize(hint.width() + bar_width, hint.height())
This produces a slightly wider QScrollArea that no longer needs the ungainly horizontal scroll bar. I got this idea by looking at a closed Qt bug report related to this problem. However, that bug report was closed as "out of scope", which makes me think that this isn't a bug at all and I'm using QScrollArea incorrectly, and my incorrect use also is requiring a workaround.
So what should I be doing instead?
So I'm trying to get PyQtGraph to plot a graph in a GUI that I am developing (you can check it here https://github.com/allg18/MuonAquisitionTool , I am using Python3 and PyQt4).
However, when I add the plotwidget to the groupbox in the MainWindow, it always stays tiny. The plotgraph didn't expand to ocuppy the whole of the groupbox.
class MainWindow2(QMainWindow, Ui_MainWindow):
def __init__(self):
super(MainWindow2, self).__init__()
# Set up the user interface from Designer.
self.setupUi(self)
"""Booting up the Graphics"""
self.preview_plot = pg.PlotWidget(self.groupBox_2)
self.preview_plot.adjustSize()
self.data1 = numpy.random.normal(size=300)
self.curve1 = self.preview_plot.plot(self.data1)
In which the groupBox_2 is the place where I want to put it within the MainWindow.
As you can see I also tried to put a "adjust_size" there. I also tried:
self.preview_plot.resize(500, 500)
And this actually worked, it was the only way I got a size different from the one in the image
UPDATE:
I tried the adding the following line, but this did again not change the size of the graph widget...
self.graph.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
Don't try to position the widgets yourself, this is hard and error prone. Qt has a layout mechanism to do this for you. You can read about it here
I cannot fix your example because it is not complete, I don't have the QMainWindow and Ui_MainWindow classes (in the future please make an MCVE). However, there is probably some groupbox layout to which you can add your plot widget with addWidget.
I have a line editor that inherits from QTextEdit, and I am using it to edit view items that show rich text. The second parameter for QTextEdit.setAlignment is `QtAligntment' and the docs say:
Valid alignments are Qt.AlignLeft, Qt.AlignRight, Qt.AlignJustify and
Qt.AlignCenter (which centers horizontally).
That is, there is no native support for vertical alignment. Is there an indirect way to vertically center the text in QTextEdit?
Related link
Center the Text of QTextEdit horizontally and vertically : Unfortunately, the accepted answer uses QLineEdit which won't work for me.
A clue?
At the following I found a clue about how to do it in C++/Qt. I am almost able to follow it, but not quite, as it is for c++:
http://www.qtcentre.org/threads/26003-Vertical-centering-of-a-QTextEdit
I will hack at it on my own for a couple of days and try to answer it myself, but wanted to post this now in case someone has already cracked it already or done it in a different/better way.
For a single-line edit centred vertically, you just need to calculate a correct fixed height.
Using the example delegate from your previous question, it can be achieved like this:
class RichTextLineEdit(QtGui.QTextEdit):
def __init__(self, parent=None):
...
margin = 1
self.document().setDocumentMargin(margin)
fontMetrics = QtGui.QFontMetrics(self.font())
height = fontMetrics.height() + (margin + self.frameWidth()) * 2
self.setFixedHeight(height)
(NB: the reimplemented sizeHint and minimumSizeHint methods are probably redundant in the original example).
While the accepted answer works for default font size, it breaks when I change the font size or vertical margins (see comments). The text line edit class below centers the text vertically, for all font sizes and vertical margins that I've tested.
It sets up the editor using QTextDocument which is then assigned to the QTextEdit instance. QTextDocuments provide the back-end containers for QTextEdits anyway, and have built-in functionality for handling font sizes and margins, and give an additional layer of control over the editor.
In practice, I found using QTextDocument let me solve the problem in a more intuitive way without having, you don't have to delve into the nitty-gritty mechanics of frame widths, font metrics, and all that, which we did when working solely using native QTextEdit methods.
Note it uses setViewportMargins() instead of setContentMargins() (which is what you might expect it to use) because the latter is for setting margins for something that is inserted into a layout. The following editor is a standalone widget, not put into any layout, so setContentMargins() won't do anything.
import sys
from PySide import QtGui, QtCore
class TextLineEdit(QtGui.QTextEdit):
topMarginCorrection = -4 #not sure why needed
returnPressed = QtCore.Signal()
def __init__(self, fontSize = 10, verticalMargin = 2, parent = None):
QtGui.QTextEdit.__init__(self, parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.setLineWrapMode(QtGui.QTextEdit.NoWrap)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setFontPointSize(fontSize)
self.setViewportMargins(-verticalMargin, self.topMarginCorrection , 0, 0) #left, top, right, bottom
#Set up document with appropriate margins and font
document = QtGui.QTextDocument()
currentFont = self.currentFont()
currentFont.setPointSize(fontSize)
document.setDefaultFont(currentFont)
document.setDocumentMargin(verticalMargin)
self.setFixedHeight(document.size().height())
self.setDocument(document)
def keyPressEvent(self, event):
'''stops retun from returning newline'''
if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
self.returnPressed.emit()
event.accept()
else:
QtGui.QTextEdit.keyPressEvent(self, event)
def main():
app = QtGui.QApplication(sys.argv)
myLine = TextLineEdit(fontSize = 15, verticalMargin = 8)
myLine.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
In the attached example script, why won't the MyDialog instance show properly when I set MyDialog's parent to self on line 20 instead of leaving it blank? First I thought the shortcut had stopped working somehow, but obviously that's not the case.
In this case it doesn't really make any difference whether the parent is set, but in my real case I need the parent to be set.
Am I missing something obvious here?
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyDialog(QDialog):
def __init__(self, parent=None):
QDialog.__init__(self, parent)
self.setWindowFlags(Qt.FramelessWindowHint)
self.setFocusPolicy(Qt.StrongFocus)
label = QLabel(self)
label.setText("World")
hbox = QHBoxLayout()
hbox.addWidget(label)
self.setLayout(hbox)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.my_dialog = MyDialog()
#self.my_dialog = MyDialog(self)
label = QLabel(self)
label.setText("Hello")
self.setCentralWidget(label)
shortcut = QShortcut(QKeySequence(Qt.Key_Tab), self, self.show_my_dialog)
shortcut.setContext(Qt.ApplicationShortcut)
self.show()
def show_my_dialog(self):
md = self.my_dialog
if md.isVisible():
md.hide()
print 'hide'
else:
md.show()
print 'show'
def main():
app = QApplication([])
main_window = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
On my machine (Kubuntu 11.10) it's working.
If Dialog's parent is None - it opens another frameless window, and i have two entries in taskbar. If i close the main window, dialog remains.
If you are using Windows - the behavior you described might be related to it. Maybe the window is shown, it's just behind other windows?
If Dialog's parent is the main window - the frameless dialog is shown inside the main window - in the top left corner near the label.
You might be interested in this info:
A dialog window is a top-level window mostly used for short-term tasks
and brief communications with the user. QDialogs may be modal or
modeless. QDialogs can provide a return value, and they can have
default buttons. QDialogs can also have a QSizeGrip in their
lower-right corner, using setSizeGripEnabled().
Note that QDialog (an
any other widget that has type Qt::Dialog) uses the parent widget
slightly differently from other classes in Qt. A dialog is always a
top-level widget, but if it has a parent, its default location is
centered on top of the parent's top-level widget (if it is not
top-level itself). It will also share the parent's taskbar entry.
Use
the overload of the QWidget::setParent() function to change the
ownership of a QDialog widget. This function allows you to explicitly
set the window flags of the reparented widget; using the overloaded
function will clear the window flags specifying the window-system
properties for the widget (in particular it will reset the Qt::Dialog
flag).