Change button font of QMessageBox in PyQt5, when using setStyleSheet? - python

Consider this example, ripped mostly from https://pythonbasics.org/pyqt-qmessagebox/:
import sys
from PyQt5 import QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QMessageBox
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import pyqtSlot
defaultfont = QtGui.QFont('Arial', 8)
def window():
app = QApplication(sys.argv)
win = QWidget()
button1 = QPushButton(win)
button1.setText("Show dialog!")
button1.move(50,50)
button1.clicked.connect(showDialog)
win.setWindowTitle("Click button")
win.show()
sys.exit(app.exec_())
def showDialog():
msgBox = QMessageBox()
msgBox.setStyleSheet("QLabel{min-width: 200px;}")
msgBox.setFont(defaultfont)
#msgBox.button(QMessageBox.Ok).setFont(defaultfont) # nowork, msgBox.button(QMessageBox.Ok) is None
#print(msgBox.buttons()) # []
#print(msgBox.findChildren(QtWidgets.QDialogButtonBox)) # [<PyQt5.QtWidgets.QDialogButtonBox object at 0x0000000005f950d0>]
#print(msgBox.findChildren(QtWidgets.QDialogButtonBox)[0].buttons()) # []
#print(msgBox.findChildren(QtWidgets.QDialogButtonBox)[0].standardButtons()) # <PyQt5.QtWidgets.QDialogButtonBox.StandardButtons object at 0x0000000005f60580>
msgBox.setIcon(QMessageBox.Information)
msgBox.setText("Message box pop up window")
msgBox.setWindowTitle("QMessageBox Example")
msgBox.buttonClicked.connect(msgButtonClick)
returnValue = msgBox.exec_()
if returnValue == QMessageBox.Ok:
print('OK clicked')
def msgButtonClick(i):
print("Button clicked is:",i.text())
if __name__ == '__main__':
window()
As the code shows, I tried applying msgBox.setFont(defaultfont) - and indeed, it does change the font of most of the message - but it does not change the font of buttons, if the line msgBox.setStyleSheet("QLabel{min-width: 200px;}") is present; this is how it looks like on Raspberry Pi in that case:
However, if you comment the line msgBox.setStyleSheet("QLabel{min-width: 200px;}"), then the font is applied also to the button:
So, how can I both use the setStyleSheet command, and change the font of the message box - for both texts and the button? (I am aware the window title bar font
is under the control of the OS, and cannot be changed via pyqt5).

If you want to change the minimum width of the QLabels then you can use setMinimumWidth():
def showDialog():
msgBox = QMessageBox()
msgBox.setFont(defaultfont)
msgBox.setIcon(QMessageBox.Information)
msgBox.setText("Message box pop up window")
msgBox.setWindowTitle("QMessageBox Example")
msgBox.buttonClicked.connect(msgButtonClick)
for label in msgBox.findChildren(QtWidgets.QLabel):
label.setMinimumWidth(200)
returnValue = msgBox.exec_()
if returnValue == QMessageBox.Ok:
print("OK clicked")
Another solution is to access the button and set the font, but this is created after using the show() method:
def showDialog():
msgBox = QMessageBox()
msgBox.setFont(defaultfont)
msgBox.setIcon(QMessageBox.Information)
msgBox.setText("Message box pop up window")
msgBox.setWindowTitle("QMessageBox Example")
msgBox.buttonClicked.connect(msgButtonClick)
msgBox.setStyleSheet("QLabel{min-width: 200px;}")
msgBox.show()
msgBox.findChild(QtWidgets.QPushButton).setFont(defaultfont)
returnValue = msgBox.exec_()
if returnValue == QMessageBox.Ok:
print("OK clicked")

Add to your code, this line:
msgBox.setStyleSheet("QPushButton {color:red; font-family: Arial; font-size:8px;}")
The button Ok on msgBox will change to red color, and your font! Tested!

Related

How to pass a QWidget's text through the parameter of Slot using lambda function

