Widget inside QListWidgetItem disappears after internal move - python-3.x

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

Related

Add more than one icon in a treeview field with python 3 and tkinter

I know how to add an icon in the #0 column using the tags keyword :
icon = ImageTk.PhotoImage(Image.open('path/to/icon.gif')
myTreeView.tag_configure('tag_with_this_icon', image=icon)
# some loop
myTreeView.insert( [...],tags='tag_with_this_icon')
I would need to add an additional icon, or add an icon in any other column of the treeview, is it possible with tkinter ?
or do I need to tweak my icon gif files with two icons in the same icon file (all possible permutation)
or stich another treeview to the left of the first treeview with one column/empty text jsu for a second icon ?
seems a bad hack... any better ideas appreciated, thanks.
A way would be to use GTK3 rather than tkinter, a valid example below. OP ask for a solution with tkinter, but OP is me and did not find its way with it, so...
from gi.repository import Gtk, Gdk, GdkPixbuf
class MyWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
self.set_default_size(200, 200)
self.liststore = Gtk.ListStore(str, str, str)
self.treeview = Gtk.TreeView(model=self.liststore)
self.liststore.append(["dot_red16x16.png", "This is a symbol1","dot_green16x16.png"])
px_renderer = Gtk.CellRendererPixbuf()
px_column = Gtk.TreeViewColumn('')
px_column.pack_start(px_renderer, False)
str_renderer = Gtk.CellRendererText()
px_column.pack_start(str_renderer, False)
px_renderer2 = Gtk.CellRendererPixbuf()
px_column.pack_start(px_renderer2, False)
# set data connector function/method
px_column.set_cell_data_func(px_renderer, self.get_tree_cell_pixbuf,0)
px_column.set_cell_data_func(px_renderer2, self.get_tree_cell_pixbuf,2)
px_column.set_cell_data_func(str_renderer, self.get_tree_cell_text)
self.treeview.append_column(px_column)
self.add(self.treeview)
def get_tree_cell_text(self, col, cell, model, iter, user_data):
cell.set_property('text', model.get_value(iter, 1))
def get_tree_cell_pixbuf(self, col, cell, model, iter, user_data):
cell.set_property('pixbuf', GdkPixbuf.Pixbuf.new_from_file(model.get_value(iter, user_data)))
if __name__ == '__main__':
win = MyWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
line 11, replace dot_red16x16.png and dot_green16x16.png names with your own icon files and save this snipped aside the icon files.
gtk readthedocs : https://python-gtk-3-tutorial.readthedocs.io
side note :
actually the harder (on windows) is to setup GTK3 to work. Here is two ways I tested :
Run gcc python from msys and setup everything (ok with Python 3.8.1) :
https://www.gtk.org/download/windows.php
https://pygobject.readthedocs.io/en/latest/getting_started.html#windows-getting-started
The lazy way with Cpython 3.4.4 for windows, plus pygi-aio-3.24.1, a huge pack of gtk ressources :
https://sourceforge.net/projects/pygobjectwin32/files

What's difference setData, setItemData and setIem method of QStandardItemModel?

I am confused how to use setData, setItemData and setItem method of QStandardItemModel, these method seem have the same effect, i wonder to know which method should i choose to use will be best?
class DemoD(QMainWindow):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
table = QTableView()
model = QStandardItemModel(4, 2)
table.setModel(model)
# delegate = SpinBoxDelegate()
# table.setItemDelegate(delegate)
for row in range(4):
for col in range(2):
item = QStandardItem('china')
model.setItem(row, col, item)
index = model.index(row, col)
value = QVariant((row + 1)*(col + 1))
model.setData(index, value)
model.setItemData(index, {1: 'a', 2: 'b'})
self.setCentralWidget(table)
self.resize(400, 300)
app = QApplication([])
demo = DemoD()
demo.show()
app.exec()
If you want to understand the concepts of a Qt model you should read the following guides:
Model/View Programming
Model/View Tutorial
Why Qt is misusing model/view terminology?
Documentation of each method: setData(), setItemData() and setItem().
Previous concepts:
QStandarItemModel: It is a class that inherits from QAbstractItemModel that allows to store any type of information unlike QAbstractItemModel that only defines the behavior.
Considering that you have read the previous links carefully, we will try to explain the difference between the different methods that you indicate:
setData(): Every Qt model inherits from QAbstractItemModel so this class defines the general behavior, in this case it is defined that the setData() model is responsible for modifying the information of a role associated with a QModelIndex. In other words, it is the generic method that you have to implement if you want to implement an editable model, for example QStringListModel is not an editable model so it does not implement it but in the case of QStandardItemModel it is editable so you can modify the information of the model through of that method.
setItem(): QStandardItem is a concept of QStandardItemModel that is conceptually similar to QModelIndex. This element allows you to easily interact with the QModelIndex. If a QStandardItem is not associated with a model it will only store the information, at the time a model is assigned all information is passed to the model, and the model informs you of any changes that can be made by other methods such as setData. An equivalent to setData of the model is the setData method of QStandardItem but the latter does not need to provide the QModelIndex since that information is internally available or can be obtained when a model is established.
For example:
it.setText("foo")
it.setTextAlignment(QtCore.Qt.AlignCenter)
is equivalent to
it.model().setData(it.index(), "foo", QtCore.Qt.DisplayRole)
it.model().setData(it.index(), QtCore.Qt.AlignCenter, QtCore.Qt.TextAlignmentRole)
As you can see, QStandardItem allows you to modify the information of the item in a simple way, and in a simple way you can say that it is an item of the model.
setItemData(): It is a method that allows you to modify the information of several roles associated to a QModelIndex by checking if the roles are valid, in general if you use an invalid model the method will not update the information but in the case of QStandardItemModel that handles the generic information it is established that everything Role is valid for what will always work.
In the case of QStandardItemModel the following codes are equivalent:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
model = QtGui.QStandardItemModel(1, 1)
it = QtGui.QStandardItem()
model.setItem(0, 0, it)
# The following lines modify the text shown
# to be related to the Qt::DisplayRole role and
# the QModelIndex associated with the QStandardItem:
it.setText("foo")
it.setData("foo", QtCore.Qt.DisplayRole)
model.setData(it.index(), "foo", QtCore.Qt.DisplayRole)
model.setItemData(it.index(), {QtCore.Qt.DisplayRole: "foo"})
# The same as the previous lines but in the case of
# the background and the text colors of the item.
it.setForeground(QtGui.QColor("red"))
it.setBackground(QtGui.QColor("blue"))
it.setData(QtGui.QColor("red"), QtCore.Qt.ForegroundRole)
it.setData(QtGui.QColor("blue"), QtCore.Qt.BackgroundRole)
model.setData(it.index(), QtGui.QColor("red"), QtCore.Qt.ForegroundRole)
model.setData(it.index(), QtGui.QColor("blue"), QtCore.Qt.BackgroundRole)
model.setItemData(
it.index(),
{
QtCore.Qt.ForegroundRole: QtGui.QColor("red"),
QtCore.Qt.BackgroundRole: QtGui.QColor("blue"),
},
)
Both setData and setItemData are very similar.
What you have to understand is that Qt models use roles to assign certain data to each "index". This means that each index (a reference to a model row and column, possibly including a parent if the model supports trees) can have different data attached to it. The most commonly used data role is the "DisplayRole", which is what an item view usually shows as text; but other data is usually implemented (see ItemDataRole, which helps an item view to correctly show the model data to the user.
The most important difference between setData and setItemData is the mapping. What you're doing does not work as the keywords you're using are not recognized as usable roles.
In your example ({1: 'a', 2: 'b'}), 1 maps to DecorationRole (which is used from item views to show a decoration - an icon) and 2 maps to EditRole, which is used whenever the user wants to edit the contents of that item, something that can differ from what's displayed (think about entering a date in a short form such as "10/11", that can be an actual date that is finally shown as "november 10 2019").
Finally, setItem is a special function of QStandardItemModel that creates a new item (or overwrites an existing one) with the new provided QStandardItem object.
I'm providing a test example that will better show what happens in all three situations.
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout(self)
self.table = QtWidgets.QTableView()
layout.addWidget(self.table)
# hide headers, we're not interested
self.table.horizontalHeader().setVisible(False)
self.table.verticalHeader().setVisible(False)
self.table.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
self.model = QtGui.QStandardItemModel()
self.table.setModel(self.model)
for item in range(1, 6):
item = QtGui.QStandardItem('item {}'.format(item))
self.model.appendRow(item)
toolLayout = QtWidgets.QHBoxLayout()
layout.addLayout(toolLayout)
self.itemTextEdit = QtWidgets.QLineEdit('text')
toolLayout.addWidget(self.itemTextEdit)
self.itemSetTextButton = QtWidgets.QPushButton('Set text')
toolLayout.addWidget(self.itemSetTextButton)
self.itemSetTextButton.clicked.connect(self.setText)
toolLayout.addSpacing(5)
self.itemAlignCombo = QtWidgets.QComboBox()
toolLayout.addWidget(self.itemAlignCombo)
for alignText in ('Left', 'Center', 'Right'):
alignment = QtCore.Qt.AlignVCenter | getattr(QtCore.Qt, 'Align{}'.format(alignText))
self.itemAlignCombo.addItem(alignText, alignment)
self.itemSetAlignButton = QtWidgets.QPushButton('Set alignment')
toolLayout.addWidget(self.itemSetAlignButton)
self.itemSetAlignButton.clicked.connect(self.setAlignment)
self.table.setCurrentIndex(self.model.index(0, 0))
toolLayout.addSpacing(5)
self.setDataButton = QtWidgets.QPushButton('SetItemData()')
toolLayout.addWidget(self.setDataButton)
self.setDataButton.clicked.connect(self.setItemData)
setItemLayout = QtWidgets.QHBoxLayout()
layout.addLayout(setItemLayout)
self.itemRowSpin = QtWidgets.QSpinBox()
setItemLayout.addWidget(self.itemRowSpin)
self.itemRowSpin.setRange(1, self.model.rowCount() + 1)
self.itemRowSpin.setValue(self.itemRowSpin.maximum())
self.setItemButton = QtWidgets.QPushButton('SetItem()')
setItemLayout.addWidget(self.setItemButton)
self.setItemButton.clicked.connect(self.setItem)
def setText(self):
# set the text of the current item
index = self.table.currentIndex()
self.model.setData(index, self.itemTextEdit.text())
def setAlignment(self):
# set the alignment of the current item
index = self.table.currentIndex()
self.model.setData(index, self.itemAlignCombo.currentData(), QtCore.Qt.TextAlignmentRole)
def setItemData(self):
# set *both* text and alignment of the current item
index = self.table.currentIndex()
self.model.setItemData(index, {
QtCore.Qt.DisplayRole: self.itemTextEdit.text(),
QtCore.Qt.TextAlignmentRole: self.itemAlignCombo.currentData()
})
def setItem(self):
# set a new item for the selected row with the selected text and alignment
item = QtGui.QStandardItem()
item.setText(self.itemTextEdit.text())
item.setTextAlignment(QtCore.Qt.Alignment(self.itemAlignCombo.currentData()))
self.model.setItem(self.itemRowSpin.value() - 1, 0, item)
self.itemRowSpin.setMaximum(self.model.rowCount() + 1)
self.itemRowSpin.setValue(self.itemRowSpin.maximum())
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

Python3 + Pillow + QT5: Crash when I resize a label containing an image

I'm getting a message box: "Python has stopped working" when I load an image into a QLabel in a window that is already visible. Selecting debug shows: an unhandled Win32 exception occurred in Python.exe.
If I load the image into the label before showing the window, it displays correctly.
Here's the stripped down code:
#!/usr/bin/etc python
import sys
import os
import stat
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PIL import *
from PIL.ImageQt import *
def update(label):
filename = r"C:\Users\me\Pictures\images\00000229.jpg"
im1 = Image.open(filename)
print ("Read ({},{})".format(im1.width, im1.height))
im2 = im1.rotate(90, expand=True)
print("Rotate ({},{})".format(im2.width, im2.height))
im2.thumbnail((1200,1200))
print("Thumbnail({},{})".format(im2.width, im2.height))
qimage = ImageQt(im2)
pixmap = QPixmap.fromImage(qimage)
label.setPixmap(pixmap)
app = QApplication(sys.argv)
desktop = QDesktopWidget()
deskGeometry = desktop.availableGeometry()
print("desktop ({},{})".format(deskGeometry.width(), deskGeometry.height()))
window = QFrame()
# If you let QT pick the sizes itself, it picks dumb ones, then complains
# 'cause they are dumb
window.setMinimumSize(300, 200)
window.setMaximumSize(deskGeometry.width(), deskGeometry.height())
label = QLabel()
#call update here: no crash
caption = QLabel()
caption.setText("Hello world")
box = QVBoxLayout()
box.addWidget(label)
box.addWidget(caption)
#call update here, no crash
window.setLayout(box)
#call update here, no crash
window.show()
#this call to update results in a crash
update(label)
#window.updateGeometry()
print("App: exec")
app.exec_()
Output:
desktop (3623,2160)
Read (1515,1051)
Rotate (1051,1515)
Thumbnail(832,1200)
App: exec
Do I need to do anything special to tell QT that the window size will be changing? Any suggestions for diagnosing the problem from here...
Update:
If I copy the body of the update function and paste it in place of the call to update, it no long crashes -- it works as expected.
From this I conclude that there is an object-lifetime issue. Somewhere behind the scenes QT and/or Pillow is keeping a pointer to an internal buffer rather than making a copy or "stealing" the buffer. When the object containing the buffer is deleted the pointer becomes invalid and "Bad Things Happen[TM]"
Now to determine who's being lazy...
I found a solution based on the observation mentioned in the Update that this appeared to be an object lifetime issue.
Changing the line in the update function from
pixmap = QPixmap.fromImage(qimage)
to
pixmap = QPixmap.fromImage(qimage).copy()
forces a copy of the pixmap. This copy apparently has its own data buffer rather than borrowing the buffer from the Image.
The Label then keeps a reference to the pixmap -- ensuring the lifetime of the buffer. The 'bug' seems to be that QPixmap.fromImage captures a pointer to the data in the Image, but does not keep a reference to the Image so if the Image gets garbage collected (which is likely 'cause it's a big object, the Label (and the pixmap) have a pointer to unallocated memory.
[This 'pointer to the buffer' stuff is sheer speculation on my part, but the bottom line is the program no longer crashes.]
In case anybody else stumbles upon this:
I struggled with a very similar issue and was able to solve it using slots and signals (based on this great article) since the .copy() solution did not work for me. My solution was to separate the creation of the QPixmap and the QLabel.setPixmap into different functions. When the function that creates the QPixmap finishes, it emits a signal that triggers the function that sets the pixmap to the label.
For instance, if you wanted to use threading:
class WorkerSignals(QObject):
ready = pyqtSignal()
class Worker(QRunnable):
def __init__(self, some_function, *args, **kwargs):
super(Worker, self).__init__()
self.some_function = some_function
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
self.kwargs['ready'] = self.signals.ready
#pyqtSlot()
def run(self):
self.some_function(*self.args, **self.kwargs)
def make_pixmap(qimage, ready, **kwargs):
pixmap = QPixmap.fromImage(qimage) # qimage is some QImage
ready.emit()
def set_pixmap(pixmap):
label.setPixmap(pixmap) # 'label' is some QLabel
threadpool = QThreadPool
worker = Worker(make_pixmap, image)
worker.signals.ready.connect(set_pixmap)
threadpool.start(worker)
Obviously using threading is not necessary in this scenario, but this is just to show that it's possible in case you want to process and show images without hanging the rest of the GUI.
Edit: The crashes came back, disregard the above. Working on another fix.

QDockWidget hide title bar

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

Separate user interaction from programmical change: PyQt, QComboBox

I have several QComboBoxes in my PyQt4/Python3 GUI and they are filled with some entries from a database during the initialisation. Initial CurrentIndex is set to 0. There is also a tick box which changes the language of the items in my combo boxes. To preserve current user selection I backup index of the current item and setCurrentIndex to this number after I fill in ComboBox with translated items. All those actions emit currentIndexChanged signal.
Based on the items selected in QComboBoxes some plot is displayed. The idea is to redraw the plot online - as soon as the user changes any of ComboBox current item. And here I have a problem since if I redraw the plot every time signal currentIndexChanged is emited, I redraw it also several times during initialization and if the translation tick box selection was changed.
What is the best way to separate these cases? In principle I need to separate programmical current Index Change from the user, and update the plot only in the later case (during GUI initialisation I can programically call update plot function once). Should I write/rewrite any signal? If so, I never did that before and would welcome any hint or a good example. Use another signal? Or maybe there is a way to temporary block all signals?
There are a few different things you can try.
Firstly, you can make sure you do all your initialization before you connect up the signals.
Secondly, you could use the activated signal, which is only sent whenever the user selects an item. (But note that, unlike currentIndexChanged, this signal is sent even if the index hasn't changed).
Thirdly, you could use blockSignals to temporarily stop any signals being sent while the current index is being changed programmatically.
Here's a script that demonstrates these possibilities:
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QVBoxLayout(self)
self.combo = QtGui.QComboBox()
self.combo.setEditable(True)
self.combo.addItems('One Two Three Four Five'.split())
self.buttonOne = QtGui.QPushButton('Change (Default)', self)
self.buttonOne.clicked.connect(self.handleButtonOne)
self.buttonTwo = QtGui.QPushButton('Change (Blocked)', self)
self.buttonTwo.clicked.connect(self.handleButtonTwo)
layout.addWidget(self.combo)
layout.addWidget(self.buttonOne)
layout.addWidget(self.buttonTwo)
self.changeIndex()
self.combo.activated['QString'].connect(self.handleActivated)
self.combo.currentIndexChanged['QString'].connect(self.handleChanged)
self.changeIndex()
def handleButtonOne(self):
self.changeIndex()
def handleButtonTwo(self):
self.combo.blockSignals(True)
self.changeIndex()
self.combo.blockSignals(False)
def changeIndex(self):
index = self.combo.currentIndex()
if index < self.combo.count() - 1:
self.combo.setCurrentIndex(index + 1)
else:
self.combo.setCurrentIndex(0)
def handleActivated(self, text):
print('handleActivated: %s' % text)
def handleChanged(self, text):
print('handleChanged: %s' % text)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

Resources