By default if a cell is edited in a QTreeWidget, the editor changes its width based on length of text.
Is it possible to set the editorΒ΄s width to fill the cell?
Here is the code to reproduce the screenshot:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Example(QTreeWidget):
def __init__(self):
super().__init__()
self.resize(600, 400)
self.setHeaderLabels(['Col1', 'Col2', 'Col3', 'Col4'])
self.setRootIsDecorated(False)
self.setAlternatingRowColors(True)
self.setSelectionBehavior(QAbstractItemView.SelectItems)
# self.setSelectionMode(QAbstractItemView.SingleSelection)
self.setStyleSheet('QTreeView { show-decoration-selected: 1;}')
for i in range(5):
item = QTreeWidgetItem(['hello', 'bello'])
item.setFlags(item.flags() | Qt.ItemIsEditable)
self.addTopLevelItem(item)
def main():
app = QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You can create a simple QStyledItemDelegate and override its updateEditorGeometry() in order to always resize it to the index rectangle:
class FullSizedDelegate(QStyledItemDelegate):
def updateEditorGeometry(self, editor, opt, index):
editor.setGeometry(opt.rect)
class Example(QTreeWidget):
def __init__(self):
# ...
self.setItemDelegate(FullSizedDelegate(self))
** UPDATE **
The default text editor for all item views is an auto expanding QLineEdit, which tries to expand itself to the maximum available width (the right edge of the viewport) if the text is longer than the visual rectangle of the item. In order to avoid this behavior and always use the item rect, you have to return a standard QLineEdit. In this case the updateGeometry override is usually not necessary anymore (but I'd keep it anyway, as some styles might still prevent that):
class FullSizedDelegate(QStyledItemDelegate):
def createEditor(self, parent, opt, index):
if index.data() is None or isinstance(index.data(), str):
return QLineEdit(parent)
return super().createEditor(parent, opt, index)
Related
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_()
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_())
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()
I have a main window which contains a main widget, to which a vertical layout is set. To the layout is added a QTableWidget only (for the moment).
When I start the application and call show on the main_window, only part of the QTableWidget is shown. I can extend the window manually to see it all, but I would like the window to have its size nicely adapted to the size of the QTableWidget.
Googling the question found a lot of posts on how to use resize to an arbitrary size, and call to resize(int) works fine, but this is not quite what I am asking
Lots of other posts are not explicit enough, e.g "use sizePolicy" or "use frameGeometry" or "use geometry" or "use sizeHint". I am sure all of them may be right, but an example on how to would be awesome.
You can do something like this, from within your MainWindow after placing all the elements you need in the layout:
self.setFixedSize(self.layout.sizeHint())
This will set the size of the MainWindow to the size of the layout, which is calculated using the size of widgets that are arranged in the layout.
I think overriding sizeHint() on the QTableWidget is the key:
import sys
from PyQt5.QtCore import QSize
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableWidget
class Table(QTableWidget):
def sizeHint(self):
horizontal = self.horizontalHeader()
vertical = self.verticalHeader()
frame = self.frameWidth() * 2
return QSize(horizontal.length() + vertical.width() + frame,
vertical.length() + horizontal.height() + frame)
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
top = Table(3, 5, self)
self.setCentralWidget(top)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
You can use sizeHint() but not as stated in the other answers. sizeHint() returns a QSize object with a width and height. Let's say you have a main window mainWindow and a widget inside it called content. If your resizing involves content height to get bigger, you can fit the mainWindow to it like this:
mainWindow.resize(mainWindow.sizeHint().width,
mainWindow.size().height() + content.sizeHint().height());
Old but i experienced this a while back and seeing how the answers here didn't exactly work for me.
Here's what i did:
Please make sure you have the central widget for the 'mainwindow' set properly and the parent of the layout is the central widget,
Then set a sizepolicy for the mainwindow/widget as you wish.
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class RandomWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(RandomWidget, self).__init__(parent)
self.layout = QtWidgets.QVBoxLayout()
self.setLayout(self.layout)
self.ui()
self.layout.addWidget(self.table)
self.layout.addWidget(self.table2)
def ui(self):
self.table = QtWidgets.QTableWidget()
self.table.setMinimumSize(800,200)
self.table2 = QtWidgets.QTableWidget()
class Mainwindow(QtWidgets.QMainWindow):
def __init__(self):
self.widget = None
super(Mainwindow, self).__init__()
self.setWindowTitle('test')
def ui(self):
self.setCentralWidget(self.widget)
self.show()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Window = Mainwindow()
Window.widget = RandomWidget(Window)
Window.ui()
sys.exit(app.exec_())
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: