QDockWidget hide title bar - python-3.x

I can't find how to hide the title bar of my Qdockwidgets :
I have found this on the references :
QDockWidget.setTitleBarWidget (self, QWidget widget)
The widget argument has it's ownership transferred to Qt.
Sets an arbitrary widget as the dock widget's title bar. If widget is 0, any custom title bar widget previously set on the dock widget is removed, but not deleted, and the default title bar will be used instead.
And this in quite a few places :
dockWidget->setTitleBarWidget(new QWidget());
but how to actually use them ? I simply can't find any simple example of code showing those in action not even in the references and all of my tries ended up with various errors.
In the whole page : http://pyqt.sourceforge.net/Docs/PyQt4/qdockwidget.html#setTitleBarWidget there is simply nothing which tell me how I can use this :
QDockWidget.setTitleBarWidget (self, QWidget widget)
If I read the documentation I should replace the "widget" one by a 0 so I get no title bar displayed
self.dockWdg1.setTitleBarWidget(self, QtGui.QWidget(0))# If I don't add QtGui in front of QWidget it don't know what is QWidget
give me this error :
TypeError: QWidget(QWidget parent=None, Qt.WindowFlags flags=0): argument 1 has unexpected type 'int'
Another question, is there actually people using this references : ? http://pyqt.sourceforge.net/Docs/PyQt4/qdockwidget.html#setTitleBarWidget
My question can seem weird but I find thoses references so incomplete (no code example) and the explanations so obscures I really wonder if I am looking on the right one, especially when on the other side we have this : http://srinikom.github.io/pyside-docs/PySide/QtGui/QDockWidget.html#PySide.QtGui.QDockWidget with pySide which seems so so so so so much better. PyQT references succeed in being even worst than MaxScript references which is not a little thing to do.
My complete code :
import sys, random
from PyQt4 import QtCore, QtGui
from PyQt4.QtGui import QPalette, QBrush, QPixmap
class MainWin(QtGui.QMainWindow):
def __init__(self):
super(MainWin, self).__init__()
self.initUI()
def initUI(self):
#central widget
self.theboard = Board(self)
self.setCentralWidget(self.theboard)
#dock1 Left Dock
self.dockWdg1 = LeftDock(self)
#Use SizeQWidget() to define the initial size of QDockWidget
self.content1 = SizeQWidget()
self.dockWdg1.setWidget(self.content1)
self.dockWdg1.setFeatures(self.dockWdg1.NoDockWidgetFeatures)
self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.dockWdg1)
self.dockWdg1.setTitleBarWidget(self, QtGui.QWidget(0))# If I don't add QtGui in front of QWidget it don't know what is QWidget
self.resize(360, 760)
self.setWindowTitle('Test')
self.show()
class LeftDock(QtGui.QDockWidget):
def __init__(self, parent):
super(LeftDock, self).__init__(parent)
self.initLeftDock()
def initLeftDock(self):
self.setGeometry(300, 300, 200, 120)
#self.setWindowTitle('LeftDock')
class Board(QtGui.QFrame):
def __init__(self, parent):
super(Board, self).__init__(parent)
self.initBoard()
def initBoard(self):
print("ddd")
#Dummy QWidget used by QDockWidget for defining initial size.
class SizeQWidget(QtGui.QWidget):
def sizeHint(self):
return QtCore.QSize(100, 75)
def main():
app = QtGui.QApplication([])
mw = MainWin()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

The PyQt docs are automatically created from the C++ docs. I always just use the C++ docs.
The docs are thus not of very good overall quality.
Now to your main issue at hand.
Note that QtGui.QWidget takes a QObject* as first parameter (the parent of the newly created object). Also note that a null pointer is represented in Python by None, not 0.
Then we need to know that self is usually implicitly passed by calling the method on an instance. (self in the context of PyQt is just like every other self you will ever encounter in Python).
Thus, this works:
self.dockWdg1.setTitleBarWidget(QtGui.QWidget(None))
Personally I would do this a little bit different:
self.dockWdg1.setTitleBarWidget(QtGui.QWidget(self.dockWdg1))
That is, setting the parent of the title bar widget to the dock widget itself, preserving a nice object hierarchy.
I might also remark that "removing" the title bar widget of a dock widget by replacing it with an invisible (size is zero) widget it becomes impossible to float the dock widget.

