Custom Titlebar with frame in PyQt5 - python

I'm working on an opensource markdown supported minimal note taking application for Windows/Linux. I'm trying to remove the title bar and add my own buttons. I want something like, a title bar with only two custom buttons as shown in the figure
Currently I have this:
I've tried modifying the window flags:
With not window flags, the window is both re-sizable and movable. But no custom buttons.
Using self.setWindowFlags(QtCore.Qt.FramelessWindowHint), the window has no borders, but cant move or resize the window
Using self.setWindowFlags(QtCore.Qt.CustomizeWindowHint), the window is resizable but cannot move and also cant get rid of the white part at the top of the window.
Any help appreciated. You can find the project on GitHub here.
Thanks..
This is my python code:
from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets, uic
import sys
import os
import markdown2 # https://github.com/trentm/python-markdown2
from PyQt5.QtCore import QRect
from PyQt5.QtGui import QFont
simpleUiForm = uic.loadUiType("Simple.ui")[0]
class SimpleWindow(QtWidgets.QMainWindow, simpleUiForm):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)
self.setupUi(self)
self.markdown = markdown2.Markdown()
self.css = open(os.path.join("css", "default.css")).read()
self.editNote.setPlainText("")
#self.noteView = QtWebEngineWidgets.QWebEngineView(self)
self.installEventFilter(self)
self.displayNote.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
#self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
def eventFilter(self, object, event):
if event.type() == QtCore.QEvent.WindowActivate:
print("widget window has gained focus")
self.editNote.show()
self.displayNote.hide()
elif event.type() == QtCore.QEvent.WindowDeactivate:
print("widget window has lost focus")
note = self.editNote.toPlainText()
htmlNote = self.getStyledPage(note)
# print(note)
self.editNote.hide()
self.displayNote.show()
# print(htmlNote)
self.displayNote.setHtml(htmlNote)
elif event.type() == QtCore.QEvent.FocusIn:
print("widget has gained keyboard focus")
elif event.type() == QtCore.QEvent.FocusOut:
print("widget has lost keyboard focus")
return False
The UI file is created in the following hierarchy

Here are the steps you just gotta follow:
Have your MainWindow, be it a QMainWindow, or QWidget, or whatever [widget] you want to inherit.
Set its flag, self.setWindowFlags(Qt.FramelessWindowHint)
Implement your own moving around.
Implement your own buttons (close, max, min)
Implement your own resize.
Here is a small example with move around, and buttons implemented. You should still have to implement the resize using the same logic.
import sys
from PyQt5.QtCore import QPoint
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QHBoxLayout
from PyQt5.QtWidgets import QLabel
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QWidget
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.layout = QVBoxLayout()
self.layout.addWidget(MyBar(self))
self.setLayout(self.layout)
self.layout.setContentsMargins(0,0,0,0)
self.layout.addStretch(-1)
self.setMinimumSize(800,400)
self.setWindowFlags(Qt.FramelessWindowHint)
self.pressing = False
class MyBar(QWidget):
def __init__(self, parent):
super(MyBar, self).__init__()
self.parent = parent
print(self.parent.width())
self.layout = QHBoxLayout()
self.layout.setContentsMargins(0,0,0,0)
self.title = QLabel("My Own Bar")
btn_size = 35
self.btn_close = QPushButton("x")
self.btn_close.clicked.connect(self.btn_close_clicked)
self.btn_close.setFixedSize(btn_size,btn_size)
self.btn_close.setStyleSheet("background-color: red;")
self.btn_min = QPushButton("-")
self.btn_min.clicked.connect(self.btn_min_clicked)
self.btn_min.setFixedSize(btn_size, btn_size)
self.btn_min.setStyleSheet("background-color: gray;")
self.btn_max = QPushButton("+")
self.btn_max.clicked.connect(self.btn_max_clicked)
self.btn_max.setFixedSize(btn_size, btn_size)
self.btn_max.setStyleSheet("background-color: gray;")
self.title.setFixedHeight(35)
self.title.setAlignment(Qt.AlignCenter)
self.layout.addWidget(self.title)
self.layout.addWidget(self.btn_min)
self.layout.addWidget(self.btn_max)
self.layout.addWidget(self.btn_close)
self.title.setStyleSheet("""
background-color: black;
color: white;
""")
self.setLayout(self.layout)
self.start = QPoint(0, 0)
self.pressing = False
def resizeEvent(self, QResizeEvent):
super(MyBar, self).resizeEvent(QResizeEvent)
self.title.setFixedWidth(self.parent.width())
def mousePressEvent(self, event):
self.start = self.mapToGlobal(event.pos())
self.pressing = True
def mouseMoveEvent(self, event):
if self.pressing:
self.end = self.mapToGlobal(event.pos())
self.movement = self.end-self.start
self.parent.setGeometry(self.mapToGlobal(self.movement).x(),
self.mapToGlobal(self.movement).y(),
self.parent.width(),
self.parent.height())
self.start = self.end
def mouseReleaseEvent(self, QMouseEvent):
self.pressing = False
def btn_close_clicked(self):
self.parent.close()
def btn_max_clicked(self):
self.parent.showMaximized()
def btn_min_clicked(self):
self.parent.showMinimized()
if __name__ == "__main__":
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
Here are some tips:
Option 1:
Have a QGridLayout with widget in each corner and side(e.g. left, top-left, menubar, top-right, right, bottom-right, bottom and bottom left)
With the approach (1) you would know when you are clicking in each border, you just got to define each one size and add each one on their place.
When you click on each one treat them in their respective ways, for example, if you click in the left one and drag to the left, you gotta resize it larger and at the same time move it to the left so it will appear to be stopped at the right place and grow width.
Apply this reasoning to each edge, each one behaving in the way it has to.
Option 2:
Instead of having a QGridLayout you can detect in which place you are clicking by the click pos.
Verify if the x of the click is smaller than the x of the moving pos to know if it's moving left or right and where it's being clicked.
The calculation is made in the same way of the Option1
Option 3:
Probably there are other ways, but those are the ones I just thought of. For example using the CustomizeWindowHint you said you are able to resize, so you just would have to implement what I gave you as example. BEAUTIFUL!
Tips:
Be careful with the localPos(inside own widget), globalPos(related to your screen). For example: If you click in the very left of your left widget its 'x' will be zero, if you click in the very left of the middle(content)it will be also zero, although if you mapToGlobal you will having different values according to the pos of the screen.
Pay attention when resizing, or moving, when you have to add width or subtract, or just move, or both, I'd recommend you to draw on a paper and figure out how the logic of resizing works before implementing it out of blue.
GOOD LUCK :D

