How to make my title less Window drag-able in PyQt5? - python

I want to build a window which has no title bar, so i do. But it is not any more draggable. You cannot make my window move from here to there.
I know it is because of me, removing the title bar, but how to fix it?
This is my code:
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QWidget
import sys
def window():
app = QApplication(sys.argv)
win = QMainWindow()
win.setGeometry(300, 300, 300, 300)
win.setWindowTitle("Test")
win.setWindowFlags(QtCore.Qt.FramelessWindowHint)
label = QLabel(win)
label.setText("Hello world")
win.show()
sys.exit(app.exec_())
window()
Any help will be appreciated. Please help me with this...

You need to reimplement the mousePress and mouseMove methods of the widget (mouseRelease is technically not mandatory, but is actually required for consistency, as the release event has to be correctly intercepted by Qt to avoid any confusion). The former will get the current cursor position relative to the geometry (self.offset), while the latter will compute the new "window" position by adding the new position to the current one and subtracting the offset.
I would also suggest you to use a QWidget instead of a QMainWindow. While QMainWindow implementation is very similar to that of QWidgets, subclassing a QMainWindow for your purpose might be a bit harder, as it's widget more complex than it seems.
If you only need a QMainWindow to get a status bar, just add a new one to the widget layout; if you also need a menubar, add it to the widget's layout using setMenuBar.
class FramelessWidget(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Test")
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.label = QLabel("Hello world", self)
self.offset = None
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.offset = event.pos()
else:
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.offset is not None and event.buttons() == QtCore.Qt.LeftButton:
self.move(self.pos() + event.pos() - self.offset)
else:
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.offset = None
super().mouseReleaseEvent(event)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = FramelessWidget()
win.setGeometry(300, 300, 300, 300)
win.show()
sys.exit(app.exec_())

Related

How to receive hover events for child widgets?

I have a QWidget containing another (child) widget for which I'd like to process hoverEnterEvent and hoverLeaveEvent. The documentation mentions that
Mouse events occur when a mouse cursor is moved into, out of, or within a widget, and if the widget has the Qt::WA_Hover attribute.
So I tried to receive the hover events by setting this attribute and implementing the corresponding event handlers:
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout
class TestWidget(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout()
layout.addWidget(TestLabel('Test 1'))
layout.addWidget(TestLabel('Test 2'))
self.setLayout(layout)
self.setAttribute(Qt.WA_Hover)
class TestLabel(QLabel):
def __init__(self, text):
super().__init__(text)
self.setAttribute(Qt.WA_Hover)
def hoverEnterEvent(self, event): # this is never invoked
print(f'{self.text()} hover enter')
def hoverLeaveEvent(self, event): # this is never invoked
print(f'{self.text()} hover leave')
def mousePressEvent(self, event):
print(f'{self.text()} mouse press')
app = QApplication([])
window = TestWidget()
window.show()
sys.exit(app.exec_())
However it doesn't seem to work, no hover events are received. The mousePressEvent on the other hand does work.
In addition I tried also the following things:
Set self.setMouseTracking(True) for all widgets,
Wrap the TestWidget in a QMainWindow (though that's not what I want to do for the real application),
Implement event handlers on parent widgets and event.accept() (though as I understand it, events propagate from inside out, so this shouldn't be required).
How can I receive hover events on my custom QWidgets?
The QWidget like the QLabel do not have the hoverEnterEvent and hoverLeaveEvent methods, those methods are from the QGraphicsItem so your code doesn't work.
If you want to listen to the hover events of the type you must override the event() method:
import sys
from PyQt5.QtCore import Qt, QEvent
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout
class TestWidget(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout(self)
layout.addWidget(TestLabel("Test 1"))
layout.addWidget(TestLabel("Test 2"))
class TestLabel(QLabel):
def __init__(self, text):
super().__init__(text)
self.setAttribute(Qt.WA_Hover)
def event(self, event):
if event.type() == QEvent.HoverEnter:
print("enter")
elif event.type() == QEvent.HoverLeave:
print("leave")
return super().event(event)
def main():
app = QApplication(sys.argv)
window = TestWidget()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Did you know that you can do this with QWidget's enterEvent and leaveEvent? All you need to do is change the method names. You won't even need to set the Hover attribute on the label.
from PyQt5.QtWidgets import QApplication, QGridLayout, QLabel, QWidget
class Window(QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
layout = QGridLayout()
self.label = MyLabel(self)
layout.addWidget(self.label)
self.setLayout(layout)
text = "hover label"
self.label.setText(text)
class MyLabel(QLabel):
def __init__(self, parent=None):
super(MyLabel, self).__init__(parent)
self.setParent(parent)
def enterEvent(self, event):
self.prev_text = self.text()
self.setText('hovering')
def leaveEvent(self, event):
self.setText(self.prev_text)
if __name__ == "__main__":
app = QApplication([])
w = Window()
w.show()
app.exit(app.exec_())

QComboBox click triggers a leaveEvent on the main QDialog

I am trying to build a hover Dialog but i am stuck at the interaction between QComboBox selection and QDialog's leaveEvent. it looks like when i try to click on combobox to select something, it triggers a leaveEvent which then hides my QDialog. Why is this happening? What can i try to ensure that the Dialog is only hidden when I move my mouse outside of the Dialog?
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
class hoverDialog(QDialog):
def __init__(self, parent=None):
super().__init__()
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.v = QVBoxLayout()
self.combobox = QComboBox()
self.combobox.addItems(['Work-around','Permanent'])
self.textedit = QPlainTextEdit()
self.v.addWidget(self.combobox)
self.v.addWidget(self.textedit)
self.setLayout(self.v)
#self.setMouseTracking(True)
def leaveEvent(self, event):
self.hide()
return super().leaveEvent(event)
class Table(QWidget):
def __init__(self, parent=None):
super().__init__()
self.label4 = QLabel()
self.label4.setText("hover popup")
self.label4.installEventFilter(self)
self.checkbox1 = QCheckBox()
self.pushButton3 = QPushButton()
self.pushButton3.setText('Generate')
self.pushButton3.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
self.pushButton3.clicked.connect(self.buttonPressed)
self.hbox5 = QHBoxLayout()
self.hbox5.addWidget(self.checkbox1)
self.hbox5.addWidget(self.label4)
self.hbox5.addWidget(self.pushButton3)
self.vbox1 = QVBoxLayout()
self.vbox1.addLayout(self.hbox5)
self.setLayout(self.vbox1)
self.autoResolve = hoverDialog(self)
def eventFilter(self, obj, event):
if obj == self.label4 and event.type() == QtCore.QEvent.Enter and self.autoResolve.isHidden():
self.onHovered()
return super().eventFilter(obj, event)
def onHovered(self):
pos = QtGui.QCursor.pos()
self.autoResolve.move(pos)
self.autoResolve.show()
def buttonPressed(self):
if self.checkbox.isChecked():
print('do something..')
if __name__ == '__main__':
app = QApplication(sys.argv)
form = Table()
form.show()
app.exec_()
Looking at the sources, I believe that the origin of the problem is in Qt's behavior whenever a new popup widget is shown.
I cannot guarantee this at 100%, but in any case the simplest solution is to hide the widget only if the combo popup is not shown:
def leaveEvent(self, event):
if not self.combobox.view().isVisible():
self.hide()
Note that your approach is not really perfect: since the dialog could be shown with a geometry that is outside the current mouse position, it will not be able to hide itself until the mouse actually enters it. You should probably filter FocusOut and WindowDeactivate events also.

Create a Translucent canvas for dropping files [PyQT]

Baseline
How I want to create
Hi
I have a simple PyQT5 app. The main window is a QMainWindow which houses a QWidget. The Layout of the QWidget is as follows:
Class Canvas(QWidget):
def __init__(self):
super().__init__()
self.ListOfPlots = []
self.outFile = "temp.prb"
self.initUI()
def initUI(self):
self.headLabel = QLabel("List of Plots:")
self.label = QLabel("",self)
self.setAcceptDrops(True)
self.createPushButtons()
hbox = QHBoxLayout() #Horizontal Layout
#hbox.addStretch(1)
hbox.addWidget(self.combineButton)
hbox.addWidget(self.openButton)
hbox.addWidget(self.resetButton)
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.headLabel)
self.vbox.addWidget(self.label)
self.vbox.addLayout(hbox) ## The horizontal box is placed into vertical layout
self.setLayout(self.vbox)
I want to create a translucent drop area as shown in the second picture with a label indicating drop files here. What would be the most suitable way to do it?
The entire widget is ok to allow drops. I just want a box indicating it is ok to drop here (like an indicator).
You can use dynamic properties to trigger an indicator when it's okay to drop. If you need the background to be semi-transparent, use rgba for your widget's stylesheet background property. background:rgba(255,255,255,90)
from PySide2 import QtWidgets
import sys
from PySide2.QtWidgets import QWidget, QGridLayout, QFrame
class DropZone(QFrame):
def __init__(self, parent=None):
QFrame.__init__(self)
self.setFixedSize(200, 200)
self.setAcceptDrops(True)
self.setObjectName('DropZone')
self.setStyleSheet(
'QFrame#DropZone[Dropindicator=true]{border:3px solid green;background:darkorange;}\nQFrame#DropZone{background:orange;}')
def dragEnterEvent(self, event):
if event.mimeData().hasFormat('text/plain'):
self.setProperty('Dropindicator',True)
print(event.mimeData().text())
self.setStyle(self.style())
...
event.accept()
else:
event.ignore()
def dropEvent(self, event):
event.accept()
if event.isAccepted():
self.setProperty('Dropindicator',False)
self.setStyle(self.style())
class Widget( QWidget):
def __init__(self,parent=None):
QWidget.__init__(self)
gl = QGridLayout()
self.setLayout(gl)
self.dz = DropZone()
self.dz.setParent(self)
gl.addWidget(self.dz)
self.setLayout(gl)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())

PyQt5 - resize label to fill the whole window

from PyQt5 import QtGui, QtCore, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.label = QtWidgets.QLabel(self)
self.label.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored)
self.label.resize(800, 600)
self.label.setContentsMargins(0, 0, 0, 0);
self.pixmap = QtGui.QPixmap("image.jpg")
self.label.setPixmap(self.pixmap)
self.label.setMinimumSize(1, 1)
self.label.installEventFilter(self)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.label)
def eventFilter(self, source, event):
if (source is self.label and event.type() == QtCore.QEvent.Resize):
self.label.setPixmap(self.pixmap.scaled(
self.label.size(), QtCore.Qt.KeepAspectRatio))
return super(Window, self).eventFilter(source, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
window.resize(800, 600)
sys.exit(app.exec_())
This is my application, my goal is simple - have an image that fills the whole window and resizes after the window resize.
This code works okay in resizing the image, but the label doesn't cover the whole window, I have those "borders". How can I remove them/resize the label to window size?
I am working on Windows if this changes things.
That is the effect I get now.
I solved it in PyQt4, so I'm not 100% sure if it will work for PyQt5, but I guess it should (some minor modifications might be needed, e.g. import PyQt5 instead of PyQt4).
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.label = QtGui.QLabel(self)
self.label.resize(800, 600)
pixmap1 = QtGui.QPixmap("image.png")
self.pixmap = pixmap1.scaled(self.width(), self.height())
self.label.setPixmap(self.pixmap)
self.label.setMinimumSize(1, 1)
def resizeEvent(self, event):
pixmap1 = QtGui.QPixmap("image.png")
self.pixmap = pixmap1.scaled(self.width(), self.height())
self.label.setPixmap(self.pixmap)
self.label.resize(self.width(), self.height())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
window.resize(800, 600)
sys.exit(app.exec_())
The most important for you is definition of resizeEvent. You could use already defined self.pixmap and just resize it, but the quality of the image would degrade the more resizing you use. Therefore, it's better always create new pixmap scaled to current width and height of the Window.
No need to create a QLabel inside the separate QWidget. You can simply inherit QLabel instead of QWidget. It will make your code more simple and cleaner:
class MyLabelPixmap(QtWidgets.QLabel):
def __init__(self):
QtWidgets.QLabel.__init__(self)
self.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored)
self.resize(800, 600)
self.pixmap = QtGui.QPixmap("image.jpg")
self.setPixmap(self.pixmap)
self.installEventFilter(self)
def eventFilter(self, source, event):
if (source is self and event.type() == QtCore.QEvent.Resize):
self.setPixmap(self.pixmap.scaled(self.size()))
return super(Window, self).eventFilter(source, event)
In case you would like to embed your MyLabelPixmap widget into the QMainWindow just add in your QMainWindow.__init__
self.myLabelPixmap = MyLabelPixmap()
self.setCentralWidget(self.myLabelPixmap)

Relative QWidget position in parent - incorrect Y

Maybe I'm just having a bad day but I just can't seem to get this to work.
I'm trying to set the position of a widget floating inside another but it always seems to be offset.
My layout looks like this and I'm trying to make a "floating" widget inside canvas in the top right.
I re-implemented the show method (have also tried the showEvent) with this logic:
def show(self):
pos = self.parent().mapToGlobal(self.parent().pos())
topright = self.parent().rect().topRight()
self.resize(QSize(self.geometry().width(), self.parent().geometry().size().height()))
newpos = (pos + topright) - QPoint(self.geometry().width(), 0)
self.move(newpos)
super(InfoDock, self).show()
This is the result:
The two toolbars are added into canvas_page using:
self.canvas_page.layout().insertWidget(2, self.toolbar2)
self.canvas_page.layout().insertWidget(3, self.toolbar)
If I remove these calls it moves the widget higher but it still seems to be offset the size of settignsLabel_2 and line_2
To set an absolute (floating) position for a widget, reimplement the resizeEvent of its parent, and move() the widget relative to that parent:
def resizeEvent(self, event):
# move to top-right corner
self.widget.move(self.width() - self.widget.width() - 1, 1)
super(Canvas, self).resizeEvent(event)
UPDATE:
Working demo script:
from PyQt4 import QtCore, QtGui
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
toolbar = self.addToolBar('Toolbar')
toolbar.addAction('Action')
widget = QtGui.QWidget(self)
layout = QtGui.QVBoxLayout(widget)
self.canvas = Canvas(widget)
layout.addWidget(self.canvas)
self.setCentralWidget(widget)
class Canvas(QtGui.QGraphicsView):
def __init__(self, parent):
super(Canvas, self).__init__(parent)
self.widget = QtGui.QComboBox(self)
def resizeEvent(self, event):
self.widget.move(self.width() - self.widget.width() - 2, 2)
super(Canvas, self).resizeEvent(event)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
window.setGeometry(500, 300, 200, 200)
sys.exit(app.exec_())

Categories