Change text when button is clicked - qt

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.

Related

Custom title bar with dockable toolbar

I want to create a custom title bar for my PyQt application, and my application uses dockable toolbars. To be dockable, the toolbars should be added to the MainWindow. However, with my custom toolbar being a Widget added to a frameless window, the toolbars dock themselves around the title bar. They can be docked above the title bar, and whenever docked on the sides, they push the title bar, which is not the expected behavior. I understand that this is due to the fact the the toolbar areas are always around the central widget of the window, and my custom title bar is inside the central widget. However, I don't see how I can make this work the way I want. Here is a MWE (I'm using PyQt=5.12.3) :
import sys
from typing import Optional
from PyQt5.QtCore import Qt, QSize, QPoint
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QMainWindow, QApplication, QVBoxLayout, QPushButton, QWidget, QToolBar, QHBoxLayout, QLabel, \
QToolButton
class CustomTitleBar(QWidget):
def __init__(self, title: str, parent: Optional[QWidget] = None):
super().__init__(parent=parent)
self.window_parent = parent
layout = QHBoxLayout()
self.setObjectName("CustomTitleBar")
self.setLayout(layout)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
self.setFixedHeight(40)
self.title = title
self.title_label = QLabel(self.title)
self.title_label.setObjectName("TitleBarLabel")
layout.addWidget(self.title_label)
layout.addStretch(1)
but_minimize = QToolButton()
but_minimize.setText("🗕")
but_minimize.setObjectName("MinimizeButton")
layout.addWidget(but_minimize)
but_minimize.clicked.connect(self.window().showMinimized)
self.but_resize = QToolButton()
if self.window().isMaximized():
self.but_resize.setText("🗗")
else:
self.but_resize.setText("🗖")
layout.addWidget(self.but_resize)
self.but_resize.clicked.connect(self.toggle_maximized)
self.but_resize.setObjectName("ResizeButton")
but_close = QToolButton()
but_close.setText("🗙")
layout.addWidget(but_close)
but_close.clicked.connect(self.window().close)
but_close.setObjectName("CloseButton")
self.m_pCursor = QPoint(0, 0)
self.moving = False
def toggle_maximized(self):
if self.window().isMaximized():
self.but_resize.setText("🗖")
self.window().showNormal()
else:
self.but_resize.setText("🗗")
self.window().showMaximized()
def mousePressEvent(self, event: QMouseEvent) -> None:
pass
def mouseDoubleClickEvent(self, event: QMouseEvent) -> None:
pass
def mouseMoveEvent(self, event: QMouseEvent) -> None:
pass
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
pass
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowSystemMenuHint)
self.resize(QSize(800, 600))
main_widget = QWidget(self)
self.setCentralWidget(main_widget)
layout = QVBoxLayout(self)
main_widget.setLayout(layout)
titlebar = CustomTitleBar("Custom TitleBar Test Window", self)
layout.addWidget(titlebar)
layout.addWidget(QPushButton("Hello world"))
layout.addStretch(1)
my_toolbar = QToolBar(self)
self.addToolBar(Qt.RightToolBarArea, my_toolbar)
my_toolbar.addWidget(QPushButton("A"))
my_toolbar.addWidget(QPushButton("B"))
my_toolbar.addWidget(QPushButton("C"))
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
The resulting window is as follow:
How can I get the dockable toolbars to behave around my custom title bar the way they should behave around a standard title bar ?
Since the title bar should be put outside the standard contents, you cannot put it inside the central widget.
The solution is to setContentsMargins() using the height of the title bar for the top margin. Then, since the title bar is not managed by any layout, you need to resize it by overriding the resizeEvent():
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.titlebar = CustomTitleBar("Custom TitleBar Test Window", self)
self.setContentsMargins(0, self.titlebar.sizeHint().height(), 0, 0)
def resizeEvent(self, event):
super().resizeEvent(event)
self.titlebar.resize(self.width(), self.titlebar.sizeHint().height())
As I guess, firstly you need to look QDockWidget Because you put your CustomTitleBar into centeralWidget in MainWindow and it states in Docking area, this is expected behaviour.
You need to create a Vertical Layout which would your , and put CustomTitleBar into it, you don't need MainWindow in your code. In main you can try something like that:
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QWidget()
layout = QHBoxLayout()
titlebar = CustomTitleBar("Custom TitleBar Test Window", self)
layout.addWidget(titlebar)
window.setLayout(layout)
main_widget = QWidget(self)
layout.addWidget(main_widget)
main_widget_layout = QVBoxLayout()
main_widget.setLayout(main_widget_layout)
my_toolbar = QToolBar(self)
main_widget.addToolBar(Qt.RightToolBarArea, my_toolbar)
my_toolbar.addWidget(QPushButton("A"))
my_toolbar.addWidget(QPushButton("B"))
my_toolbar.addWidget(QPushButton("C"))
main_widget.addWidget(QPushButton("Hello world"))
main_widget_layout.addStretch(1)
layout.addStretch(1)
window.show()
app.exec_()