While the accepted answer can be considered valid, it has some issues.
using setGeometry() is not appropriate (and the reason for using it was wrong) since it doesn't consider possible frame margins set by the style;
the position computation is unnecessarily complex;
resizing the title bar to the total width is wrong, since it doesn't consider the buttons and can also cause recursion problems in certain situations (like not setting the minimum size of the main window); also, if the title is too big, it makes impossible to resize the main window;
buttons should not accept focus;
setting a layout creates a restraint for the "main widget" or layout, so the title should not be added, but the contents margins of the widget should be used instead;
I revised the code to provide a better base for the main window, simplify the moving code, and add other features like the Qt windowTitle() property support, standard QStyle icons for buttons (instead of text), and proper maximize/normal button icons. Note that the title label is not added to the layout.
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
self.titleBar = MyBar(self)
self.setContentsMargins(0, self.titleBar.height(), 0, 0)
self.resize(640, self.titleBar.height() + 480)
def changeEvent(self, event):
if event.type() == event.WindowStateChange:
self.titleBar.windowStateChanged(self.windowState())
def resizeEvent(self, event):
self.titleBar.resize(self.width(), self.titleBar.height())
class MyBar(QWidget):
clickPos = None
def __init__(self, parent):
super(MyBar, self).__init__(parent)
self.setAutoFillBackground(True)
self.setBackgroundRole(QPalette.Shadow)
# alternatively:
# palette = self.palette()
# palette.setColor(palette.Window, Qt.black)
# palette.setColor(palette.WindowText, Qt.white)
# self.setPalette(palette)
layout = QHBoxLayout(self)
layout.setContentsMargins(1, 1, 1, 1)
layout.addStretch()
self.title = QLabel("My Own Bar", self, alignment=Qt.AlignCenter)
# if setPalette() was used above, this is not required
self.title.setForegroundRole(QPalette.Light)
style = self.style()
ref_size = self.fontMetrics().height()
ref_size += style.pixelMetric(style.PM_ButtonMargin) * 2
self.setMaximumHeight(ref_size + 2)
btn_size = QSize(ref_size, ref_size)
for target in ('min', 'normal', 'max', 'close'):
btn = QToolButton(self, focusPolicy=Qt.NoFocus)
layout.addWidget(btn)
btn.setFixedSize(btn_size)
iconType = getattr(style,
'SP_TitleBar{}Button'.format(target.capitalize()))
btn.setIcon(style.standardIcon(iconType))
if target == 'close':
colorNormal = 'red'
colorHover = 'orangered'
else:
colorNormal = 'palette(mid)'
colorHover = 'palette(light)'
btn.setStyleSheet('''
QToolButton {{
background-color: {};
}}
QToolButton:hover {{
background-color: {}
}}
'''.format(colorNormal, colorHover))
signal = getattr(self, target + 'Clicked')
btn.clicked.connect(signal)
setattr(self, target + 'Button', btn)
self.normalButton.hide()
self.updateTitle(parent.windowTitle())
parent.windowTitleChanged.connect(self.updateTitle)
def updateTitle(self, title=None):
if title is None:
title = self.window().windowTitle()
width = self.title.width()
width -= self.style().pixelMetric(QStyle.PM_LayoutHorizontalSpacing) * 2
self.title.setText(self.fontMetrics().elidedText(
title, Qt.ElideRight, width))
def windowStateChanged(self, state):
self.normalButton.setVisible(state == Qt.WindowMaximized)
self.maxButton.setVisible(state != Qt.WindowMaximized)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.clickPos = event.windowPos().toPoint()
def mouseMoveEvent(self, event):
if self.clickPos is not None:
self.window().move(event.globalPos() - self.clickPos)
def mouseReleaseEvent(self, QMouseEvent):
self.clickPos = None
def closeClicked(self):
self.window().close()
def maxClicked(self):
self.window().showMaximized()
def normalClicked(self):
self.window().showNormal()
def minClicked(self):
self.window().showMinimized()
def resizeEvent(self, event):
self.title.resize(self.minButton.x(), self.height())
self.updateTitle()
if __name__ == "__main__":
app = QApplication(sys.argv)
mw = MainWindow()
layout = QVBoxLayout(mw)
widget = QTextEdit()
layout.addWidget(widget)
mw.show()
mw.setWindowTitle('My custom window with a very, very long title')
sys.exit(app.exec_())

This is for the people who are going to implement custom title bar in PyQt6 or PySide6
The below changes should be done in the answer given by #musicamante
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
# self.clickPos = event.windowPos().toPoint()
self.clickPos = event.scenePosition().toPoint()
def mouseMoveEvent(self, event):
if self.clickPos is not None:
# self.window().move(event.globalPos() - self.clickPos)
self.window().move(event.globalPosition().toPoint() - self.clickPos)
if __name__ == "__main__":
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
# sys.exit(app.exec_())
sys.exit(app.exec())
References:
QMouseEvent.globalPosition(),
QMouseEvent.scenePosition()
This method of moving Windows with Custom Widget doesn't work with WAYLAND. If anybody has a solution for that please post it here for future reference

Working functions for WAYLAND and PyQT6/PySide6 :
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
self._move()
return super().mousePressEvent(event)
def _move(self):
window = self.window().windowHandle()
window.startSystemMove()
Please check.

Related

