Draggable window with pyqt4 - python

just a simple question: I'm using pyqt4 to render a simple window. Here's the code, I post the whole thing so it easier to explain.
from PyQt4 import QtGui, QtCore, Qt
import time
import math
class FenixGui(QtGui.QWidget):
def __init__(self):
super(FenixGui, self).__init__()
# setting layout type
hboxlayout = QtGui.QHBoxLayout(self)
self.setLayout(hboxlayout)
# hiding title bar
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
# setting window size and position
self.setGeometry(200, 200, 862, 560)
self.setAttribute(Qt.Qt.WA_TranslucentBackground)
self.setAutoFillBackground(False)
# creating background window label
backgroundpixmap = QtGui.QPixmap("fenixbackground.png")
self.background = QtGui.QLabel(self)
self.background.setPixmap(backgroundpixmap)
self.background.setGeometry(0, 0, 862, 560)
# fenix logo
logopixmap = QtGui.QPixmap("fenixlogo.png")
self.logo = QtGui.QLabel(self)
self.logo.setPixmap(logopixmap)
self.logo.setGeometry(100, 100, 400, 150)
def main():
app = QtGui.QApplication([])
exm = FenixGui()
exm.show()
app.exec_()
if __name__ == '__main__':
main()
Now, you see that I put a background label in my window. I would like that the window could be dragged around the screen by dragging this label. I mean: you click on the label, you drag the label, and the whole window comes around the screen. Is this possible? I accept non-elegant ways as well, because as you can see I hid the title bar so it would be impossible to drag the window if I don't make it draggable via the background label.
Hope I explained my problem properly
Thank you very much!!
Matteo Monti

You can override mousePressEvent() and mouseMoveEvent() to get the location of the mouse cursor and move your widget to that location. mousePressEvent will give you the offset from the cursor position to the top left corner of your widget, and then you can calculate what the new position of the top left corner should be. You can add these methods to your FenixGui class.
def mousePressEvent(self, event):
self.offset = event.pos()
def mouseMoveEvent(self, event):
x=event.globalX()
y=event.globalY()
x_w = self.offset.x()
y_w = self.offset.y()
self.move(x-x_w, y-y_w)

Related

setting a cursor in PyQt5 label widget