I am trying to understand why I cannot pass a parameter to my Slot function. I defined a showMessage(・・・) function so I can reuse it with a different message from a different Signal source. However the code below does not work. How could I fix the code so that it shows the text of the QLineEdit right above the pressed QPushButton when QMessageBox is shown.
import sys
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLineEdit, QMessageBox
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.mainWidget = QWidget()
mainLayout = QVBoxLayout(self.mainWidget)
lineEdit_1 = QLineEdit()
button_1 = QPushButton("Show Message 1")
lineEdit_2 = QLineEdit()
button_2 = QPushButton("Show Message 2")
mainLayout.addWidget(lineEdit_1)
mainLayout.addWidget(button_1)
mainLayout.addWidget(lineEdit_2)
mainLayout.addWidget(button_2)
button_1.clicked.connect(lambda state, \
msg=self.mainWidget.layout().itemAt(0).widget().text(), \
srcIndex=0: self.showMessage(msg, srcIndex))
button_2.clicked.connect(lambda state, \
msg=self.mainWidget.layout().itemAt(2).widget().text(), \
srcIndex=2: self.showMessage(msg, srcIndex))
self.setCentralWidget(self.mainWidget)
self.show()
def showMessage(self, message, srcIndex):
print("Debug Print (%d): %s" % (srcIndex, self.mainWidget.layout().itemAt(srcIndex).widget().text()))
msgBox = QMessageBox()
msgBox.setWindowTitle("Message")
msgBox.setIcon(QMessageBox.Information)
msgBox.setText("\"" + str(message) + "\"")
msgBox.setStandardButtons(QMessageBox.Yes)
rtnVal = msgBox.exec()
if __name__ == "__main__":
app = QApplication(sys.argv)
GUI = Window()
sys.exit(app.exec_())
I did check the following entry but I do not think it solves my problem:
PyQt sending parameter to slot when connecting to a signal
On QPushButton clicked check a condition and then connect to a function
The next:
button_1.clicked.connect(lambda state, \
msg=self.mainWidget.layout().itemAt(0).widget().text(), \
srcIndex=0: self.showMessage(msg, srcIndex))
equals:
msg=self.mainWidget.layout().itemAt(0).widget().text()
srcIndex=0
button_1.clicked.connect(lambda state, msg=msg, srcIndex=srcIndex: self.showMessage(msg, srcIndex))
In reality you are passing the text when the window is being built and not the text that you have when the button is pressed.
The best thing is to create an intermediate function that gets the text:
button_1.clicked.connect(lambda state, lineedit=lineEdit_1: self.foo(lineedit))
button_2.clicked.connect(lambda state, lineedit=lineEdit_2: self.foo(lineedit))
def foo(self, lineedit):
self.showMessage(lineedit.text())
def showMessage(self, message):
msgBox = QMessageBox()
msgBox.setWindowTitle("Message")
msgBox.setIcon(QMessageBox.Information)
msgBox.setText("\"" + str(message) + "\"")
msgBox.setStandardButtons(QMessageBox.Yes)
rtnVal = msgBox.exec()

How to center text and buttons in QMessageBox widget

I'm making a QMessageBox and was having trouble centering the text and the buttons in the pop up window.
This is the code that I have for the message box:
def update_msgbox(self):
self.msg = QMessageBox(self.centralwidget)
self.msg.setWindowTitle(" Software Update")
self.msg.setText("A software update is available. \nDo you want to update now?")
self.msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
self.msg.setStyleSheet("QLabel{min-width: 200px;}")
self.msg.exec_()
and this is the way the popup window currently looks:
Is there a way to center the text and to center the buttons?
A possible solution is to remove (or delete if necessary) the elements and add them back to the layout:
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QMessageBox, QLabel, QDialogButtonBox
class MessageBox(QMessageBox):
def __init__(self, parent=None):
super().__init__(parent)
grid_layout = self.layout()
qt_msgboxex_icon_label = self.findChild(QLabel, "qt_msgboxex_icon_label")
qt_msgboxex_icon_label.deleteLater()
qt_msgbox_label = self.findChild(QLabel, "qt_msgbox_label")
qt_msgbox_label.setAlignment(Qt.AlignCenter)
grid_layout.removeWidget(qt_msgbox_label)
qt_msgbox_buttonbox = self.findChild(QDialogButtonBox, "qt_msgbox_buttonbox")
grid_layout.removeWidget(qt_msgbox_buttonbox)
grid_layout.addWidget(qt_msgbox_label, 0, 0, alignment=Qt.AlignCenter)
grid_layout.addWidget(qt_msgbox_buttonbox, 1, 0, alignment=Qt.AlignCenter)
def main():
app = QApplication([])
msg = MessageBox()
msg.setWindowTitle("Software Update")
msg.setText("A software update is available.<br>Do you want to update now?")
msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
msg.setStyleSheet("QLabel{min-width: 200px;}")
msg.exec_()
if __name__ == "__main__":
main()