i use this to hide them all in c++
for (auto &dock:findChildren<QDockWidget*>())
dock->setTitleBarWidget(new QWidget);

Related

Widget inside QListWidgetItem disappears after internal move

I have a QListWidget which is populated by QLabel via .setItemWidget() and a drag and drop mode InternalMove, when I move an item inside the list its label disappears.
How can I solve this issue?
A minimal example to reproduce
from PyQt5.QtWidgets import (
QApplication, QLabel, QStyle,
QListWidget, QListWidgetItem
)
from PyQt5.QtCore import QSize
import sys
if __name__ == '__main__':
app = QApplication(sys.argv)
list = QListWidget()
list.setFixedHeight(400)
list.setDragDropMode(QListWidget.DragDropMode.InternalMove)
for _ in range(8):
item = QListWidgetItem()
item.setSizeHint(QSize(40, 40))
list.addItem(item)
label = QLabel()
label.setPixmap(list.style().standardIcon(
QStyle.StandardPixmap.SP_ArrowUp).pixmap(QSize(40,40)))
list.setItemWidget(item, label)
list.show()
sys.exit(app.exec())
edit
After reading the documentation for the .setItemWidget() which states:
This function should only be used to display static content in the place of a list widget item. If you want to display custom dynamic content or implement a custom editor widget, use QListView and subclass QStyledItemDelegate instead.
I wonder if this is related to the issue and what does "static content" mean in this context, is QLabel considered "dynamic content"?
edit #2
The problem is inside a dropEvent() a dropMimeData() is called which in turn creates a complete new item? (rowsInserted is called), which isn't supposed to happen for self items I guess, because a widget set in the dragged item isn't serialized and stored inside mimedata so the widget is decoupled, The dropMimeData() is usually called when you drag and drop items from a different list.
So I guess an ugly way to solve this is to store a manually serialized widget inside a QListWidget.mimeData() as a custom mimetype via QMimeData.setData() and recreate the widget after a drop inside QListWidget.dropMimeData().
for example:
from PyQt5.QtWidgets import (
QApplication, QLabel, QStyle,
QListWidget, QListWidgetItem
)
from PyQt5.QtCore import QSize, QMimeData, QBuffer, QIODevice
from PyQt5.QtGui import QPixmap
import pickle
import sys
class ListWidget(QListWidget):
def mimeData(self, items:list[QListWidgetItem]) -> QMimeData:
mimedata = QListWidget.mimeData(self, items)
# e.g. serialize pixmap
custommime = []
for item in items:
label:QLabel = self.itemWidget(item)
buff = QBuffer()
buff.open(QIODevice.OpenModeFlag.WriteOnly)
label.pixmap().save(buff, 'PNG')
buff.close()
custommime.append(buff.data())
mimedata.setData('application/custommime', pickle.dumps(custommime))
#
return mimedata
def dropMimeData(self, index:int, mimedata:QMimeData, action) -> bool:
result = QListWidget.dropMimeData(self, index, mimedata, action)
# e.g. recreate pixmap
if mimedata.hasFormat('application/custommime'):
for i, data in enumerate(
pickle.loads(mimedata.data('application/custommime')),
start=index):
pixmap = QPixmap()
pixmap.loadFromData(data, 'PNG')
label = QLabel()
label.setPixmap(pixmap)
self.setItemWidget(self.item(i), label)
#
return result
if __name__ == '__main__':
app = QApplication(sys.argv)
list = ListWidget()
list.setFixedHeight(400)
list.setDragDropMode(QListWidget.DragDropMode.InternalMove)
list.setSelectionMode(QListWidget.SelectionMode.ExtendedSelection)
for i in range(8):
item = QListWidgetItem()
item.setSizeHint(QSize(40, 40))
list.addItem(item)
label = QLabel()
label.setPixmap(list.style().standardIcon(
QStyle.StandardPixmap.SP_DialogOkButton + i).pixmap(QSize(40,40)))
list.setItemWidget(item, label)
list.show()
sys.exit(app.exec())
This is caused by a Qt bug which only affects fairly recent versions. I can consistently reproduce it when using Qt-5.15.6 and Qt-6.4.0 - but not e.g. Qt-5.12.1. The issue seems to be closely related to QTBUG-100128.
UPDATE:
Unfortunately, after some further experimentation today, it seems the suggested work-around given below isn't an effective solution. I have found it's also possible to make item-widgets disappear by drag and drop onto non-empty areas.
After testing some other versions of Qt5, I can confirm that the bug is completely absent in 5.12.x, 5.13.x, 5.14.x, 5.15.0 and 5.15.1. This agrees with the existing Qt bug report above which identified Qt-5.15.2 as the version where the bug was introduced.
Contrary to what is suggested in the question, there's no reason whatsoever why a label should not be used as an item-widget. The term "static content", just means "not updated by user-defined custom drawing".
UPDATE 2:
This bug seems to be a regression from QTBUG-87057, which made quite a large number of internal changes to how list-view rows are moved during drag and drop. The complexity of those change probably means a simple work-around that undoes its negative side-effects probably isn't possible. The changes affect all Qt5 versions greater than 5.15.1 and Qt6 versions greater than 6.0.
AFAICS, this only affects dragging and dropping the current last item in the view onto a blank area. Other items and multiple selections aren't affected. This suggests the following work-around:
class ListWidget(QListWidget):
def dropEvent(self, event):
if (self.currentRow() < self.count() - 1 or
self.itemAt(event.pos()) is not None):
super().dropEvent(event)
list = ListWidget()
...
or using an event-filter:
class Monitor(QObject):
def eventFilter(self, source, event):
if event.type() == QEvent.Drop:
view = source.parent()
if (view.currentRow() == view.count() - 1 and
view.itemAt(event.pos()) is None):
return True
return super().eventFilter(source, event)
monitor = Monitor()
list = QListWidget()
list.viewport().installEventFilter(monitor)
...

