PyQt - Manually resizing menu widget does not resize its content - qt

I am using a QToolButton to open up a menu, which is a simple QWidget. Inside this widget live a QTextEdit and a Button. At the lower right corner there is a QSizeGrip which I want to use to let the user resize the widget and thus the QTextEdit.
If I use this widget on its own inside a MainWindow (Option1) everything works as expected. If however I put this widget into the menu (Option2), I cannot resize it anymore. Dragging the QSizeGrip changes the size of the Menu but not the Widget. I have already experimented with setWindowFlags(QtCore.Qt.SubWindow) and setSizePolicy(..) without any notable effect.
My Question is: How do I make the widget (together with the TextEdit) resizable?
Here is the code and below a picture.
import sys
from PyQt4 import QtGui, QtCore
class MyWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self.setLayout(QtGui.QVBoxLayout())
self.TextEdit = QtGui.QTextEdit()
self.Button = QtGui.QPushButton("Push")
self.UpdateWidget = QtGui.QWidget()
self.UpdateWidget.setLayout(QtGui.QHBoxLayout())
self.UpdateWidget.layout().addWidget(self.Button, 1)
self.UpdateWidget.layout().addWidget(QtGui.QSizeGrip(self), 0)
self.layout().addWidget(self.TextEdit)
self.layout().addWidget(self.UpdateWidget)
self.layout().setSpacing(0)
self.layout().setContentsMargins(0,0,0,0)
self.UpdateWidget.layout().setSpacing(4)
self.UpdateWidget.layout().setContentsMargins(0,0,0,0)
# This is what I already tried to make the menu resizable:
#self.setWindowFlags(QtCore.Qt.SubWindow)
#self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
#self.TextEdit.setWindowFlags(QtCore.Qt.SubWindow)
#self.TextEdit.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
class ToolBar(QtGui.QWidget):
def __init__(self, parent=None):
super(ToolBar, self).__init__(parent)
self.setLayout(QtGui.QHBoxLayout())
self.Button = QtGui.QToolButton()
self.Button.setText("Open Text Editor")
self.Button.setPopupMode(QtGui.QToolButton.InstantPopup)
self.Button.setMenu(QtGui.QMenu(self.Button))
action = QtGui.QWidgetAction(self.Button)
action.setDefaultWidget(MyWidget())
self.Button.menu().addAction(action)
self.layout().addWidget(self.Button)
class App(QtGui.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent)
#Option 1:
#Use MyWidget in MainWindow. This works as expected.
#self.central = MyWidget()
#Option 2:
#Use MyWidget as default widgt for a menu action.
#In this case MyWidget cannot be resized.
self.central = ToolBar()
self.setCentralWidget(self.central)
if __name__=='__main__':
app = QtGui.QApplication(sys.argv)
thisapp = App()
thisapp.show()
sys.exit(app.exec_())

Problem is because Button.menu doesn't inform your widget about size change.
In ToolBar you can assign own function to resizeEvent which will resize your widget.
You need access to your widget
self.MyW = MyWidget()
and you can assign own function
self.Button.menu().resizeEvent = self.onResize
def onResize(self, event):
self.MyW.resize(event.size())
Full code:
import sys
from PyQt4 import QtGui, QtCore
class MyWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self.setLayout(QtGui.QVBoxLayout())
self.TextEdit = QtGui.QTextEdit()
self.Button = QtGui.QPushButton("Push")
self.UpdateWidget = QtGui.QWidget()
self.UpdateWidget.setLayout(QtGui.QHBoxLayout())
self.UpdateWidget.layout().addWidget(self.Button, 1)
self.UpdateWidget.layout().addWidget(QtGui.QSizeGrip(self), 0)
self.layout().addWidget(self.TextEdit)
self.layout().addWidget(self.UpdateWidget)
self.layout().setSpacing(0)
self.layout().setContentsMargins(0,0,0,0)
self.UpdateWidget.layout().setSpacing(4)
self.UpdateWidget.layout().setContentsMargins(0,0,0,0)
class ToolBar(QtGui.QWidget):
def __init__(self, parent=None):
super(ToolBar, self).__init__(parent)
self.setLayout(QtGui.QHBoxLayout())
self.Button = QtGui.QToolButton()
self.Button.setText("Open Text Editor")
self.Button.setPopupMode(QtGui.QToolButton.InstantPopup)
self.Button.setMenu(QtGui.QMenu(self.Button))
self.MyW = MyWidget() # <-- here
action = QtGui.QWidgetAction(self.Button)
action.setDefaultWidget(self.MyW) # <-- here
self.Button.menu().addAction(action)
self.layout().addWidget(self.Button)
self.Button.menu().resizeEvent = self.onResize # <-- here
def onResize(self, event): # <-- here
self.MyW.resize(event.size()) # <-- here
class App(QtGui.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent)
#Option 1:
#Use MyWidget in MainWindow. This works as expected.
#self.central = MyWidget()
#Option 2:
#Use MyWidget as default widgt for a menu action.
#In this case MyWidget cannot be resized.
self.central = ToolBar()
self.setCentralWidget(self.central)
if __name__=='__main__':
app = QtGui.QApplication(sys.argv)
thisapp = App()
thisapp.show()
sys.exit(app.exec_())
Maybe it can be done olny with SizeGrip and MyWidget but I didn't try