how to remove title bar only when it is not floated in QDockWidget

I want to hide title bar in dockwidget when it is not floated.
dock = QDockWidget()
dock.setTitleBarWidget(QWidget())
dock.titleBarWidget().hide()
this is hide the title bar of dockwidget
but when it is floated, It doesn't show title bar
You're aware, that when you hide the Title Bar of docked QDockWidget, it is not movable anymore, right?
The matter is not that simple. I ended up on stiching some events together:
I have Arrange (Edit) mode and View (normal) mode. In edit mode the Title bars are visible to allow drag&drop of the panels as requried. A toolbar button takes care of switching the modes. In your case you can asume View mode (arrange mode off) only.
When panel is undocked and form is not set to edit mode, undocked panel's title bar will be reset to None, resulting in showing the window borders, while the docked panels have hidden Title Bar.
When undocked panels gets docked, if the form is in view mode, the title bar is set to the widget itself, thus hiding it. topLevelChanged signal is used to trigger the check and change.
The result is that windows are arrangable in many ways, docked or undocked, with clean user experience. Note that while in the example you can set tabs closable, the close buttons are not handled in the example.
Here is fully functional test app:
from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QMainWindow, QTextEdit, QDockWidget, QToolBar, QTabWidget, QAction, QLayout, QTabBar
import PyQt5.QtWidgets
from PyQt5.QtGui import QIcon, QPixmap
#from PyQt5.QtCore import QMainWindow
_DOCK_OPTS = PyQt5.QtWidgets.QMainWindow.AllowNestedDocks
_DOCK_OPTS |= PyQt5.QtWidgets.QMainWindow.AllowTabbedDocks
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setWindowTitle("Test")
tb = self.addToolBar("Test")
self.ShowTitleBars = False
secondQMainWindow = QMainWindow()
self.setTabPosition (Qt.LeftDockWidgetArea, QTabWidget.North)
self.central = secondQMainWindow
self.setDockOptions(_DOCK_OPTS)
self.dw1 = QDockWidget("One")
self.dw1.topLevelChanged.connect(self.HideTitleBar)
self.dw1.setTitleBarWidget(QWidget(self.dw1))
textArea = QTextEdit()
textArea.setText("Text area 1")
self.dw1.setWidget(textArea)
self.dw2 = QDockWidget("Two")
textArea2 = QTextEdit()
textArea2.setText("Text area 2")
self.dw2.setWidget(textArea2)
self.dw2.topLevelChanged.connect(self.HideTitleBar)
self.dw2.setTitleBarWidget(QWidget(self.dw2))
self.addDockWidget(Qt.LeftDockWidgetArea, self.dw1)
self.addDockWidget(Qt.RightDockWidgetArea, self.dw2)
self.tabifyDockWidget(self.dw1, self.dw2)
self.dw3 = QDockWidget("Three")
self.dw3.topLevelChanged.connect(self.HideTitleBar)
self.dw3.setTitleBarWidget(QWidget(self.dw3))
textArea3 = QTextEdit()
textArea3.setText("Text area 3")
self.dw3.setWidget(textArea3)
self.addDockWidget(Qt.LeftDockWidgetArea, self.dw3)
barOn = QAction("Tabs closable",self)
barOn.triggered.connect(self.CountTabWidgetsInTopWindow)
tb.addAction(barOn)
barOff = QAction("Anotherbar",self)
#barOff.triggered.connect(self.ToogleTwo)
tb.addAction(barOff)
barTog = QAction("Toggle Title Bars",self)
barTog.setCheckable(True)
barTog.triggered.connect(self.ToogleTitles2)
tb.addAction(barTog)
def ToogleTwo(self):
self.dw1.setTitleBarWidget(QWidget(self.dw1))
self.dw2.setTitleBarWidget(QWidget(self.dw2))
self.dw3.setTitleBarWidget(QWidget(self.dw3))
print("Test OFF")
def ToogleTitles(self):
#self.dw1.setTitleBarWidget(self.dw1)
self.dw1.setTitleBarWidget(None)
self.dw2.setTitleBarWidget(None)
self.dw3.setTitleBarWidget(None)
print("Test ON")
def SetTabsClosable(self):
for widget in self.children():
if isinstance(widget, QTabBar):
widget.setTabsClosable(True)
def CountTabWidgetsInTopWindow(self):
for widget in self.children():
if isinstance(widget, QTabBar):
widget.setTabsClosable(True)
else:
print("QTabWidget " + widget.objectName() + " -- " + widget.__class__.__name__)
print("Counted.")
def HideTitleBar(self):
dockw = self.sender()
if dockw.isFloating() == False and self.ShowTitleBars == False:
dockw.setTitleBarWidget(QWidget(dockw))
def ToogleTitles2(self):
if self.ShowTitleBars == True:
self.ShowTitleBars = False
else:
self.ShowTitleBars = True
for widget in self.children():
if isinstance(widget, QDockWidget):
if widget.titleBarWidget() == None and self.ShowTitleBars == False:
if widget.isFloating() == False:
widget.setTitleBarWidget(QWidget(widget))
else:
widget.setTitleBarWidget(None)
print("Test Toggle")
if __name__ == '__main__':
import sys
app = PyQt5.QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
app.exec_()
in qt C++, but i think it is the same in py:
Initially a floating window will have the following flags:
QFlags<Qt::WindowType>(Tool|X11BypassWindowManagerHint|WindowTitleHint|WindowSystemMenuHint|CustomizeWindowHint|WindowCloseButtonHint)
You will need to set the Qt::CustomizeWindowH and remove the other flags on the dock widget.
Then:
You need to set the flags Qt:Tool and respectively the Qt:FramelessWindowHint (depending on the system visu you use - need also the X11 flag to set)
QFlags<Qt::WindowType>(Tool|X11BypassWindowManagerHint|FramelessWindowHint)
Setting the flag will need to be done using the method setWindowFlags:https://doc.qt.io/qt-5/qwidget.html#windowFlags-prop
Additionally there is another method: setWindowFlag(flag,bool):https://doc.qt.io/qt-5/qwidget.html#setWindowFlag