PyQt/PySide How to access/move QGraphicsItem after having added it to QGraphicsScene

This might be a very uninformed question.
I've been trying to figure out QGraphics*, and have run into a problem when trying to move an item (a pixmap) relative to or inside of the QGraphicsView.
class MainWindow(QMainWindow,myProgram.Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.scene = QGraphicsScene()
self.graphicsView.setScene(self.scene)
pic = QPixmap('myPic.png')
self.scene.addPixmap(pic)
print(self.scene.items())
This is the relevant part of the program with an arbitrary PNG
My goal, for example, would be to move the trash-can to the left-most part of the QGraphicsView.
I tried appending this to the code above:
pics = self.scene.items()
for i in pics:
i.setPos(100,100)
However, it doesn't make a difference, and even if it did, it would be awkward to have to search for it with a "for in".
So my questions are:
How do I access a QPixmap item once I have added it to a QGraphicsView via a QGraphicsScene. (I believe The QPixmap is at this point a QGraphicsItem, or more precisely a QGraphicsPixmapItem.)
Once accessed, how do I move it, or change any of its other attributes.
Would be very grateful for help, or if anyone has any good links to tutorials relevant to the question. :)
The problem is that you need to set the size of your scene and then set positions of the items, check this example:
from PyQt4 import QtGui as gui
app = gui.QApplication([])
pm = gui.QPixmap("icon.png")
scene = gui.QGraphicsScene()
scene.setSceneRect(0, 0, 200, 100) #set the size of the scene
view = gui.QGraphicsView()
view.setScene(scene)
item1 = scene.addPixmap(pm) #you get a reference of the item just added
item1.setPos(0,100-item1.boundingRect().height()) #now sets the position
view.show()
app.exec_()

PySide/PyQt: Is it possible to make strings that you attach to the QTextBrowser separate clickable units

