Pyside Changing Layouts - qt

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)

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_()

How to define the icon of a Dock in pyqtgraph?

In pyqtgraph, Docks can be torn out of the DockArea by dragging or double clicking. The popups use a default icon. I would like to define my own icon. In the code below I set the application window. The same code has no effect on the dock, though there is no error message.
import sys
from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout
from pyqtgraph.dockarea import Dock, DockArea
from PyQt5.QtGui import QIcon
class Foo(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowIcon(QIcon('direction'))
lay = QVBoxLayout(self)
da = DockArea()
d = Dock("Dock")
d.setWindowIcon(QIcon('direction')) # no effect
da.addDock(d)
lay.addWidget(da)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = Foo()
w.show()
sys.exit(app.exec_())
I looked in the source code of Dock and DockArea of pyqtgraph and found out I had to overwrite floatDock function.
I created a function
def floatDockPatched(self, dock):
"""Removes *dock* from this DockArea and places it in a new window."""
area = self.addTempArea()
area.win.resize(dock.size())
area.win.setWindowIcon(QIcon("res/haip.png"))
area.win.setWindowTitle(dock.label.text())
area.moveDock(dock, 'top', None)
and assigned it to the class as overwrite
DockArea.floatDock = floatDockPatched

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 - 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

How to set background color to entire widget with stylesheet in PySide

I am trying to set a background color of a widget, but it only applies to widget's children. The code below is a simple representation of the real app structure. I'd like testWidget to be entirely red, which is 100x100 pixel rectangle due to it's size, but for some reason only the button is red.
from PySide import QtGui
class Widget(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
mainLayout = QtGui.QVBoxLayout(self)
testWidget = QtGui.QWidget()
testWidget.setFixedSize(100,100)
testWidget.setStyleSheet('background-color: red;')
testLayout = QtGui.QVBoxLayout()
testWidget.setLayout(testLayout)
but = QtGui.QPushButton('TEST')
but.setFixedSize(20,20)
testLayout.addWidget(but)
mainLayout.addWidget(testWidget)
w = Widget()
w.show()
By default, a QWidget does not fill its background. You can either use a QFrame instead or setting the WA_StyledBackground attribute of the QWidget to True as said here : PySide: QWidget does not draw background color.
To apply the style sheet only to the container, and not to its children, the container widget can be named and the style sheet can specifically be applied to it by referring to its name.
Below is a MWE, derived from your code, that shows how it can be done using a QFrame instead of a QWidget :
from PySide import QtGui
import sys
class Widget(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
mainLayout = QtGui.QVBoxLayout(self)
testWidget = QtGui.QFrame()
testWidget.setFixedSize(100,100)
testWidget.setObjectName("myWidget")
testWidget.setStyleSheet("#myWidget {background-color:red;}")
testLayout = QtGui.QVBoxLayout()
testWidget.setLayout(testLayout)
but = QtGui.QPushButton('TEST')
testLayout.addWidget(but)
mainLayout.addWidget(testWidget)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
instance_1 = Widget()
instance_1.show()
sys.exit(app.exec_())
which results in:

Resources