I am making an image acquisition program using a webcam in PyQt. Whenever I click on the image, which is inside a label widget, i need to put an additional fixed cursor at that position (to have a reference point for e.g). I created a cursor object, set a shape and position (obtained from the clicked position). yet I dont see an additional cursor being created at the clicked position i.e. Qpoint
below is the code snippet:
def eventFilter(self, source, event):
if event.type()==QtCore.QEvent.MouseButtonPress:
self.new_cursor=QtGui.QCursor() # the additional cursori want to place
self.new_cursor.setShape(self,Qt.PointingHandCursor) # setting shape
self.cursor_clicked=event.pos() # getting position from the click
self.cursor_clicked=self.label.mapFromParent(event.pos()) #mapping to widget coords.
self.cursor_x=self.cursor_clicked.x()
self.cursor_y=self.cursor_clicked.y()
self.new_cursor.setPos(self.cursor_x,self.cursor_y)
self.setCursor(self.new_cursor)
return QtWidgets.QWidget.eventFilter(self,source,event)
QCursor is not a "static image", but is an "abstract" object related to the mouse cursor, so there's no use of it for your purpose.
What you're looking for is drawing on the existing image or the widget that shows it.
Since you probably want to leave the image unchanged, the second option is what you're looking for.
The idea is that you call the base class implementation of the paintEvent method and then draw over it.
Painting a crosshair by hand is not that hard using simple lines, but you'll need to draw an extra border around the cross using a different color to ensure its visibility even on lighter or darker backgrounds, which makes it unnecessary long; in this example I'm using the cursor image Qt uses in the CursorShape enum documentation, but you can use whatever image you want, as soon as its center is exactly at the center of it (hint: use a square image with odd sized width/height).
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout(self)
self.label = QtWidgets.QLabel()
layout.addWidget(self.label)
self.label.setPixmap(QtGui.QPixmap('myimage.png'))
self.label.installEventFilter(self)
self.cursorPos = None
# I'm using the crosshair cursor as shown at
# https://doc.qt.io/qt-5/qt.html#CursorShape-enum
self.cursorPixmap = QtGui.QPixmap('cursor-cross.png')
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
# set the current position and schedule a repaint of the label
self.cursorPos = event.pos()
self.label.update()
elif event.type() == QtCore.QEvent.Paint:
# intercept the paintEvent of the label and call the base
# implementation to actually draw its contents
self.label.paintEvent(event)
if self.cursorPos is not None:
# if the cursor position has been set, draw it
qp = QtGui.QPainter(self.label)
# translate the painter at the cursor position
qp.translate(self.cursorPos)
# paint the pixmap at an offset based on its center
qp.drawPixmap(-self.cursorPixmap.rect().center(), self.cursorPixmap)
return True
return super().eventFilter(source, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Besides that, another approach is to use the Graphics View Framework, add the pixmap to the scene and add/move another pixmap for the cursor when the user clicks on the image. Dealing with QGraphicsViews, QGraphicsScenes and their items is a bit more complex, but if you're going to need some more advanced level of interaction with an image, it usually is the better path to take.
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout(self)
self.view = QtWidgets.QGraphicsView()
layout.addWidget(self.view)
# remove any border around the view
self.view.setFrameShape(0)
self.scene = QtWidgets.QGraphicsScene()
self.view.setScene(self.scene)
pixmap = QtGui.QPixmap('myimage.png')
# adapt the view's size to that of the pixmap
self.view.setFixedSize(pixmap.size())
# add a pixmap to a scene, which returns a QGraphicsPixmapItem
self.pixmapItem = self.scene.addPixmap(pixmap)
self.crossHairItem = None
self.view.installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
if not self.crossHairItem:
# as above, get a QGraphicsPixmapItem for the crosshair cursor
pixmap = QtGui.QPixmap('cursor-cross.png')
self.crossHairItem = self.scene.addPixmap(pixmap)
# set an offset of the item, so that its position is always
# based on the center of the pixmap
self.crossHairItem.setOffset(-pixmap.rect().center())
self.crossHairItem.setPos(self.view.mapToScene(event.pos()))
return super().eventFilter(source, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Both methods behave in the same way for the user, and as you can see they look exactly identical.

How can I make a PyQt widget resizable by dragging?

I have a QScrollArea containing a widget with a QVBoxLayout. Inside this layout are several other widgets. I want the user to be able to drag the lower borders of those widgets to resize them in the vertical direction. When they are resized, I don't want them to "steal" size from the other widgets in the scrolling area; instead I want the entire scrolled "page" to change its size. So if you enlarge one of the widgets, it should push the other widgets down (out of the viewport of the scroll area); if you shrink it, it should pull the other widgets up. Dragging the border of one widget should not change the size of any of the other widgets in the vertical scroll; it should just move them.
I began by using a QSplitter. If I use that, I can drag to change the size of a widget, but there doesn't seem to be a way to get it to "push/pull" the others as I described above, rather than growing/shrinking them. But I can't find any other way to give a widget a draggable handle that will allow me to change its size. How can I accomplish this?
Here is a simple example of what I'm doing. (In this example I've commented out the splitter, but if you uncomment it you can see what happens with that version.)
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.Qsci import QsciScintilla, QsciLexerPython
class SimplePythonEditor(QsciScintilla):
def __init__(self, parent=None):
super(SimplePythonEditor, self).__init__(parent)
self.setMinimumHeight(50)
class Chunk(QFrame):
def __init__(self, parent=None):
super(Chunk, self).__init__(parent)
layout = QVBoxLayout(self)
sash = QSplitter(self)
layout.addWidget(sash)
sash.setOrientation(Qt.Vertical)
editor = self.editor = SimplePythonEditor()
output = self.output = SimplePythonEditor()
output.setReadOnly(True)
sash.addWidget(editor)
sash.addWidget(output)
self.setLayout(layout)
print(self.sizePolicy())
class Widget(QWidget):
def __init__(self, parent= None):
global inout
super(Widget, self).__init__()
#Container Widget
widget = QWidget()
#Layout of Container Widget
layout = QVBoxLayout(self)
#sash = QSplitter(self)
#layout.addWidget(sash)
#sash.setOrientation(Qt.Vertical)
for num in range(5):
editor = SimplePythonEditor()
editor.setText("Some stuff {}".format(num))
layout.addWidget(editor)
#sash.addWidget(editor)
widget.setLayout(layout)
#Scroll Area Properties
scroll = QScrollArea()
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll.setWidgetResizable(True)
scroll.setWidget(widget)
scroll.setMaximumHeight(500)
#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_()
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.setWindowTitle("MainWindow")
MainWindow.resize(500, 500)
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
MainWindow.setCentralWidget(self.centralwidget)
QMetaObject.connectSlotsByName(MainWindow)
class Ewindow(QMainWindow,QApplication):
"""docstring for App"""
resized = pyqtSignal()
def __init__(self,parent):
super(Ewindow,self).__init__(parent=parent)
self.setGeometry(500, 500, 800,800)
self.setWindowTitle('Mocker')
self.setWindowIcon(QIcon('icon.png'))
self.setAttribute(Qt.WA_DeleteOnClose)
ui2 = Ui_MainWindow()
ui2.setupUi(self)
self.resized.connect(self.readjust)
def resizeEvent(self, event):
self.resized.emit()
return super(Ewindow, self).resizeEvent(event)
def readjust(self):
self.examForm.move(self.width()-self.examForm.width(),0)
self.btn_skip.move(self.width()-self.btn_skip.width(),self.height()-100)
self.btn_next.move(self.btn_showAnswers.x()+self.btn_showAnswers.width(),self.height()-100)
self.btn_prev.move(0,self.height()-100)
self.btn_showAnswers.move(self.btn_prev.x()+self.btn_prev.width(),self.height()-100)
self.btn_home.move(self.width()-200,self.height()-150)
self.lbscreen1.resize(self.width()-self.examForm.width(),self.height()-200)
self.examForm.resize(200,self.height()-150)
self.btn_skip.resize(self.examForm.width(),100)
self.btn_next.resize(self.btn_prev.width(),100)
self.btn_prev.resize(self.width()*0.25,100)
self.btn_showAnswers.resize(self.btn_prev.width(),100)
self.btn_home.resize(200,50)
here is an example code of a resizable window it moves and stretches widgets as you resize the window. The idea is to keep widget coordinates and sizes relative to each other.
so i had to make a class Ui_MainWindow and set it for my window class ui2.setupUi(self) and also declare the resized = pyqtSignal() which i'd be using to run the readjust function which resets size and coordinates of the widgets like so self.resized.connect(self.readjust).
i hope this helps!

Qt.ScrollBarAsNeeded not showing scrollbar when it's actually needed

I'm implementing a python application using PyQt5 and I encountered some problems when making use of a QScrollArea. This is the layout of my application:
It's composed of 2 QScrollArea (left and right pane) and a QMdiArea (center widget) arranged into a QHBoxLayout. When I expand the widgets on the left pane by clicking on the controls, and the height of the QWidget of the QScrollArea is bigger than then height of the QScrollArea itself, the scrollbar appears (as expected), but it's overlapping the content of the QScrollArea. To fix this problem I reimplemented the resizeEvent adding the necessary space for the scrollbar (till this point everything works.
Now, when I manually resize the Main Window, the left Pane gets more space and the scrollbar should disappear (but it doesn't) and it overlaps the widgets of the pane:
I also tried to manually toggle the visibility of the scrollbar (when the resizeEvent is received): when I do this, I can successfully hide the scrollbar but then I can't show it again (not matter if I call setVisible(True) on the scrollbar). This results in the space for the scrollbar being added, but the scrollbar is missing and the content of the pane is not scrollable:
Here is the implementation of the pane widget:
class Pane(QScrollArea):
MinWidth = 186
def __init__(self, alignment=0, parent=None):
super().__init__(parent)
self.mainWidget = QWidget(self)
self.mainLayout = QVBoxLayout(self.mainWidget)
self.mainLayout.setAlignment(alignment)
self.mainLayout.setContentsMargins(0, 0, 0, 0)
self.mainLayout.setSpacing(0)
self.setContentsMargins(0, 0, 0, 0)
self.setFrameStyle(QFrame.NoFrame)
self.setFixedWidth(Pane.MinWidth)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Ignored)
self.setWidgetResizable(True)
self.setWidget(self.mainWidget)
def resizeEvent(self, resizeEvent):
if self.viewport().height() < self.widget().height():
self.setFixedWidth(Pane.MinWidth + 18)
# THIS DOESN'T WORK
#self.verticalScrollBar().show()
else:
self.setFixedWidth(Pane.MinWidth)
#self.verticalScrollBar().hide()
def addWidget(self, widget):
self.mainLayout.addWidget(widget)
def removeWidget(self, widget):
self.mainLayout.removeWidget(widget)
def update(self, *__args):
for item in itemsInLayout(self.mainLayout):
item.widget().update()
super().update(*__args)
What I want to achieve is pretty simple (but practically it seems not as simple): I would like to dynamically show the vertical scrollbar on my left/right pane widgets only when it's needed, and add the necessary space for the scrollbar so it doesn't overlap the widgets in the QScrollArea.
Before someone asks, I already tried to do something like this:
def resizeEvent(self, resizeEvent):
if self.viewport().height() < self.widget().height():
self.setFixedWidth(Pane.MinWidth + 18)
scrollbar = self.verticalScrollbar()
scrollbar.setVisible(True)
self.setVerticalScrollBar(scrollbar) ## APP CRASH
else:
self.setFixedWidth(Pane.MinWidth)
#self.verticalScrollBar().hide()
which results in my application to crash.
I hope that someone already faced this issue and is able to help me.
EDIT: I'm using PyQt5.5 compiled against Qt5.5 under OSX Yosemite 10.10.4 using clang.
Everything seems to work as expected for me without any need for workarounds. However, I strongly suspect there are additional constraints in your real code that you have not revealed in your question.
UPDATE
Below is a simple example that resizes the scrollareas when the scrollbars are shown/hidden:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
widget = QtWidgets.QWidget(self)
layout = QtWidgets.QHBoxLayout(widget)
self.mdi = QtWidgets.QMdiArea(self)
self.leftScroll = Pane(
QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft, self)
self.rightScroll = Pane(
QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft, self)
layout.addWidget(self.leftScroll)
layout.addWidget(self.mdi)
layout.addWidget(self.rightScroll)
self.setCentralWidget(widget)
for scroll in self.leftScroll, self.rightScroll:
for index in range(4):
widget = QtWidgets.QTextEdit()
widget.setText('one two three four five')
scroll.addWidget(widget)
class Pane(QtWidgets.QScrollArea):
MinWidth = 186
def __init__(self, alignment=0, parent=None):
super().__init__(parent)
self.mainWidget = QtWidgets.QWidget(self)
self.mainLayout = QtWidgets.QVBoxLayout(self.mainWidget)
self.mainLayout.setAlignment(alignment)
self.mainLayout.setContentsMargins(0, 0, 0, 0)
self.mainLayout.setSpacing(0)
self.setContentsMargins(0, 0, 0, 0)
self.setFrameStyle(QtWidgets.QFrame.NoFrame)
self.setFixedWidth(Pane.MinWidth)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
self.setSizePolicy(QtWidgets.QSizePolicy.Maximum,
QtWidgets.QSizePolicy.Ignored)
self.setWidgetResizable(True)
self.setWidget(self.mainWidget)
self.verticalScrollBar().installEventFilter(self)
def addWidget(self, widget):
self.mainLayout.addWidget(widget)
def removeWidget(self, widget):
self.mainLayout.removeWidget(widget)
def eventFilter(self, source, event):
if isinstance(source, QtWidgets.QScrollBar):
if event.type() == QtCore.QEvent.Show:
self.setFixedWidth(Pane.MinWidth + source.width())
elif event.type() == QtCore.QEvent.Hide:
self.setFixedWidth(Pane.MinWidth)
return super(Pane, self).eventFilter(source, event)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 800, 300)
window.show()
sys.exit(app.exec_())

How to add a second pixmap to an abstract button when pushed in PySide/PyQt

I'm very new to PySide/PyQt environment. I'm trying to make a menu of buttons on top and assign a task to each so that when they are clicked a function draws a painting on the central window. But I also want to make the button change when they are clicked.
I think this might be an straighforward problem to solve if I use QPushButton, but my buttons are images and I'm using the method suggested HERE and use QAbstractButton to create them.
It is mentioned there that
You can add second pixmap and draw it only when the mouse pointer is
hover over button.
And I'm trying to do exactly that. My question is this:
what are possible ways to achieve this? Are the same methods in QPushButtons applicable here? If so, are there any examples of it somewhere?
Here is a snippet of my code:
import sys
from PySide import QtGui, QtCore
BACKGROUND_COLOR = '#808080'
ICON_PATH_ACTIVE = 'icons/activ'
ICON_PATH_PASSIVE = 'icons/pasiv'
class MainWindow(QtGui.QMainWindow):
def __init__(self, app=None):
super(MainWindow, self).__init__()
self.initUI()
def initUI(self):
dockwidget = QtGui.QWidget()
self.setGeometry(200, 200, 400, 300)
hbox = QtGui.QHBoxLayout()
1_button = PicButton(QtGui.QPixmap("icons/pasiv/1.png"))
2_button = PicButton(QtGui.QPixmap("icons/pasiv/2.png"))
3_button = PicButton(QtGui.QPixmap("icons/pasiv/3.png"))
hbox.addWidget(1_button)
hbox.addWidget(2_button)
hbox.addWidget(3_button)
vbox = QtGui.QVBoxLayout()
vbox.addLayout(hbox)
vbox.setAlignment(hbox, QtCore.Qt.AlignTop)
dockwidget.setLayout(vbox)
self.setCentralWidget(dockwidget)
class PicButton(QtGui.QAbstractButton):
def __init__(self, pixmap, parent=None):
super(PicButton, self).__init__(parent)
self.pixmap = pixmap
self.setFixedSize(100, 100)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.drawPixmap(event.rect(), self.pixmap)
def main():
app = QtGui.QApplication(sys.argv)
central = MainWindow()
central.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Thank you.
Use a regular QPushButton with an icon.
iplay = QtGui.QIcon("path/play_icon.png")
ipause = QtGui.QIcon("path/pause_icon.png")
btn = QtGui.QPushButton(ipause, "", None)
def toggle_play():
if btn.icon() == ipause:
btn.setIcon(iplay)
# Do Pause Action
else:
btn.setIcon(ipause)
# Do Play Action
btn.clicked.connect(toggle_play)
btn.show()
If you want hover functionality then you will have to subclass the QPushButton
class MyButton(QtGui.QPushButton):
custom_click_signal = QtCore.Signal()
def enterEvent(self, event):
super().enterEvent(event)
# Change icon hove image here
def leaveEvent(self, event):
super().leaveEvent(event)
# Change icon back to original image here.
def mousePressEvent(self, event):
super().mousePressEvent(event)
self.custom_click_signal.emit()
# connect to signal btn.custom_click_signal.connect(method)
Icons are probably the easiest way instead of manually managing the paint event. There are also mousePressEvent and mouseReleaseEvents if you want the icon to change for someone holding the button down.

Moving a QGraphicsItem around a central point in PyQt4

I'm using Python 2.7 and PyQt4. I am trying to have a half-circle object that is a QGraphicsItem. I want to be able to move it using the mouse, by clicking and dragging. I can create the object and move it around with the mouse by setting the flag ItemIsMovable. Now the half-circle moves around freely but I want it to move just around the fixed central point. It is difficult to describe, but it should be something similar to a dial. How can I accomplish this?
you can use QGraphicsItem::mouseMoveEvent event to track item's movements within the scene and correct its position once it's moved off the restricted area. Pls, check if an example below would work for you:
import sys
from PyQt4 import QtGui, QtCore
class TestEclipseItem(QtGui.QGraphicsEllipseItem):
def __init__(self, parent=None):
QtGui.QGraphicsPixmapItem.__init__(self, parent)
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
# set move restriction rect for the item
self.move_restrict_rect = QtCore.QRectF(20, 20, 200, 200)
# set item's rectangle
self.setRect(QtCore.QRectF(50, 50, 50, 50))
def mouseMoveEvent(self, event):
# check of mouse moved within the restricted area for the item
if self.move_restrict_rect.contains(event.scenePos()):
QtGui.QGraphicsEllipseItem.mouseMoveEvent(self, event)
class MainForm(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
scene = QtGui.QGraphicsScene(-50, -50, 600, 600)
ellipseItem = TestEclipseItem()
scene.addItem(ellipseItem)
view = QtGui.QGraphicsView()
view.setScene(scene)
view.setGeometry(QtCore.QRect(0, 0, 400, 200))
self.setCentralWidget(view)
def main():
app = QtGui.QApplication(sys.argv)
form = MainForm()
form.show()
app.exec_()
if __name__ == '__main__':
main()
hope this helps, regards

Categories

Resources