This might be a silly question but:
When you append a given string to a QTextBrowser object, can you make it a link to a signal to a function that takes its text and does something with it? All I need is for it to save the text to a variable actually.
As in, can a link lead to a function instead of to a website.
It certainly is possible.
Here is a code example:
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
main_layout = QtGui.QVBoxLayout()
self.browser = QtGui.QTextBrowser()
self.browser.setHtml('''<html><body>some text<br/>click me to call a function<br/>
Click me to scroll down<br>foo<br>foo<br>foo<br>foo<br>foo<br>foo<br>
foo<a id="my_anchor"></a><br>bar<br>bar<br>bar<br>bar<br>bar<br>bar<br>hello!<br>hello!<br>hello!<br>hello!<br>hello!<br>hello!<br>hello!<br>hello!</body></html''')
self.browser.anchorClicked.connect(self.on_anchor_clicked)
main_layout.addWidget(self.browser)
self.setLayout(main_layout)
def on_anchor_clicked(self,url):
text = str(url.toString())
if text.startswith('some_special_identifier://'):
self.browser.setSource(QtCore.QUrl()) #stops the page from changing
function = text.replace('some_special_identifier://','')
if hasattr(self,function):
getattr(self,function)()
def a_function(self):
print 'you called?'
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Any link that has a url that begins with "some_special_identifier://" will be picked up and the text after that will be used to look up and call a function of the same name. Please note that this could be a little risky as there is the potential for all sorts of functions to be called that perhaps you don't intend, if the user has any control over what is displayed in the TextBrowser. It might be better to only allow certain functions to be run, and perhaps only at certain times. That is of course up to you to enforce!
P.S. my code is written for Python 2.7 (I see you are using Python 3). So I think you will need to change print 'text' to print('text') at the very least!

(Py)Qt: Best practice for managing Custom QWidget's layout created on the fly

I have a QMainWindow having various widgets, loaded with loadUI. One of those widgets is a QGroupBox which is empty at start (No layout either).
I also have a custom widget (CustomWidgetContainer) that creates various widgets (CustomWidget) within itself. How many and which is determined at runtime.
The problem is that CustomWidgetContainer has its own layout to add its widgets to. But when the CustomWidgetContainer gets added to the QMainWindow, the layout is replaced with the parenting layout of the QMainWindow (which is expected and documented behaviour).
But when this CustomWidgetContainer needs its own layout (self.layout()) to add additional CustomWidgets to or to remove all the CustomWidgets, self.layout() returns None.
I can imagine all kind of workaround checking to see if the parent already has a layout (use that one, set one in the parent etc) and such. But I don't want to do anything with my parent (QMainWindow) from my child (CustomWidgetContainer) class as I consider this bad practice.
Snippets:
ScalarInputEdit == CustomWidget, InputsWidget == CustomWidgetContainer
class MainWindow(qt.QMainWindow):
....
def connect(self, host, port):
self._client = PymotClient(host, port)
self.client.connect()
self._set_enabled_connected()
self.log.info("Connected to %s:%d", host, port)
self._inputswidget = InputsWidget(self, self.client)
print "Layout Before:", self._inputswidget.layout()
self.inputsBox.setLayout(self._inputswidget.layout())
print "Layout After:", self._inputswidget.layout()
self._inputswidget.append_inputs_from_client()
class InputsWidget(qt.QWidget):
def __init__(self, parent, client):
super(InputsWidget, self).__init__(parent)
....
self.setLayout(qt.QGridLayout())
def append_inputs_from_client(self):
for inp in some_list:
self.append_input(inp)
def append_input(self, pbo):
self.layout().addWidget(ScalarInputEdit(self, self.client, pbo))
def remove_all_inputs(self):
for child in self.layout().children():
child.deleteLater()
Output:
Layout Before: <PyQt4.QtGui.QGridLayout object at 0x8880ecc>
Layout After: None
Exception:
File "...inputwidgets.py", line 134, in append_input
self.layout().addWidget(ScalarInputEdit(self, self.client, pbo))
AttributeError: 'NoneType' object has no attribute 'addWidget'
What is standard/good practice to do this? It's seems quite common for me to need your layout() in some later stage (after __ init__). But as some usage cases seem to replace MY layout, or worse even remove it, how can I be sure of a layout to add/remove to/from?
Quote:
when the CustomWidgetContainer gets added to the QMainWindow, the layout is
replaced with the parenting layout of the QMainWindow (which is expected and
documented behaviour).
This is very unclear to me: what documented behaviour are you referring to?
self._inputswidget = InputsWidget(self, self.client)
print "Layout Before:", self._inputswidget.layout()
self.inputsBox.setLayout(self._inputswidget.layout())
Why are you setting the layout of inputsBox to the layout of _inputswidget?
print "Layout After:", self._inputswidget.layout()
The layout of _inputswidget will quite naturally be None now, because you have just transferred it to inputsBox.
self._inputswidget.append_inputs_from_client()
Obviously, this can't work, because you took away the layout of _inputswidget, and didn't replace it.