Related

Change text when button is clicked

I want to change the text on a button ( Start optimization ) to ( Cancel optimization ) when the text was clicked. So far I got:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
layout = QGridLayout()
layout.addLayout(self.optimize_button(), 1, 3, 1, 1)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
# method for widgets
def optimize_button(self):
hbox = QHBoxLayout()
button = QPushButton("", self)
button.setText("Start optimization")
button.setGeometry(200, 150, 100, 30)
button.clicked.connect(self.clickme)
#self.push.clicked.connect(self.clickme)
hbox.addWidget(button)
self.show()
return hbox
def clickme(self):
print("pressed")
self.button.setText("Cancel optimization")
I tried to make use out of https://www.geeksforgeeks.org/pyqt5-how-to-change-the-text-of-existing-push-button/ but it doesn't work.
I guess the issue lays somewhere that clicking the button is calling clickme() which doesn't know anything about that button. But I don't know how to refer accordingly.
edit:
def clickme(self):
print("pressed")
self.button = QPushButton("", self)
self.button.setText("Cancel optimization")
is not working?
Option 1: Store it as a class member
A solution is to:
Store the button as a class member during construction
Change the button that is stored as class member.
So, your simplified example would become:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
hbox = QHBoxLayout()
self.button = QPushButton("", self) // (1) store the button as a class member
button.setText("Start optimization")
button.clicked.connect(self.clickme)
hbox.addWidget(button)
widget = QWidget()
widget.setLayout(hbox)
self.setCentralWidget(widget)
self.show()
def clickme(self):
print("pressed")
self.button.setText("Cancel optimization") // (2) self.button is the button stored in (1)
Option 2: QObject::sender
As mentioned in the comments, using QObject::sender may be an alternative to obtain the clicked button inside clickme.

How to disable and re-enable QPushButton

I made MainWindow and Dialog with Qt-designer.The MainWindow and Dialog have one QPushButton. Clicking a button in the MainWindow disables the button and opens a Dialog Window. When you click the Dialog button, the Dialog window closes and the MainWindow's button is activated again.
import sys
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *
from PyQt5 import uic
form_mainwindow = uic.loadUiType("dialog_mainWindow.ui")[0]
form_dialog = uic.loadUiType("Dialog__.ui")[0]
class dialog(QDialog, form_dialog) :
def __init__(self):
super(dialog, self).__init__()
self.setupUi(self)
self.closeBtn.clicked.connect(self.close)
self.closeBtn.clicked.connect(self.closeFN)
def closeFN(self):
main = mainwindow()
main.pushButton.setEnabled(True)
class mainwindow(QtWidgets.QMainWindow, form_mainwindow) :
def __init__(self):
super(mainwindow, self).__init__()
self.setupUi(self)
self.pushButton.clicked.connect(self.dia)
def dia(self,checked):
d = dialog()
self.pushButton.setEnabled(False)
d.show()
d.exec_()
if __name__ == "__main__" :
app = QtWidgets.QApplication(sys.argv)
Window = mainwindow()
Window.show()
sys.exit(app.exec_())
This is my code. However, my code is that when the Dialog window is closed, the button in the MainWindow is not activated again. Why??
You are calling mainwindow() in your dialogs closeFN method, which creates a new mainwindow widget. So the button that you are setting to be enabled is not the same one that you used to create the dialog in the first place. You are setting a button on a new window that isn't visible yet because you never call .show() or similar method that makes top level windows visible, and you also are not keeping a reference to the new window so it would be garbage collected anyway.
A solution to this is to have your mainwindow connect to the dialogs closebtn.clicked signal which could then trigger it's own method that set's the pushbutton to be enabled.
For example:
...
class dialog(QDialog) :
def __init__(self):
super(dialog, self).__init__()
...
# self.closeFN is no longer necessary
self.closeBtn.clicked.connect(self.close)
class mainwindow(QtWidgets.QMainWindow):
def __init__(self):
super(mainwindow, self).__init__()
...
self.pushButton.clicked.connect(self.dia)
def dia(self):
d = dialog()
self.pushButton.setEnabled(False)
# connect to closeBtn signal to enable the pushButton
d.closeBtn.clicked.connect(lambda: self.pushButton.setEnabled(True))
d.exec_()
...