Drawing Line from QLabel to QLabel in PyQt

I'm fairly new to PyQt
I'm trying to drawing a line from 1 QLabel to another.
My 2 QLabel are located on another QLabel which acts as an image in my GUI.
I've managed to track the mouse event and move the label around, but I cannot draw the line between them using QPainter.
Thank you in advance :)
This is my MouseTracking class
class MouseTracker(QtCore.QObject):
positionChanged = QtCore.pyqtSignal(QtCore.QPoint)
def __init__(self, widget):
super().__init__(widget)
self._widget = widget
self.widget.setMouseTracking(True)
self.widget.installEventFilter(self)
#property
def widget(self):
return self._widget
def eventFilter(self, o, e):
if e.type() == QtCore.QEvent.MouseMove:
self.positionChanged.emit(e.pos())
return super().eventFilter(o, e)
This is my DraggableLabel class:
class DraggableLabel(QLabel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.LabelIsMoving = False
self.setStyleSheet("border-color: rgb(238, 0, 0); border-width : 2.0px; border-style:inset; background: transparent;")
self.origin = None
# self.setDragEnabled(True)
def mousePressEvent(self, event):
if not self.origin:
# update the origin point, we'll need that later
self.origin = self.pos()
if event.button() == Qt.LeftButton:
self.LabelIsMoving = True
self.mousePos = event.pos()
# print(event.pos())
def mouseMoveEvent(self, event):
if event.buttons() == Qt.LeftButton:
# move the box
self.move(self.pos() + event.pos() - self.mousePos)
# print(event.pos())
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
print(event.pos())
def paintEvent(self, event):
painter = QPainter()
painter.setBrush(Qt.red)
# painter.setPen(qRgb(200,0,0))
painter.drawLine(10, 10, 200, 200)
This is my custom class for the QTabwigdet (since I need to control and track the position of 2 QLabels whenever the user add/insert a new Tab)
class DynamicTab(QWidget):
def __init__(self):
super(DynamicTab, self).__init__()
# self.count = 0
self.setMouseTracking(True)
self.setAcceptDrops(True)
self.bool = True
self.layout = QVBoxLayout(self)
self.label = QLabel()
self.layout.addChildWidget(self.label)
self.icon1 = DraggableLabel(parent=self)
#pixmap for icon 1
pixmap = QPixmap('icon1.png')
# currentTab.setLayout(QVBoxLayout())
# currentTab.layout.setWidget(QRadioButton())
self.icon1.setPixmap(pixmap)
self.icon1.setScaledContents(True)
self.icon1.setFixedSize(20, 20)
self.icon2 = DraggableLabel(parent=self)
pixmap = QPixmap('icon1.png')
# currentTab.setLayout(QVBoxLayout())
# currentTab.layout.setWidget(QRadioButton())
self.icon2.setPixmap(pixmap)
self.icon2.setScaledContents(True)
self.icon2.setFixedSize(20, 20)
#self.label.move(event.x() - self.label_pos.x(), event.y() - self.label_pos.y())
MainWindow and main method:
class UI_MainWindow(QMainWindow):
def __init__(self):
super(UI_MainWindow, self).__init__()
self.setWindowTitle("QHBoxLayout")
self.PictureTab = QTabWidget
def __setupUI__(self):
# super(UI_MainWindow, self).__init__()
self.setWindowTitle("QHBoxLayout")
loadUi("IIML_test2.ui", self)
self.tabChanged(self.PictureTab)
# self.tabChanged(self.tabWidget)
self.changeTabText(self.PictureTab, index=0, TabText="Patient1")
self.Button_ImportNew.clicked.connect(lambda: self.insertTab(self.PictureTab))
# self.PictureTab.currentChanged.connect(lambda: self.tabChanged(QtabWidget=self.PictureTab))
# self.tabWidget.currentChanged.connect(lambda: self.tabChanged(QtabWidget=self.tabWidget))
def tabChanged(self, QtabWidget):
QtabWidget.currentChanged.connect(lambda : print("Tab was changed to ", QtabWidget.currentIndex()))
def changeTabText(self, QTabWidget, index, TabText):
QTabWidget.setTabText(index, TabText)
def insertTab(self, QtabWidget):
# QFileDialog.getOpenFileNames(self, 'Open File', '.')
QtabWidget.addTab(DynamicTab(), "New Tab")
# get number of active tab
count = QtabWidget.count()
# change the view to the last added tab
currentTab = QtabWidget.widget(count-1)
QtabWidget.setCurrentWidget(currentTab)
pixmap = QPixmap('cat.jpg')
#currentTab.setLayout(QVBoxLayout())
#currentTab.layout.setWidget(QRadioButton())
# currentTab.setImage("cat.jpg")
currentTab.label.setPixmap(pixmap)
currentTab.label.setScaledContents(True)
currentTab.label.setFixedSize(self.label.width(), self.label.height())
tracker = MouseTracker(currentTab.label)
tracker.positionChanged.connect(self.on_positionChanged)
self.label_position = QtWidgets.QLabel(currentTab.label, alignment=QtCore.Qt.AlignCenter)
self.label_position.setStyleSheet('background-color: white; border: 1px solid black')
currentTab.label.show()
# print(currentTab.label)
#QtCore.pyqtSlot(QtCore.QPoint)
def on_positionChanged(self, pos):
delta = QtCore.QPoint(30, -15)
self.label_position.show()
self.label_position.move(pos + delta)
self.label_position.setText("(%d, %d)" % (pos.x(), pos.y()))
self.label_position.adjustSize()
# def SetupUI(self, MainWindow):
#
# self.setLayout(self.MainLayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
UI_MainWindow = UI_MainWindow()
UI_MainWindow.__setupUI__()
widget = QtWidgets.QStackedWidget()
widget.addWidget(UI_MainWindow)
widget.setFixedHeight(900)
widget.setFixedWidth(1173)
widget.show()
try:
sys.exit(app.exec_())
except:
print("Exiting")
My concept: I have a DynamicTab (QTabWidget) which acts as a picture opener (whenever the user press Import Now). The child of this Widget are 3 Qlabels: self.label is the picture it self and two other Qlabels are the icon1 and icon2 which I'm trying to interact/drag with (Draggable Label)
My Problem: I'm trying to track my mouse movement and custom the painter to paint accordingly. I'm trying that out by telling the painter class to paint whenever I grab the label and move it with my mouse (Hence, draggable). However, I can only track the mouse position inside the main QLabel (the main picture) whenever I'm not holding or clicking my left mouse.
Any help will be appreciated here.
Thank you guys.
Painting can only happen within the widget rectangle, so you cannot draw outside the boundaries of DraggableLabel.
The solution is to create a further custom widget that shares the same parent, and then draw the line that connects the center of the other two.
In the following example I install an event filter on the two draggable labels which will update the size of the custom widget based on them (so that its geometry will always include those two geometries) and call self.update() which schedules a repainting. Note that since the widget is created above the other two, it might capture mouse events that are intended for the others; to prevent that, the Qt.WA_TransparentForMouseEvents attribute must be set.
class Line(QWidget):
def __init__(self, obj1, obj2, parent):
super().__init__(parent)
self.obj1 = obj1
self.obj2 = obj2
self.obj1.installEventFilter(self)
self.obj2.installEventFilter(self)
self.setAttribute(Qt.WA_TransparentForMouseEvents)
def eventFilter(self, obj, event):
if event.type() in (event.Move, event.Resize):
rect = self.obj1.geometry() | self.obj2.geometry()
corner = rect.bottomRight()
self.resize(corner.x(), corner.y())
self.update()
return super().eventFilter(obj, event)
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(painter.Antialiasing)
painter.setPen(QColor(200, 0, 0))
painter.drawLine(
self.obj1.geometry().center(),
self.obj2.geometry().center()
)
class DynamicTab(QWidget):
def __init__(self):
# ...
self.line = Line(self.icon1, self.icon2, self)
Notes:
to simplify things, I only use resize() (not setGeometry()), in this way the widget will always be placed on the top left corner of the parent and we can directly get the other widget's coordinates without any conversion;
the custom widget is placed above the other two because it is added after them; if you want to place it under them, use self.line.lower();
the painter must always be initialized with the paint device argument, either by using QPainter(obj) or painter.begin(obj), otherwise no painting will happen (and you'll get lots of errors in the output);
do not use layout.addChildWidget() (which is used internally by the layout), but the proper addWidget() function of the layout;
the stylesheet border syntax can be shortened with border: 2px inset rgb(238, 0, 0);;
the first lines of insertTab could be simpler: currentTab = DynamicTab() QtabWidget.addTab(currentTab, "New Tab");
currentTab.label.setFixedSize(self.label.size());
QMainWindow is generally intended as a top level widget, it's normally discouraged to add it to a QStackedWidget; note that if you did that because of a Youtube tutorial, that tutorial is known for suggesting terrible practices (like the final try/except block) which should not be followed;
only classes and constants should have capitalized names, not variables and functions which should always start with a lowercase letter;

PyQt5 grabWindow captures black screen instead of the selected area

I have an application where I have a transparent window, I am capturing the screen underneath and then displaying the same once user release the left mouse button.
But the problem is I see only black screen, I tried saving the selected screenshot but still same black screen.
Here is my code :
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
import sys
class MainWindow(qtw.QMainWindow):
def __init__(self, *arg, **kwargs):
super().__init__()
self.setWindowFlag(qtc.Qt.FramelessWindowHint)
self.setAttribute(qtc.Qt.WA_TranslucentBackground)
borderWidget = qtw.QWidget(objectName='borderWidget')
self.setCentralWidget(borderWidget)
bgd = self.palette().color(qtg.QPalette.Window)
bgd.setAlphaF(.005)
self.setStyleSheet('''
#borderWidget {{
border: 3px solid blue;
background: {bgd};
}}
'''.format(bgd=bgd.name(bgd.HexArgb)))
self.setGeometry(100, 100, 400, 300)
self.showFullScreen()
self.setCursor(qtc.Qt.CrossCursor)
self.begin = None
self.end = None
self.show()
def paintEvent(self, event):
if self.begin:
qpbox = qtg.QPainter(self)
br = qtg.QBrush(qtg.QColor(100, 10, 10, 40))
qpbox.setBrush(br)
qpbox.drawRect(qtc.QRect(self.begin, self.end))
# close on right click
def mouseReleaseEvent(self, QMouseEvent):
if QMouseEvent.button() == qtc.Qt.RightButton:
self.close()
elif QMouseEvent.button() == qtc.Qt.LeftButton:
screen = qtw.QApplication.primaryScreen()
img = screen.grabWindow(self.winId(), self.begin.x(), self.end.y(), self.end.x() - self.begin.x() , self.end.y()-self.begin.y())
img.save('screenshot.png', 'png')
self.setStyleSheet("")
self.central_widget = qtw.QWidget()
label = qtw.QLabel(self)
label.setPixmap(img)
self.resize(img.width(), img.height())
self.setCentralWidget(label)
def mousePressEvent(self, QMouseEvent):
if QMouseEvent.button() == qtc.Qt.LeftButton:
self.begin = QMouseEvent.pos()
self.end = QMouseEvent.pos()
self.update()
def mouseMoveEvent(self, QMouseEvent):
self.end = QMouseEvent.pos()
self.update()
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
w = MainWindow()
sys.exit(app.exec_())
You're grabbing from the current window, not from the desktop. While what you see is the desktop (due to the transparency), specifying a window id results in grabbing only that window without considering the background composition or any other foreign window.
If you want to grab from the screen, you need to use the root window's id, which is 0.
Also note that:
the coordinates are wrong, as you used self.end for the y coordinate;
if the user selects a negative rectangle, the result is unexpected; you should use a normalized rectangle instead (which always have positive width and height);
you should hide the widget before properly taking the screenshot, otherwise you will also capture the darkened background of the capture area;
there's no need to always replace the central widget, just use an empty QLabel and change its pixmap;
an alpha value of 0.005 is practically pointless, just make it transparent;
the capture rectangle should be cleared after the screenshot has been taken;
class MainWindow(qtw.QMainWindow):
def __init__(self, *arg, **kwargs):
super().__init__()
self.setWindowFlags(self.windowFlags() | qtc.Qt.FramelessWindowHint)
self.setAttribute(qtc.Qt.WA_TranslucentBackground)
# use a QLabel
borderWidget = qtw.QLabel(objectName='borderWidget')
self.setCentralWidget(borderWidget)
self.setStyleSheet('''
#borderWidget {{
border: 3px solid blue;
background: transparent;
}}
''')
# pointless, you're showing the window in full screen
# self.setGeometry(100, 100, 400, 300)
# variables that are required for painting must be declared *before*
# calling any show* function; while this is generally not an issue,
# as painting will actually happen "later", it's conceptually wrong
# to declare a variable after it's (possibly) required by a function.
self.captureRect = None
self.showFullScreen()
self.setCursor(qtc.Qt.CrossCursor)
# unnecessary, you've already called showFullScreen
# self.show()
def paintEvent(self, event):
if self.captureRect:
qpbox = qtg.QPainter(self)
br = qtg.QBrush(qtg.QColor(100, 10, 10, 40))
qpbox.setBrush(br)
qpbox.drawRect(self.captureRect)
def mouseReleaseEvent(self, event):
if event.button() == qtc.Qt.RightButton:
self.close()
elif event.button() == qtc.Qt.LeftButton:
self.hide()
screen = qtw.QApplication.primaryScreen()
img = screen.grabWindow(0, *self.captureRect.getRect())
self.show()
img.save('screenshot.png', 'png')
self.setStyleSheet('')
self.centralWidget().setPixmap(img)
self.captureRect = None
def mousePressEvent(self, event):
if event.button() == qtc.Qt.LeftButton:
self.begin = event.pos()
self.captureRect = qtc.QRect(self.begin, qtc.QSize())
def mouseMoveEvent(self, event):
self.captureRect = qtc.QRect(self.begin, event.pos()).normalized()
self.update()
Note that I changed the event handler argument: QMouseEvent is a class, and even though you're using the module (so the actual Qt class would be qtg.QMouseEvent), that might be confusing and risky if you eventually decide to directly import classes; besides, only class and constant names should have capitalized names, not variables or functions.

Overriding QCompleter popup position

There have been similar questions asked about overriding the QCompleter popup position but i'll still not found a working solution. I simply want to move the popup down around 5px (I have some specific styling requirements)
I've tried subclassing a QListView and using that as my popup using setPopup(). I then override the showEvent and move the popup down in Y. I also do this on the resizeEvent since I believe this is triggered when items are filtered and the popup resizes. However this doesn't work.. I then used a singleshot timer to trigger the move after 1ms. This does kind of work but it seems quite inconsistent - the first time it shows is different to subsequent times or resizing.
Below is my latest attempt (trying to hack it by counting the number of popups..), hopefully someone can show me what i'm doing wrong or a better solution
import sys
import os
from PySide2 import QtCore, QtWidgets, QtGui
class QPopup(QtWidgets.QListView):
def __init__(self, parent=None):
super(QPopup, self).__init__(parent)
self.popups = 0
def offset(self):
y = 3 if self.popups < 2 else 7
print('y: {}'.format(y))
self.move(self.pos().x(), self.pos().y() + y)
self.popups += 1
def showEvent(self, event):
print('show')
# self.offset()
QtCore.QTimer.singleShot(1, self.offset)
def resizeEvent(self, event):
print('resize')
# self.offset()
QtCore.QTimer.singleShot(1, self.offset)
class MyDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
self.create_widgets()
self.create_layout()
self.create_connections()
def create_widgets(self):
self.le = QtWidgets.QLineEdit('')
self.completer = QtWidgets.QCompleter(self)
self.completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.completer.setCompletionMode(QtWidgets.QCompleter.PopupCompletion)
self.completer.setMaxVisibleItems(10)
self.completer.setFilterMode(QtCore.Qt.MatchContains)
self.completer.setPopup(QPopup())
popup = QPopup(self)
self.completer.setPopup(popup)
self.model = QtCore.QStringListModel()
self.completer.setModel(self.model)
self.le.setCompleter(self.completer)
self.completer.model().setStringList(['one','two','three'])
def create_layout(self):
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.addWidget(self.le)
def create_connections(self):
pass
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
my_dialog = MyDialog()
my_dialog.show() # Show the UI
sys.exit(app.exec_())
One solution could be to make a subclass of QLineEdit and override keyPressEvent to display the popup with an offset:
PySide2.QtWidgets.QCompleter.complete([rect=QRect()])
For PopupCompletion and QCompletion::UnfilteredPopupCompletion modes, calling this function displays the popup displaying the current completions. By default, if rect is not specified, the popup is displayed on the bottom of the widget() . If rect is specified the popup is displayed on the left edge of the rectangle.
see doc.qt.io -> QCompleter.complete.
Complete, self-contained example
The rect is calculated based on the y-position of the cursor rect. The height of the popup window is not changed. The width is adjusted to the width of the ZLineEdit widget.
rect = QtCore.QRect(0,
self.cursorRect().y() + 4,
self.width(),
self.completer().widget().height())
Your code, slightly modified using the points mentioned above, could look like this:
import sys
from PySide2 import QtCore, QtWidgets
from PySide2.QtWidgets import QLineEdit, QDialog, QCompleter
class ZLineEdit(QLineEdit):
def __init__(self, string, parent=None):
super().__init__(string, parent)
def keyPressEvent(self, event):
super().keyPressEvent(event)
if len(self.text()) > 0:
rect = QtCore.QRect(0,
self.cursorRect().y() + 4,
self.width(),
self.completer().widget().height())
self.completer().complete(rect)
class MyDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.le = ZLineEdit('')
autoList = ['one', 'two', 'three']
self.completer = QCompleter(autoList, self)
self.setup_widgets()
self.create_layout()
self.create_connections()
def setup_widgets(self):
self.completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.completer.setCompletionMode(QtWidgets.QCompleter.PopupCompletion)
self.completer.setMaxVisibleItems(10)
self.completer.setFilterMode(QtCore.Qt.MatchContains)
self.le.setCompleter(self.completer)
def create_layout(self):
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.addWidget(self.le)
def create_connections(self):
pass
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
my_dialog = MyDialog()
my_dialog.show()
sys.exit(app.exec_())
Test
On the left side you see the default behavior. On the right side the popup is moved down 4px:

Initially moved scroll bar inside QGraphics

I am trying to set the vertical and horizontal scroll bars initially moved inside a QGraphicsScene widget. The following code should move the bars and set them in the middle, but they are not moved:
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class Diedrico(QtWidgets.QWidget):
def paintEvent(self, event):
qp = QtGui.QPainter(self)
pen = QtGui.QPen(QtGui.QColor(QtCore.Qt.black), 5)
qp.setPen(pen)
qp.drawRect(500, 500, 1000, 1000)
class UiVentana(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(UiVentana, self).__init__(parent)
self.resize(1000, 1000)
self.setFixedSize(1000, 1000)
self.scene = QtWidgets.QGraphicsScene(self)
self.view = QtWidgets.QGraphicsView(self.scene)
# This two lines should move the scroll bar
self.view.verticalScrollBar().setValue(500)
self.view.horizontalScrollBar().setValue(500)
self.diedrico = Diedrico()
self.diedrico.setFixedSize(2000, 2000)
self.scene.addWidget(self.diedrico)
self.setCentralWidget(self.view)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_R:
self.view.setTransform(QtGui.QTransform())
elif event.key() == QtCore.Qt.Key_Plus:
scale_tr = QtGui.QTransform()
scale_tr.scale(1.5, 1.5)
tr = self.view.transform() * scale_tr
self.view.setTransform(tr)
elif event.key() == QtCore.Qt.Key_Minus:
scale_tr = QtGui.QTransform()
scale_tr.scale(1.5, 1.5)
scale_inverted, invertible = scale_tr.inverted()
if invertible:
tr = self.view.transform() * scale_inverted
self.view.setTransform(tr)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ui = UiVentana()
ui.show()
sys.exit(app.exec_())
I could move the bars when I used a scroll area such as in this question
The answer given by #S.Nick works fine, but I'd like to add some insight about why you are facing this issue and what's happening "under the hood".
First of all, in your code you try to set the values of the scroll bars before adding any object to the scene.
At that point, you just created the view and the scene. The view widget has not been shown (so it doesn't "know" its actual size yet), and the scene is empty, meaning that the sceneRect is null, as in 0 width and 0 height: in this scenario, the scroll bars have a maximum value of 0, and setting any value won't give any result.
NOTE: There is a very important aspect to keep in mind: unless
explicitly declared or requested, the sceneRect of a
QGraphicsScene is always null until a view shows it. And by
"requested" I mean that even just calling scene.sceneRect() is
enough to ensure that the scene actually and finally "knows" its
extent.
After trying to set the scroll bars (with no results), you added the widget to the scene. The problem is that a view (which is a QAbstractScrollArea descendant) only updates its scrollbars as soon as it's actually mapped on the screen.
This is a complex "path" that starts from showing the main parent window (if any), which, according to its contents resizes itself and, again, resizes its contents if they require it, eventually based on their [nested widget] size policies. Only then, the view "decides" if scrollbars are needed, and eventually sets their maximum. And, only then you can actuall set a value for those scroll bars, and that's because only then the view "asks" the scene about its sceneRect.
This also (partially) explains why the view behaves in different way than a standard scroll area: widgets have a sizeHint that is used by the QWidget that contains them inside the scroll area, and, theoretically, their size is mapped as soon as they're created. But. this depends on their size hints and policies, so you cannot guarantee the actual scroll area contents size until it's finally mapped/shown; long story short: it "works", but not perfectly - at least not until everything has finally been shown.
A test example
There are different ways to solve your problem, according to your needs and implementation.
Set the sceneRect independently, even before adding any object to the scene (but if those objects boundaries go outside the scene, you'll face some inconsistency)
Call scene.sceneRect() as explained above, after adding all objects
Set the scoll bars only after the view has been shown and resized
I've prepared an example that shows the three situations explained above. It will create a new view and update its scrollbars at different points according to the checkboxes, to show how differently they behave. Note that when setting the sceneRect I used a rectangle smaller than the widget size to better display its behavior: you can see that the visual result of "Set scene rect" and "Check scene rect" is similar, but the scroll bar positions are different.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Diedrico(QtWidgets.QWidget):
def paintEvent(self, event):
qp = QtGui.QPainter(self)
pen = QtGui.QPen(QtGui.QColor(QtCore.Qt.black), 5)
qp.setPen(pen)
qp.drawRect(500, 500, 1000, 1000)
class TestView(QtWidgets.QGraphicsView):
def __init__(self, setRect=False, checkScene=False, showEventCheck=False):
super(TestView, self).__init__()
self.setFixedSize(800, 800)
scene = QtWidgets.QGraphicsScene()
self.setScene(scene)
self.diedrico = Diedrico()
self.diedrico.setFixedSize(2000, 2000)
scene.addWidget(self.diedrico)
if setRect:
scene.setSceneRect(0, 0, 1500, 1500)
elif checkScene:
scene.sceneRect()
self.showEventCheck = showEventCheck
if not showEventCheck:
self.scroll()
def scroll(self):
self.verticalScrollBar().setValue(500)
self.horizontalScrollBar().setValue(500)
def showEvent(self, event):
super(TestView, self).showEvent(event)
if not event.spontaneous() and self.showEventCheck:
self.scroll()
class ViewTester(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
self.setRectCheck = QtWidgets.QCheckBox('Set scene rect')
layout.addWidget(self.setRectCheck)
self.checkSceneCheck = QtWidgets.QCheckBox('Check scene rect')
layout.addWidget(self.checkSceneCheck)
self.showEventCheck = QtWidgets.QCheckBox('Scroll when shown')
layout.addWidget(self.showEventCheck)
showViewButton = QtWidgets.QPushButton('Show view')
layout.addWidget(showViewButton)
showViewButton.clicked.connect(self.showView)
self.view = None
def showView(self):
if self.view:
self.view.close()
self.view.deleteLater()
self.view = TestView(
setRect = self.setRectCheck.isChecked(),
checkScene = self.checkSceneCheck.isChecked(),
showEventCheck = self.showEventCheck.isChecked()
)
self.view.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
viewTester = ViewTester()
viewTester.show()
sys.exit(app.exec_())
Finally, remember that using absolute values for scrollbars is not a good idea. If you want to "center" the view, consider using centerOn (and its item based overload), or set values based on scrollBar.maximum()/2.
You want to set the value when the widget is not yet formed, make it a moment.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Diedrico(QtWidgets.QWidget):
def paintEvent(self, event):
qp = QtGui.QPainter(self)
pen = QtGui.QPen(QtGui.QColor(QtCore.Qt.black), 5)
qp.setPen(pen)
qp.drawRect(500, 500, 1000, 1000)
class UiVentana(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(UiVentana, self).__init__(parent)
self.resize(1000, 1000)
self.setFixedSize(1000, 1000)
self.scene = QtWidgets.QGraphicsScene(self)
self.view = QtWidgets.QGraphicsView(self.scene)
# This two lines should move the scroll bar
QtCore.QTimer.singleShot(0, self.set_Value) # +++
self.diedrico = Diedrico()
self.diedrico.setFixedSize(2000, 2000)
self.scene.addWidget(self.diedrico)
self.setCentralWidget(self.view)
def set_Value(self): # +++
self.view.verticalScrollBar().setValue(500)
self.view.horizontalScrollBar().setValue(500)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_R:
self.view.setTransform(QtGui.QTransform())
elif event.key() == QtCore.Qt.Key_Plus:
scale_tr = QtGui.QTransform()
scale_tr.scale(1.5, 1.5)
tr = self.view.transform() * scale_tr
self.view.setTransform(tr)
elif event.key() == QtCore.Qt.Key_Minus:
scale_tr = QtGui.QTransform()
scale_tr.scale(1.5, 1.5)
scale_inverted, invertible = scale_tr.inverted()
if invertible:
tr = self.view.transform() * scale_inverted
self.view.setTransform(tr)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ui = UiVentana()
ui.show()
sys.exit(app.exec_())

How to customize QPushButton to only popup menu when clicked around the arrow?

I want to add a popup menu to QPushButton, but only popup it when you click near the arrow, if you click other area on the button, it calls the slot connected in main UI.
I know there is QToolButton, and you can set its ToolButtonPopupMode to MenuButtonPopup, but for some reason it looks different than then rest of the button on my UI, I assume I could somehow modify the style of it to make it look exactly like QPushButton, anyway in the end I decided to subclass QPushButton instead.
The problems in the following code are:
1. How do I get the rect of the arrow, maybe show a dashed rect around the arrow, I thought the "popup menu hotspot" area should be a little bit bigger than the arrow. right now I hardcoded 20px, but I think it should be retrieved from QStyle?
[solved] How to make the button look "pressed" when clicked not near the arrow, right now its look does not change, I guess it's because I did not call base class MousePressEvent, because I don't want the menu to popup when clicked elsewhere.
How to move the position of the arrow, in my applicaton it is too close to the right edge, how can I move it to the left a little bit?
code:
from PyQt4 import QtGui, QtCore
import sys
class MyButton(QtGui.QPushButton):
def __init__(self, parent=None):
super(MyButton, self).__init__(parent)
def mousePressEvent(self, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
# figure out press location
pos = event.pos
topRight = self.rect().topRight()
bottomRight = self.rect().bottomRight()
frameWidth = self.style().pixelMetric(QtGui.QStyle.PM_DefaultFrameWidth)
print topRight, bottomRight, frameWidth
# get the rect from QStyle instead of hardcode numbers here
arrowTopLeft = QtCore.QPoint(topRight.x()-20, topRight.y())
arrowRect = QtCore.QRect(arrowTopLeft, bottomRight)
if arrowRect.contains(event.pos()):
print 'clicked near arrow'
# event.accept()
QtGui.QPushButton.mousePressEvent(self, event)
else:
print 'clicked outside'
# call the slot connected, without popup the menu
# the following code now does not make
# the button pressed
self.clicked.emit(True)
event.accept()
class Main(QtGui.QDialog):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QtGui.QVBoxLayout()
pushbutton = MyButton('Popup Button')
layout.addWidget(pushbutton)
menu = QtGui.QMenu()
menu.addAction('This is Action 1', self.Action1)
menu.addAction('This is Action 2', self.Action2)
pushbutton.setMenu(menu)
self.setLayout(layout)
pushbutton.clicked.connect(self.button_press)
def button_press(self):
print 'You pressed button'
def Action1(self):
print 'You selected Action 1'
def Action2(self):
print 'You selected Action 2'
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Main()
main.show()
app.exec_()
edit:
it seems this will stop the menu from poping up if clicked on the left side of the button
else:
print 'clicked outside'
self.blockSignals(True)
QtGui.QPushButton.mousePressEvent(self, event)
self.blockSignals(False)
Have you thought on using a QComboBox?
Or maybe two buttons next to each other one for appearance only, and the other that calls your context?
Would work to use mask on your button through pixmap.
You also could make some use of setStyleSheet("") can make some use of these attributes.
Here is a little example:
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QHBoxLayout
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QScrollArea
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QWidget
class WPopUpButton(QWidget):
"""WPopUpButton is a personalized QPushButton."""
w_container = None
v_layout_container = None
v_scroll_area = None
v_layout_preview = None
def __init__(self):
"""Init UI."""
super(WPopUpButton, self).__init__()
self.init_ui()
def init_ui(self):
"""Init all ui object requirements."""
self.button_that_do_nothing = QPushButton("Popup Button")
self.button_that_do_nothing.setStyleSheet("""
border: 0px;
background: gray;
""")
self.button_that_do_something = QPushButton("->")
#you can also set icon, to make it look better :D
self.button_that_do_something.setStyleSheet("""
border: 0px;
background: gray;
""")
self.layout = QHBoxLayout()
self.layout.setSpacing(0)
self.layout.setContentsMargins(0,0,0,0)
self.layout.addWidget(self.button_that_do_nothing)
self.layout.addWidget(self.button_that_do_something)
self.setLayout(self.layout)
self.create_connections()
def create_connections(self):
self.button_that_do_something.pressed.connect(self.btn_smtg_pressed)
self.button_that_do_something.released.connect(self.btn_smtg_released)
def btn_smtg_pressed(self):
self.button_that_do_something.setStyleSheet("""
border: 0px;
background: blue;
""")
def btn_smtg_released(self):
self.button_that_do_something.setStyleSheet("""
border: 0px;
background: gray;
""")
# HERE YOU DO WHAT YOU NEED
# FOR EXAMPLE CALL YOUR CONTEXT WHATEVER :D
def run():
app = QApplication(sys.argv)
GUI = WPopUpButton()
GUI.show()
sys.exit(app.exec_())
run()
By the way I'm using Pyqt5, you just gotta change your imports ")
Here's another option that may partially answer your question.
Instead of using the default menu, you can combine CustomContextMenu and custom arrow created by either QLabel and/or .png images.
setContentsMargins in the code will allow a much more flexible layout.
sample image
import os
from PyQt5.QtWidgets import (
QDialog,
QPushButton,
QApplication,
QVBoxLayout,
QMenu,
QStyle,
QHBoxLayout,
QLabel,
)
from PyQt5.QtCore import (
QEvent,
QPoint,
QRect,
Qt,
QSize,
)
from PyQt5.QtGui import (
QIcon,
QMouseEvent,
)
import sys
import functools
import copy
class MyButton(QPushButton):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.clicked_near_arrow = None
# set icon by letter
self.label_icon = QLabel(" ▼ ")
self.label_icon.setAttribute(Qt.WA_TranslucentBackground)
self.label_icon.setAttribute(Qt.WA_TransparentForMouseEvents)
icon_size = QSize(19, 19)
# set icon by picture
self.pixmap_default = QIcon("default_button.png").pixmap(icon_size) # prepare images if necessary
self.pixmap_presssed = QIcon("pressed_button.png").pixmap(icon_size) # prepare images if necessary
self.pic_icon = QLabel()
self.pic_icon.setAttribute(Qt.WA_TranslucentBackground)
self.pic_icon.setAttribute(Qt.WA_TransparentForMouseEvents)
self.pic_icon.setPixmap(self.pixmap_default)
# layout
lay = QHBoxLayout(self)
lay.setContentsMargins(0, 0, 6, 3)
lay.setSpacing(0)
lay.addStretch(1)
lay.addWidget(self.pic_icon)
lay.addWidget(self.label_icon)
def set_icon(self, pressed):
if pressed:
self.label_icon.setStyleSheet("QLabel{color:white}")
self.pic_icon.setPixmap(self.pixmap_presssed)
else:
self.label_icon.setStyleSheet("QLabel{color:black}")
self.pic_icon.setPixmap(self.pixmap_default)
def mousePressEvent(self, event):
if event.type() == QEvent.MouseButtonPress:
self.set_icon(pressed=True)
# figure out press location
topRight = self.rect().topRight()
bottomRight = self.rect().bottomRight()
# get the rect from QStyle instead of hardcode numbers here
arrowTopLeft = QPoint(topRight.x()-19, topRight.y())
arrowRect = QRect(arrowTopLeft, bottomRight)
if arrowRect.contains(event.pos()):
self.clicked_near_arrow = True
self.blockSignals(True)
QPushButton.mousePressEvent(self, event)
self.blockSignals(False)
print('clicked near arrow')
self.open_context_menu()
else:
self.clicked_near_arrow = False
QPushButton.mousePressEvent(self, event)
def mouseMoveEvent(self, event):
if self.rect().contains(event.pos()):
self.set_icon(pressed=True)
else:
self.set_icon(pressed=False)
QPushButton.mouseMoveEvent(self, event)
def mouseReleaseEvent(self, event):
self.set_icon(pressed=False)
if self.clicked_near_arrow:
self.blockSignals(True)
QPushButton.mouseReleaseEvent(self, event)
self.blockSignals(False)
else:
QPushButton.mouseReleaseEvent(self, event)
def setMenu(self, menu):
self.menu = menu
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.open_context_menu)
# ContextMenueのlauncher
def open_context_menu(self, point=None):
point = QPoint(7, 23)
self.menu.exec_(self.mapToGlobal(point))
event = QMouseEvent(QEvent.MouseButtonRelease, QPoint(10, 10), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
self.mouseReleaseEvent(event)
class Main(QDialog):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
menu = QMenu()
menu.addAction('This is Action 1', self.Action1)
menu.addAction('This is Action 2', self.Action2)
pushbutton = MyButton('Popup Button')
pushbutton.setMenu(menu)
layout = QVBoxLayout()
layout.addWidget(pushbutton)
self.setLayout(layout)
# event connect
pushbutton.setAutoDefault(False)
pushbutton.clicked.connect(self.button_press)
def button_press(self):
print('You pressed button')
def Action1(self):
print('You selected Action 1')
def Action2(self):
print('You selected Action 2')
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Main()
main.show()
app.exec_()

Categories