How to display self defined QSplitter Class onto QMainWindow

I am currently trying to display a split screen view onto my window using PyQt5 with QSplitter and QMainWindow. I need QMainWindow to be able to create the menu options for the application that I am working on and would need to use QSplitter to use three separate windows for the application. Currently based on my code, the window is showing up, but there is no split screen shown at all. The only thing on the display is a blank screen when there should be the numbers: 1, 2, 3, and 4 shown in each corner.
I have tried to implement the QSplitters into the main window but then found that that approach does not work as it cannot set the layout of the QSplitters on top of setting the layout of the main window from QMainWindow. The next approach I used was the class based model where I defined what exactly I wanted for each splitter and implemented the subwindows into the main window and used two splitters (horizontal and vertical).
import sys
#imports
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QAction, QVBoxLayout, QStackedWidget
from PyQt5.QtWidgets import QMessageBox, QFrame, QSplitter, QTextEdit, QHBoxLayout, QLineEdit, QLabel
from PyQt5.QtGui import QKeySequence
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtGui import QIcon #to import icon
'''
class widget1(QWidget):
"""docstring for widget1"""
def __init__(self):
QWidget.__init__(self)
hbox = QHBoxLayout()
#create split screen
#this is the top left frame of the window
topleft = QFrame()
topleft.setFrameShape(QFrame.StyledPanel)
#first splitter
splitter1 = QSplitter(Qt.Horizontal)
lineedit = QLineEdit()
splitter1.addWidget(topleft)
splitter1.addWidget(lineedit)
splitter1.setSizes([200,200])
#second splitter and it is located at the bottom of the screen
bottom = QFrame()
bottom.setFrameShape(QFrame.StyledPanel)
splitter2 = QSplitter(Qt.Horizontal)
#add the splitter to the layout
hbox.addWidget(splitter2)
'''
class SubWindow(QWidget):
def __init__(self, label):
super(SubWindow, self).__init__()
self.label = QLabel(label)
self.label.setAlignment(Qt.AlignCenter)
self.label.setStyleSheet("QLabel {font-size:40px;}")
self.main_layout = QVBoxLayout()
self.main_layout.addWidget(self.label)
self.setLayout(self.main_layout)
#GUI class to create window
class GUI(QMainWindow):
"""docstring for GUI"""
def __init__(self,):
# Understand and explain ...
super().__init__()
#initialize the UI using the initUI method
self.initUI()
def initUI(self):
#set the title of the window
self.setWindowTitle('Visualization')
#Set the status bar in the beginning
self.statusBar().showMessage("In Progress")
#create new menubar object at top of window
menubar = self.menuBar()
#add file button to menubar
file_menu = menubar.addMenu("File")
#add edit button to menubar
edit_menu = menubar.addMenu("Edit")
#add view button to menubar
view_menu = menubar.addMenu("View")
#path for new icon and create the icon
new_icon = QIcon('newfileicon.png')
open_icon =QIcon('openfileicon.png')
exitapp_icon = QIcon('exitappicon.png')
#create new subbutton for the file menu and add icon and shortcuts
#as well as connecting the methods for each file menu
new_action = QAction(new_icon,'New', self)
new_action.setShortcut('Ctrl+N')
new_action.triggered.connect(self.newCall)
open_action = QAction(open_icon,'Open', self)
open_action.setShortcut('Ctrl+O')
open_action.triggered.connect(self.openCall)
exit_action = QAction(exitapp_icon,'Exit', self)
exit_action.setShortcut('Ctrl+X')
exit_action.triggered.connect(self.exitCall)
#add the action to the file menu button
file_menu.addAction(new_action)
file_menu.addAction(open_action)
file_menu.addAction(exit_action)
#when hovering over the "new", "open", and "exit", update statusbar
new_action.setStatusTip("Create New File")
open_action.setStatusTip("Open a file")
exit_action.setStatusTip("Exit application")
#when clicking the exit, close the application
exit_action.triggered.connect(self.close)
self.SubWindow1 = SubWindow("1")
self.SubWindow2 = SubWindow("2")
self.SubWindow3 = SubWindow("3")
self.SubWindow4 = SubWindow("4")
self.subsplitter1 = QSplitter(Qt.Horizontal)
self.subsplitter1.addWidget(self.SubWindow1)
self.subsplitter1.addWidget(self.SubWindow2)
self.subsplitter2 = QSplitter(Qt.Horizontal)
self.subsplitter2.addWidget(self.SubWindow3)
self.subsplitter2.addWidget(self.SubWindow4)
self.subsplitter = QSplitter(Qt.Vertical)
self.subsplitter.addWidget(self.subsplitter1)
self.subsplitter.addWidget(self.subsplitter2)
self.main_layout = QVBoxLayout()
self.main_layout.addWidget(self.subsplitter)
self.setLayout(self.main_layout)
#hbox = widget1()
#self.view = QHBoxLayout(self)
#resize the window to a 900x800 window
self.resize(900, 800)
def newCall(self):
QMessageBox.about(self, "Confirmation", "Are you sure you want to create a new file?")
def openCall(self):
QMessageBox.about(self, "Confirmation", "Are you sure you want to open a file?")
#if yes to the messagebox, then close, else dont close and pass
def exitCall(self):
reply = QMessageBox.question(self, "Confirmation", "Are you sure you want to exit the application?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
self.close
sys.exit()
else:
pass
#main function
if __name__ == '__main__':
app = QApplication(sys.argv)
gui = GUI()
gui.show()
sys.exit(app.exec_())
I expected there to be the results of having four split screens with the values of "1", "2", "3", and "4" on each corner of the screen. Instead the output is what I have gotten before which is just a blank screen with the menubar and status bar being functional.
QMainWindow unlike QWidget already has a predefined layout:
So you should not use a layout to set the QSplitter but use the setCentralWidget() method of QMainWindow:
# ...
self.subsplitter = QSplitter(Qt.Vertical)
self.subsplitter.addWidget(self.subsplitter1)
self.subsplitter.addWidget(self.subsplitter2)
self.setCentralWidget(self.subsplitter)
self.resize(900, 800)
# ...

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