Change highlight color for a specific item (QAction type) on a QMenuBar

I want to mimic the behavior of the window top-right corner actions when hovering over the minimize-maximize-close buttons.
So I have created a QMenuBar that contains 3 QActions.
class WindowMenuActions(QMenuBar):
def __init__(self):
super(WindowMenuActions, self).__init__()
minimize_action = QAction(self)
minimize_action.setIcon(_MINIMIZE_ICON)
maximize_action = QAction(self)
maximize_action.setIcon(_MAXIMIZE_ICON)
exit_action = QAction(self)
exit_action.setIcon(_CLOSE_ICON)
self.addAction(minimize_action)
self.addAction(maximize_action)
self.addAction(exit_action)
And set the background color when hovering with stylesheet (it is applied to the entire QApplication):
I want the close button to have a different highlight color, so I tried setting a boolean property on the exit_action to change the color only for that specific item, but it does not do any effect (in fact, if I add the property in the stylesheet it doesn't even consider the stylesheet values)
Any idea of how to achieve this?
To extend the question, I would not only use this for the minimize-maximize-close actions, but also to understand how to apply different hover/selected colors on QActions in the QMenuBar (not in the QMenu, which I already found a solution for that).
This is an running example:
from PySide2.QtWidgets import QMenuBar, QAction, QStyle, QApplication, QMainWindow
from PySide2.QtCore import Qt
class WindowMenuActions(QMenuBar):
def __init__(self):
super(WindowMenuActions, self).__init__()
minimize_action = QAction(self)
_MINIMIZE_ICON = self.style().standardIcon(QStyle.SP_TitleBarMinButton)
_MAXIMIZE_ICON = self.style().standardIcon(QStyle.SP_TitleBarMaxButton)
_EXIT_ICON = self.style().standardIcon(QStyle.SP_TitleBarCloseButton)
minimize_action.setIcon(_MINIMIZE_ICON)
minimize_action.setProperty('exit_action', False)
maximize_action = QAction(self)
maximize_action.setProperty('exit_action', False)
maximize_action.setIcon(_MAXIMIZE_ICON)
exit_action = QAction(self)
exit_action.setProperty('exit_action', True)
exit_action.setIcon(_EXIT_ICON)
self.addAction(minimize_action)
self.addAction(maximize_action)
self.addAction(exit_action)
self.setStyleSheet(
'QMenuBar::item:selected {'
'background-color: grey;'
'}'
'QMenuBar::item[exit_action=true]:selected {'
' background-color: red;'
'}')
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
main = QMainWindow()
main.setMenuBar(WindowMenuActions())
main.show()
sys.exit(app.exec_())
To anyone interested, I sorted by inheriting from a regular QWidget instead of a QMenuBar and using QToolButton instead of QAction.
Working example:
from PySide2.QtWidgets import QMenuBar, QStyle, QApplication, QMainWindow, QWidget, QHBoxLayout, QToolButton, QMenu
from PySide2.QtCore import Qt
class MenuBar(QMenuBar):
def __init__(self):
super(MenuBar, self).__init__()
self.addMenu(QMenu('File'))
self.addMenu(QMenu('Help'))
self.setCornerWidget(WindowMenuActions(self))
class WindowMenuActions(QWidget):
def __init__(self, parent=None):
super(WindowMenuActions, self).__init__(parent)
_MINIMIZE_ICON = self.style().standardIcon(QStyle.SP_TitleBarMinButton)
_MAXIMIZE_ICON = self.style().standardIcon(QStyle.SP_TitleBarMaxButton)
_EXIT_ICON = self.style().standardIcon(QStyle.SP_TitleBarCloseButton)
minimize = QToolButton(self)
minimize.setIcon(_MINIMIZE_ICON)
maximize = QToolButton(self)
maximize.setIcon(_MAXIMIZE_ICON)
exit_action = QToolButton(self)
exit_action.setProperty('exit_button', True)
exit_action.setIcon(_EXIT_ICON)
layout = QHBoxLayout()
layout.addWidget(minimize)
layout.addWidget(maximize)
layout.addWidget(exit_action)
self.setLayout(layout)
self.setStyleSheet(
'QToolButton:hover {'
' background: grey;'
'}'
'QToolButton[exit_button=true]:hover {'
' background: red;'
'}'
)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
main = QMainWindow()
main.setWindowFlags(Qt.FramelessWindowHint)
main.setMenuBar(MenuBar())
main.show()
sys.exit(app.exec_())

PyQt button click area (Non rectangular area)

I was working on a PySide interface for Maya and i was wondering if its possible to define a NON RECTANGULAR clickeable area for a button.
I tried using QPushButton and also extending a QLabel object to get button behavior but do you know if its possible to get a button containing a picture with alpha channel and use that alpha to define the click area for a button?
I'd appreciate a lot if you can guide me through how to solve this problem.
Thanks in advance.
I've tried this...
from PySide import QtCore
from PySide import QtGui
class QLabelButton(QtGui.QLabel):
def __init(self, parent):
QtGui.QLabel.__init__(self, parent)
def mousePressEvent(self, ev):
self.emit(QtCore.SIGNAL('clicked()'))
class CustomButton(QtGui.QWidget):
def __init__(self, parent=None, *args):
super(CustomButton, self).__init__(parent)
self.setMinimumSize(300, 350)
self.setMaximumSize(300, 350)
picture = __file__.replace('qbtn.py', '') + 'mario.png'
self.button = QLabelButton(self)
self.button.setPixmap(QtGui.QPixmap(picture))
self.button.setScaledContents(True)
self.connect(self.button, QtCore.SIGNAL('clicked()'), self.onClick)
def onClick(self):
print('Button was clicked')
if __name__ == '__main__':
app = QApplication(sys.argv)
win = CustomButton()
win.show()
app.exec_()
sys.exit()
mario.png
This is the final code i get to solve my above question...
from PySide import QtCore
from PySide import QtGui
class QLabelButton(QtGui.QLabel):
def __init(self, parent):
QtGui.QLabel.__init__(self, parent)
def mousePressEvent(self, ev):
self.emit(QtCore.SIGNAL('clicked()'))
class CustomButton(QtGui.QWidget):
def __init__(self, parent=None, *args):
super(CustomButton, self).__init__(parent)
self.setMinimumSize(300, 350)
self.setMaximumSize(300, 350)
pixmap = QtGui.QPixmap('D:\mario.png')
self.button = QLabelButton(self)
self.button.setPixmap(pixmap)
self.button.setScaledContents(True)
self.button.setMask(pixmap.mask()) # THIS DOES THE MAGIC
self.connect(self.button, QtCore.SIGNAL('clicked()'), self.onClick)
def onClick(self):
print('Button was clicked')
You can do this by catching the press/release events and checking the position of the click with the value of the pixel in the image to decide if the widget should emit a click or not.
class CustomButton(QWidget):
def __init__(self, parent, image):
super(CustomButton, self).__init__(parent)
self.image = image
def sizeHint(self):
return self.image.size()
def mouseReleaseEvent(self, event):
# Position of click within the button
pos = event.pos()
# Assuming button is the same exact size as image
# get the pixel value of the click point.
pixel = self.image.alphaChannel().pixel(pos)
if pixel:
# Good click, pass the event along, will trigger a clicked signal
super(CustomButton, self).mouseReleaseEvent(event)
else:
# Bad click, ignore the event, no click signal
event.ignore()

Pyside Changing Layouts

I have a Interface that has 4 buttons across the top of the screen, and below that I have QFrame. I want to change the layout in the QFrame based on what button is pressed. For instance, if Button_1 is pressed show a TextEdit Widget, if Button_2 is pressed show a ListViewWidget.
Does anyone have any idea on how this can be done?
Thank You Very Much!
Use a QStackedLayout to store your widgets. Then you can change to the one with setCurrentIndex. Alternatively, you can use a QStackedWidget in place of your QFrame.
A simple example:
import sys
from PySide import QtGui
class Window(QtGui.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.textEdit = QtGui.QTextEdit('Text Edit')
self.listWidget = QtGui.QListWidget()
self.listWidget.addItem('List Widget')
self.label = QtGui.QLabel('Label')
self.stackedLayout = QtGui.QStackedLayout()
self.stackedLayout.addWidget(self.textEdit)
self.stackedLayout.addWidget(self.listWidget)
self.stackedLayout.addWidget(self.label)
self.frame = QtGui.QFrame()
self.frame.setLayout(self.stackedLayout)
self.button1 = QtGui.QPushButton('Text Edit')
self.button1.clicked.connect(lambda: self.stackedLayout.setCurrentIndex(0))
self.button2 = QtGui.QPushButton('List Widget')
self.button2.clicked.connect(lambda: self.stackedLayout.setCurrentIndex(1))
self.button3 = QtGui.QPushButton('Label')
self.button3.clicked.connect(lambda: self.stackedLayout.setCurrentIndex(2))
buttonLayout = QtGui.QHBoxLayout()
buttonLayout.addWidget(self.button1)
buttonLayout.addWidget(self.button2)
buttonLayout.addWidget(self.button3)
layout = QtGui.QVBoxLayout(self)
layout.addLayout(buttonLayout)
layout.addWidget(self.frame)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
Something like...
myframe.layout().removeWidget(mywidget)
mywidget = QTextEdit()
myframe.layout().addWidget(myWidget)

Resources