Signal/Slot help-- setting a signal to a slot outside of the current class

I'm trying to populate a table (present in the main window) from a slider that's located in a widget in a separate class. I can't seem to get it to work...what's the best way to go about doing this?
Here's my current code:
class Widget(QWidget):
def __init__(self,filename,parent=None):
super(Widget,self).__init__(parent)
self.resize(900,900)
self.layout=QVBoxLayout(self)
frame=Frame(filename)
self.image=pg.ImageView()
self.image.setImage(frame.data)
self.image.setCurrentIndex(0)
fileheader=FileHeader(filename)
self.slider=QSlider(self)
self.slider.setOrientation(Qt.Horizontal)
self.slider.setMinimum(1)
self.slider.setMaximum(fileheader.numframes)
self.slider.sliderMoved.connect(self.sliderMoved)
self.layout.addWidget(self.image)
self.layout.addWidget(self.slider)
def sliderMoved(self,val):
print "slider moved to:", val
fileheader=FileHeader(filename)
idx=val
frame=fileheader.frameAtIndex(idx)
self.image.setImage(frame.data)
class MainWindow(QMainWindow):
def __init__(self, filename, parent=None):
super(MainWindow,self).__init__(parent)
self.initUI(filename)
def initUI(self,filename):
self.filetable=QTableWidget()
self.frametable=QTableWidget()
self.imageBrowser=Widget(filename)
self.imagesplitter=QSplitter(Qt.Horizontal)
self.tablesplitter=QSplitter(Qt.Horizontal)
self.imagesplitter.addWidget(self.imageBrowser)
self.tablesplitter.addWidget(self.imagesplitter)
self.tablesplitter.addWidget(self.filetable)
self.tablesplitter.addWidget(self.frametable)
self.setCentralWidget(self.tablesplitter)
exitAction=QAction(QIcon('exit.png'),'&Exit',self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(qApp.quit)
openAction=QAction(QIcon('open.png'),'&Open',self)
openAction.setShortcut('Ctrl+O')
menubar=self.menuBar()
fileMenu=menubar.addMenu('&File')
fileMenu.addAction(exitAction)
fileMenu.addAction(openAction)
self.fileheader=FileHeader(filename)
self.connect(self.frametable,
SIGNAL("Widget.sliderMoved(idx)"),
self.fileheader.frameAtIndex(idx))
self.frameheader=self.fileheader.frameAtIndex(0)
self.populate()
def populate(self):
self.filetable.setRowCount(len(self.fileheader.fileheader_fields))
self.filetable.setColumnCount(2)
self.filetable.setHorizontalHeaderLabels(['File Header','value'])
for i,field in enumerate(self.fileheader.fileheader_fields):
name=QTableWidgetItem(field)
value=QTableWidgetItem(unicode(getattr(self.fileheader,field)))
self.filetable.setItem(i,0,name)
self.filetable.setItem(i,1,value)
self.frametable.setRowCount(len(self.frameheader.frameheader_fields))
self.frametable.setColumnCount(2)
self.frametable.setHorizontalHeaderLabels(['Frame Header','Value'])
for i,fields in enumerate(self.frameheader.frameheader_fields):
Name=QTableWidgetItem(fields)
Value=QTableWidgetItem(unicode(getattr(self.frameheader,fields)))
self.frametable.setItem(i,0,Name)
self.frametable.setItem(i,1,Value)
I know the "connect" is wrong-- I'm very new to PyQt and Python in general, so I'm not quite sure where to start.
Since self.imageBrowser is your Widget class, it will have the slider attribute which has the sliderMoved signal. You just need a few more dots.
self.imageBrowser.slider.sliderMoved.connect(self.fileheader.frameAtIndex)
The way you have it organized is correct though. Your main window composes your custom widgets and binds the connections together.
Though because you have a data source, and also a QTableWidget that will need to be updated, you probably need to wrap the steps up into a little method:
def initUI(self,filename):
...
self.imageBrowser.slider.sliderMoved.connect(self._handle_slider_moved)
# initialize it the first time during the window set up
self._handle_slider_moved(0)
def _handle_slider_moved(self, val):
# update the data source
self.fileheader.frameAtIndex(val)
# update the second data source
self.frameheader=self.fileheader.frameAtIndex(0)
# now refresh the tables
self.populate()

Resources