Showing/hiding of a widget on focus at another widget

I would like "Button" object to disappear when "Target" object is not in focus (for example, when object "Secondary" is focused) and to re-appear when "Target" is in focus again. So, "Target" focused = "Button" visible. In other words, in the code below there are two lines, "Line A" and "Line B", that I would like to implement in the code.
`
import sys
from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QLineEdit
class Wn(QWidget):
def __init__(self):
super().__init__()
self.target = Target("Target", self)
self.target.setFixedSize(400, 60)
self.target.move(50, 50)
self.secondary = QLineEdit("Secondary", self)
self.secondary.setFixedSize(400, 60)
self.secondary.move(50, 150)
self.button = QPushButton("Appears # Target focused. Disappears # target not focused", self)
self.button.setFixedSize(400, 60)
self.button.move(50, 250)
class Target(QLineEdit):
def focusInEvent(self, k):
print("The target is in focus: Button should be shown")
self.setStyleSheet("background-color: red;")
# Wn.button.setHidden(False) # Line A
def focusOutEvent(self, p):
print("The target is out of focus: Button should be hidden")
self.setStyleSheet("background-color: white;")
# Wn.button.setHidden(True) # Line B
app = QApplication(sys.argv)
wn = Wn()
wn.show()
sys.exit(app.exec())
`
You can create a signal and emit it whenever the focus changes, then connect it with the button's setVisible().
class Wn(QWidget):
def __init__(self):
# ...
self.target.focusChanged.connect(self.button.setVisible)
class Target(QLineEdit):
focusChanged = pyqtSignal(bool)
def focusInEvent(self, k):
super().focusInEvent(k)
print("The target is in focus: Button should be shown")
self.setStyleSheet("background-color: red;")
self.focusChanged.emit(True)
def focusOutEvent(self, p):
super().focusOutEvent(p)
print("The target is out of focus: Button should be hidden")
self.setStyleSheet("background-color: white;")
self.focusChanged.emit(False)
Alternatively, you can just install an event filter on the line edit and look for FocusIn and FocusOut events.
Note that you should always call the base implementation of event handler overrides, unless you really know what you're doing, otherwise you might prevent proper default behavior of the object.
Also, layout managers should always be used instead of fixed geometries. Since the visibility of a widget also nullifies its size in the layout and adapts the other widgets managed by it (similarly to display: none in CSS), you should probably consider using setRetainSizeWhenHidden() for the widget's size policy:
class Wn(QWidget):
def __init__(self):
# ...
# create a proper layout and add widgets
# ...
policy = self.button.sizePolicy()
policy.setRetainSizeWhenHidden(True)
self.button.setSizePolicy(policy)

PyQt - Manually resizing menu widget does not resize its content

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

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