adding row number to QTableView in pyside2 - qt

is there any way I could add a row number beside the QTableView just for info and not actually add it into the data so that I could export it without the row number as a new column? it seems like everyone has it on default while mine is not showing at all. i tried tableView.verticalHeader().setVisible(True) but it isn't working. looked everywhere in the docs also wasn't mentioned, maybe I missed it
here's what I've tried so far
from PySide2.QtWidgets import *
from PySide2.QtGui import *
from PySide2.QtCore import *
import pandas as pd
class MyWindow(QWidget):
def __init__(self, parent=None, data=None):
QWidget.__init__(self, parent)
self.setGeometry(300, 200, 570, 450)
self.setWindowTitle('MyWindow')
data = {'name': ['abdul','budi','cindy'],'ID':[1,2,3], 'balance': [100,200,300]}
df = pd.DataFrame(data)
self.model = PandasModel(df)
self.table_view = QTableView()
self.table_view.setModel(self.model)
layout = QVBoxLayout(self)
layout.addWidget(self.table_view)
self.setLayout(layout)
self.table_view.verticalHeader().setVisible(True)
self.table_view.verticalHeader().show()
class PandasModel(QAbstractTableModel):
"""
Class to populate a table view with a pandas dataframe
"""
def __init__(self, data, parent=None):
QAbstractTableModel.__init__(self, parent)
self._data = data
def rowCount(self, parent=None):
return self._data.shape[0]
def columnCount(self, parent=None):
return self._data.shape[1]
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole:
return str(self._data.iloc[index.row(), index.column()])
return None
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._data.columns[col]
return None
if __name__ == "__main__":
app = QApplication([])
UI = MyWindow()
UI.show()
sys.exit(app.exec_())

You can just return the row number in your headerData implementation like:
QVariant MyTable::headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const
{
if (orientation == Qt::Vertical && role == Qt::DisplayRole)
return section;
return QAbstractItemModel::headerData(section, orientation, role);
}

The python approach from László Papp's answer. Turns out I have to add another if conditional statement for the vertical header as an index
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._data.columns[col]
if orientation == Qt.Vertical and role == Qt.DisplayRole:
return self._data.index[col]+1
return None

Related

Qt: A better way to fullscreen/revert QGraphicsView and QVideoWidget?

I am trying to implement fullscreen and revert functions to my multimedia viewer program, following this thread (and some other similar threads), I've reached a conclusion similar to the following MRE (the actual block of code performing/reverting the fullscreen is the elif event.key() == qtc.Qt.Key.Key_F... block inside keyPressEvent of class Stack):
from __future__ import annotations
from PySide6 import QtCore as qtc
from PySide6 import QtGui as qtg
from PySide6 import QtMultimedia as qtm
from PySide6 import QtMultimediaWidgets as qtmw
from PySide6 import QtWidgets as qtw
image_path = ""
video_path = ""
class Stack(qtw.QStackedWidget):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._full_screen_widget = None
def keyPressEvent(self, event: qtg.QKeyEvent) -> None:
if event.key() == qtc.Qt.Key.Key_N:
if not self._full_screen_widget:
if self.currentWidget() == video:
self.currentWidget().stop()
self.currentWidget().setSource("")
self.setCurrentWidget(graphics)
elif self.currentWidget() == graphics:
video.play()
self.setCurrentWidget(video)
elif (
event.key() == qtc.Qt.Key.Key_F
and event.modifiers() == qtc.Qt.KeyboardModifier.NoModifier
):
if self._full_screen_widget:
self._full_screen_widget.showNormal()
self._full_screen_widget.setWindowFlags(
self._full_screen_widget._backup_window_flags
)
self._full_screen_widget.setParent(self)
self.addWidget(self._full_screen_widget)
self.setCurrentWidget(self._full_screen_widget)
self._full_screen_widget.setFocus(qtc.Qt.FocusReason.OtherFocusReason)
self._full_screen_widget = None
else:
self._full_screen_widget = self.currentWidget()
self.removeWidget(self._full_screen_widget)
self._full_screen_widget.setWindowFlag(qtc.Qt.WindowType.Window)
self._full_screen_widget.showFullScreen()
else:
super().keyPressEvent(event)
return
event.accept()
return
class Graphics(qtw.QGraphicsView):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._backup_window_flags = self.windowFlags()
self._scene = qtw.QGraphicsScene()
self._pixmap_item = qtw.QGraphicsPixmapItem()
self._pixmap_item.setPixmap(qtg.QPixmap(image_path))
self._scene.addItem(self._pixmap_item)
self.setScene(self._scene)
def keyPressEvent(self, event: qtg.QKeyEvent):
if event.key() == qtc.Qt.Key.Key_F:
qtc.QCoreApplication.sendEvent(stack, event)
event.accept()
return
super().keyPressEvent(event)
return
class Video(qtmw.QVideoWidget):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._backup_window_flags = self.windowFlags()
loop = qtc.QEventLoop()
self._media_player = qtm.QMediaPlayer()
self._media_player.mediaStatusChanged.connect(loop.exit)
self._media_player.setVideoOutput(self)
self._media_player.setSource(video_path)
loop.exec()
def play(self):
self._media_player.play()
def stop(self):
self._media_player.stop()
def setSource(self, source: str):
self._media_player.setSource(source)
def keyPressEvent(self, event: qtg.QKeyEvent):
if event.key() == qtc.Qt.Key.Key_F:
qtc.QCoreApplication.sendEvent(stack, event)
event.accept()
return
super().keyPressEvent(event)
return
app = qtw.QApplication()
mw = qtw.QMainWindow()
stack = Stack(parent=mw)
graphics = Graphics(parent=stack)
video = Video(parent=stack)
mw.setCentralWidget(stack)
stack.addWidget(graphics)
stack.addWidget(video)
stack.setCurrentWidget(graphics)
mw.show()
app.exec()
As for why I don't just fullscreen the main window, instead of individual elements, is because the actual program's GUI has other elements to it. Hence, the main window fullscreen would contain elements other than the graphics/video widgets.

Can't change highlighted text color of custom QListView rows

For some strange reason cannot override a highlighted text colour in QListView. It worked fine (a highlighted text colour is auto-changed to white) until I defined my own widget to represent a row.
Now I can change background colour and some other visual aspects of a selected row, but text colour always remains default black.
Already tried all possible with QSS, QPalette and data()/Qt.ForegroundRole - no trick helps.
Here is a simplified code, which still suffers from the issue on OS X. Unfortunately I had no chance to test on Windows or GNU/Linux.
from PySide.QtCore import *
from PySide.QtGui import *
import sys
view = None
mapp = {}
style = '''
QListView {
show-decoration-selected: 1;
selection-color: white;
selection-background-color: #0068d9;
}
QListView::item:selected:active:hover{
background-color:red; color: white;
}
QListView::item:selected:active:!hover{
background-color: #0068d9; color: white;
}
QListView::item:selected:!active{
background-color:yellow; color: white;
}
QListView::item:!selected:hover{
background-color:green; color: white;
}
'''
class SimpleListModel(QAbstractListModel):
def __init__(self, mlist):
QAbstractListModel.__init__(self)
self._items = mlist
def rowCount(self, parent = QModelIndex()):
return len(self._items)
def index(self, row, column, parent=QModelIndex()):
node = self._items[row]
if not(str(row) in mapp):
index = self.createIndex(row, column)
widget = QLabel(node)
view.setIndexWidget(index, widget)
mapp[str(row)] = index
return index
return mapp[str(row)]
def data(self, index, role = Qt.DisplayRole):
return None
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
class MyMainWindow(QWidget):
def __init__(self):
global view
QWidget.__init__(self, None)
self._model = SimpleListModel(["test", "tes1t", "t3est", "t5est", "t3est"])
vbox = QVBoxLayout()
view = QListView()
view.setModel(self._model)
vbox.addWidget(view)
self.setLayout(vbox)
view.setStyleSheet(style)
first = self._model.index(0, 0)
view.setCurrentIndex(first)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyMainWindow()
w.show()
w.raise_()
app.exec_()
sys.exit()
Not the cleanest or best solution but this is what I came with after some tweaking.
Result:
The blue item is the selected one. The green item is the hovered one.
Code:
from PySide.QtCore import *
from PySide.QtGui import *
import sys
view = None
mapp = {}
style = '''
QListView {
show-decoration-selected: 1;
selection-color: white;
selection-background-color: #0068d9;
}
QListView::item:selected:active:hover{
background-color:red; color: white;
}
QListView::item:selected:active:!hover{
background-color: #0068d9; color: white;
}
QListView::item:selected:!active{
background-color:yellow; color: white;
}
QListView::item:!selected:hover{
background-color:green; color: white;
}
'''
class SimpleListModel(QAbstractListModel):
def __init__(self, mlist):
QAbstractListModel.__init__(self)
self._items = mlist
def rowCount(self, parent = QModelIndex()):
return len(self._items)
def index(self, row, column, parent=QModelIndex()):
node = self._items[row]
if not(str(row) in mapp):
index = self.createIndex(row, column)
widget = QLabel(node)
view.setIndexWidget(index, widget)
mapp[str(row)] = index
return index
return mapp[str(row)]
def data(self, index, role = Qt.DisplayRole):
# The following code shouldn't be put in this function but i'm in a hurry right now...
selectedIndexes = view.selectedIndexes()
# Set all items to black
for i in range(0, self.rowCount()):
currentRowIndex = self.index(i, 0, QModelIndex())
myWidget = view.indexWidget(currentRowIndex)
myWidget.setStyleSheet("color: black")
# Set selected items to white
for i in selectedIndexes:
myWidget = view.indexWidget(i)
myWidget.setStyleSheet("color: white")
return None
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
class MyMainWindow(QWidget):
def __init__(self):
global view
QWidget.__init__(self, None)
self._model = SimpleListModel(["test", "tes1t", "t3est", "t5est", "t3est"])
vbox = QVBoxLayout()
view = QListView()
view.setModel(self._model)
vbox.addWidget(view)
self.setLayout(vbox)
view.setStyleSheet(style)
first = self._model.index(0, 0)
view.setCurrentIndex(first)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyMainWindow()
w.show()
w.raise_()
app.exec_()
sys.exit()
Please let me know if there's something I didn't understand correctly or the code is not clear enough.
First:
Your code is working on GNU/Linux (Ubuntu 14.04 LTS, PyQt5) in the same way.
Since text is written to indexWidget, stylesheet for the text-color must be set for QLabel.
As QLabel does not support the hover pseudostate, it can not be done in the same way as for the items.
To set stylesheet for selected indexWidgets I used signal-slot-mechanism, for hovered indexWidgets I used event filter,
only class MyMainWindow modified:
class MyMainWindow(QWidget):
def __init__(self):
global view
QWidget.__init__(self, None)
self._model = SimpleListModel(["test", "tes1t", "t3est", "t5est", "t3est"])
vbox = QVBoxLayout()
view = QListView()
view.setModel(self._model)
view.setMouseTracking(True) # to catch mouseEvents
view.installEventFilter(self) # for events in ListView
vbox.addWidget(view)
self.setLayout(vbox)
view.setStyleSheet(style)
first = self._model.index(0, 0)
view.setCurrentIndex(first)
view.clicked.connect(self.setIndexStyle) # or any other signal
def setIndexStyle(self, index):
for i in range(0,view.model().rowCount()):
style = 'color: black;'
view.indexWidget(view.model().index(i,0)).setStyleSheet(style)
for i in view.selectedIndexes(): # works for multiseletion too
style = 'color: white;'
view.indexWidget(i).setStyleSheet(style)
def eventFilter(self,obj,event):
if event.type() == QEvent.HoverMove and isinstance(obj,QListView):
i = view.indexAt(event.pos()) # index at mouse.pos()
self.setIndexStyle(i) # selected indexWidgets still have white text
style = 'color: white;'
try: # if no item on mouse.pos()
view.indexWidget(i).setStyleSheet(style)
except AttributeError:
pass
return False
return QWidget.eventFilter(self,obj,event)

How to treat click events differently for an item's checkbox versus its text? (PyQt/PySide/Qt)

I have a QStandardItemModel in which each item is checkable. I want different slots to be called when I click on the item's checkbox, on one hand, versus when I click on its text, on the other. My ultimate goal is to have text edits, and changes in checkbox state, go onto the QUndoStack separately.
In my reimplementation of clicked I want to treat checkbox clicks and text clicks differently. So far, I have found no way to differentiate these events in the documentation for QCheckBox or QStandardItem. While QCheckBox has a toggled signal that I can use, I am not sure how to specifically listen for clicks on the text area.
I am trying to avoid having to set up coordinates manually and then listen for clicks in the different regions of the view of the item.
It doesn't seem this will be as simple as calling something like itemChanged, because that only gives you the new state of the item, not the previous state. Based on previous questions, I believe you need some way to pack the previous state into the undo stack, so you know what to revert to. That's what I am aiming to do with clicked, but there might be a better way.
This question piggybacks on the previous two in this series, in which I'm trying to figure out how to undo things in models:
How to undo edit of QStandardItem in PySide/PyQt?
How to undo an edit of a QListWidgetItem in PySide/PyQt?
Based on ekhumoro's suggestion and code nuggets, I built a treeview of a QStandardItemModel that emits a custom signal when an item changes. The code differentiates the text versus the checkbox changing via the role in setData (for text, use Qt.EditRole and for checkbox state changes use Qt.CheckStateRole) :
# -*- coding: utf-8 -*-
from PySide import QtGui, QtCore
import sys
class CommandTextEdit(QtGui.QUndoCommand):
def __init__(self, tree, item, oldText, newText, description):
QtGui.QUndoCommand.__init__(self, description)
self.item = item
self.tree = tree
self.oldText = oldText
self.newText = newText
def redo(self):
self.item.model().itemDataChanged.disconnect(self.tree.itemDataChangedSlot)
self.item.setText(self.newText)
self.item.model().itemDataChanged.connect(self.tree.itemDataChangedSlot)
def undo(self):
self.item.model().itemDataChanged.disconnect(self.tree.itemDataChangedSlot)
self.item.setText(self.oldText)
self.item.model().itemDataChanged.connect(self.tree.itemDataChangedSlot)
class CommandCheckStateChange(QtGui.QUndoCommand):
def __init__(self, tree, item, oldCheckState, newCheckState, description):
QtGui.QUndoCommand.__init__(self, description)
self.item = item
self.tree = tree
self.oldCheckState = QtCore.Qt.Unchecked if oldCheckState == 0 else QtCore.Qt.Checked
self.newCheckState = QtCore.Qt.Checked if oldCheckState == 0 else QtCore.Qt.Unchecked
def redo(self): #disoconnect to avoid recursive loop b/w signal-slot
self.item.model().itemDataChanged.disconnect(self.tree.itemDataChangedSlot)
self.item.setCheckState(self.newCheckState)
self.item.model().itemDataChanged.connect(self.tree.itemDataChangedSlot)
def undo(self):
self.item.model().itemDataChanged.disconnect(self.tree.itemDataChangedSlot)
self.item.setCheckState(self.oldCheckState)
self.item.model().itemDataChanged.connect(self.tree.itemDataChangedSlot)
class StandardItemModel(QtGui.QStandardItemModel):
itemDataChanged = QtCore.Signal(object, object, object, object)
class StandardItem(QtGui.QStandardItem):
def setData(self, newValue, role=QtCore.Qt.UserRole + 1):
if role == QtCore.Qt.EditRole:
oldValue = self.data(role)
QtGui.QStandardItem.setData(self, newValue, role)
model = self.model()
#only emit signal if newvalue is different from old
if model is not None and oldValue != newValue:
model.itemDataChanged.emit(self, oldValue, newValue, role)
return True
if role == QtCore.Qt.CheckStateRole:
oldValue = self.data(role)
QtGui.QStandardItem.setData(self, newValue, role)
model = self.model()
if model is not None and oldValue != newValue:
model.itemDataChanged.emit(self, oldValue, newValue, role)
return True
QtGui.QStandardItem.setData(self, newValue, role)
class UndoableTree(QtGui.QWidget):
def __init__(self, parent = None):
QtGui.QWidget.__init__(self, parent = None)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.view = QtGui.QTreeView()
self.model = self.createModel()
self.view.setModel(self.model)
self.view.expandAll()
self.undoStack = QtGui.QUndoStack(self)
undoView = QtGui.QUndoView(self.undoStack)
buttonLayout = self.buttonSetup()
mainLayout = QtGui.QHBoxLayout(self)
mainLayout.addWidget(undoView)
mainLayout.addWidget(self.view)
mainLayout.addLayout(buttonLayout)
self.setLayout(mainLayout)
self.makeConnections()
def makeConnections(self):
self.model.itemDataChanged.connect(self.itemDataChangedSlot)
self.quitButton.clicked.connect(self.close)
self.undoButton.clicked.connect(self.undoStack.undo)
self.redoButton.clicked.connect(self.undoStack.redo)
def itemDataChangedSlot(self, item, oldValue, newValue, role):
if role == QtCore.Qt.EditRole:
command = CommandTextEdit(self, item, oldValue, newValue,
"Text changed from '{0}' to '{1}'".format(oldValue, newValue))
self.undoStack.push(command)
return True
if role == QtCore.Qt.CheckStateRole:
command = CommandCheckStateChange(self, item, oldValue, newValue,
"CheckState changed from '{0}' to '{1}'".format(oldValue, newValue))
self.undoStack.push(command)
return True
def buttonSetup(self):
self.undoButton = QtGui.QPushButton("Undo")
self.redoButton = QtGui.QPushButton("Redo")
self.quitButton = QtGui.QPushButton("Quit")
buttonLayout = QtGui.QVBoxLayout()
buttonLayout.addStretch()
buttonLayout.addWidget(self.undoButton)
buttonLayout.addWidget(self.redoButton)
buttonLayout.addStretch()
buttonLayout.addWidget(self.quitButton)
return buttonLayout
def createModel(self):
model = StandardItemModel()
model.setHorizontalHeaderLabels(['Titles', 'Summaries'])
rootItem = model.invisibleRootItem()
item0 = [StandardItem('Title0'), StandardItem('Summary0')]
item00 = [StandardItem('Title00'), StandardItem('Summary00')]
item01 = [StandardItem('Title01'), StandardItem('Summary01')]
item0[0].setCheckable(True)
item00[0].setCheckable(True)
item01[0].setCheckable(True)
rootItem.appendRow(item0)
item0[0].appendRow(item00)
item0[0].appendRow(item01)
return model
def main():
app = QtGui.QApplication(sys.argv)
newTree = UndoableTree()
newTree.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
The clicked signal seems to be entirely the wrong way to track changes. How are you going to deal with changes made via the keyboard? And what about changes that are made programmatically? For an undo stack to work correctly, every change has to be recorded, and in exactly the same order that it was made.
If you're using a QStandardItemModel, the itemChanged signal is almost exactly what you want. The main problem with it, though, is that although it sends the item that changed, it gives you no information about what changed. So it looks like you would need to do some subclassing and emit a custom signal that does that:
class StandardItemModel(QtGui.QStandardItemModel):
itemDataChanged = QtCore.Signal(object, object, object)
class StandardItem(QtGui.QStandardItem):
def setData(self, newValue, role=QtCore.Qt.UserRole + 1):
oldValue = self.data(role)
QtGui.QStandardItem.setData(self, newValue, role)
model = self.model()
if model is not None:
model.itemDataChanged.emit(oldValue, newvValue, role)
Note that the signal will only be emitted for changes made after the item has been added to the model.

QLabel word wrap mode

I have a label that sometimes contain a long text with no spaces (path in the computer).
So word-wrap wraps it very weirdly.
Is there a way to make the word-wrap of the label break in the middle of the word or not only at white spaces?
This isn't elegant but does work...
So say header class has Private:
QLabel *thisLabel;
QString *pathName;
QString *pathNameClean;
and of course defining thisLabel some where.
so it would be nice if it was this simple....
thisLabel->setWordWrap(true);
that's fine IF AND ONLY IF the word has break points
(WHICH PATHS SHOULD AVOID)
SO keep your actual path in a separate string if you need it for QFile purposes later.
Then manually define a character per line number, and insert the spaces into the string....
so we'll say 50 chars is a good width...
pathNameClean = new QString(pathName);
int c = pathName->length();
if( c > 50)
{
for(int i = 1; i <= c/50; i++)
{
int n = i * 50;
pathName->insert(n, " ");
}
}
thisLabel->setText(pathName);
Shazam.... simulated WordWrap with no original spaces...
just remember that pathName string is now just for pretty QLabel purposes and that the pathNameClean string is the actual path. Qt programs will crash if you try to open a file with a space injected path.....
(if there's no simple class method it's likely just a few lines of code to do...
and why problem solving is a programmers best tool!)
One way is to use the QTextOption class with a QTextDocument instead of a QLabel. This let you use QTextOption::WrapMode. QTextOption::WrapAtWordBoundaryOrAnywhere should do what you want.
QLabel with other wrap mode
I happen to have this same question in 2021, so I here I will share with you some of the best answers I have found so far.
TextWrapAnywhere QLabel
Subclass QLabel and and implement the paintEvent, where you can set the text alignment to TextWrapAnywhere when you drawItemText.
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QStyleOption, QVBoxLayout, QWidget, QStyle
class SuperQLabel(QLabel):
def __init__(self, *args, **kwargs):
super(SuperQLabel, self).__init__(*args, **kwargs)
self.textalignment = Qt.AlignLeft | Qt.TextWrapAnywhere
self.isTextLabel = True
self.align = None
def paintEvent(self, event):
opt = QStyleOption()
opt.initFrom(self)
painter = QPainter(self)
self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self)
self.style().drawItemText(painter, self.rect(),
self.textalignment, self.palette(), True, self.text())
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setFixedSize(100, 200)
self.label = QLabel()
self.label.setWordWrap(True)
self.label.setText("1111111111111111111111111111")
self.slabel = SuperQLabel()
self.slabel.setText("111111111111111111111111111")
self.centralwidget = QWidget()
self.setCentralWidget(self.centralwidget)
self.mainlayout = QVBoxLayout()
self.mainlayout.addWidget(self.label)
self.mainlayout.addWidget(self.slabel)
self.centralwidget.setLayout(self.mainlayout)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Text Wrap at char instead of white space
According to this answer(in op's comment) provided by #ekhumoro, if you are looking for wrapping a line based on comma, you can insert a zero-width-space after the char you want to wrap and use the built in word wrap function.
Here is an example:
from PyQt5.QtCore import QRect, Qt
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QStyleOption, QStylePainter, QVBoxLayout, QWidget, QStyle
class CommaWrapableQLabel(QLabel):
def __init__(self, *args, **kwargs):
super(CommaWrapableQLabel, self).__init__(*args, **kwargs)
def setWordWrapAtAnychar(self, char):
newtext = self.text().replace(char, f"{char}\u200b")
self.setText(newtext)
self.setWordWrap(True)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setFixedSize(100, 200)
self.label = QLabel()
self.label.setWordWrap(True)
self.label.setText(
'Dog,Rabbit,Train,Car,Plane,Cheese,Meat,Door,Window')
self.slabel = CommaWrapableQLabel()
self.slabel.setText(
'Dog,Rabbit,Train,Car,Plane,Cheese,Meat,Door,Window')
self.slabel.setWordWrapAtAnychar(",")
self.centralwidget = QWidget()
self.setCentralWidget(self.centralwidget)
self.mainlayout = QVBoxLayout()
self.mainlayout.addWidget(self.label)
self.mainlayout.addWidget(self.slabel)
self.centralwidget.setLayout(self.mainlayout)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Black magic solution
As said from other answers, you can reimplement paintEvent() for QLabel to pass the Qt::TextWrapAnywhere flag.
However, overriding paintEvent() may cause unexpected side effects since the default implementation of paintEvent() contains a lot of extra features besides just painting the text as is.
Actually the alignment flags passed to QStyle::drawItemText is stored in a private member QLabelPrivate::align. I came up with the idea to force rewrite the value of it with the Qt::TextWrapAnywhere flag set, and it works. This workaround requires a bit of C++ black magic.
class WorkaroundLabel: public QLabel {
Q_OBJECT
public:
WorkaroundLabel(QString text, QWidget* parent): QLabel(text, parent) {
setWordWrap(true);
ushort* p;
if (FindAlignAddr(&p)) {
*p = (*p | Qt::TextWrapAnywhere);
} else {
// workaround failed
}
}
virtual ~WorkaroundLabel() {}
protected:
// "ushort align;" in qtbase/src/widgets/widgets/qlabel_p.h
bool FindAlignAddr(ushort** out) {
Qt::Alignment align = alignment();
void* d_raw = (void*) d_ptr.data();
ushort* p = reinterpret_cast<ushort*>(d_raw);
for (int i = 0; i < 1024; i += 1) {
setAlignment(Qt::AlignLeft);
ushort a = *p;
setAlignment(Qt::AlignRight);
ushort b = *p;
if (a != b) {
*out = p;
setAlignment(align);
return true;
}
p++;
}
setAlignment(align);
return false;
}
};
In 2020, PySide2, it is just:
tmp = QLabel()
tmp.setWordWrap(True)

PySide moving QGraphicsPixmapItem jumps to upper left corner of scene

I am writing an application that allows a user to place images on a QGraphicsScene (contained within a QGraphicsView) by clicking on a blank area and then move them about using mousemoveevent. The images are created using a subclassed QGraphicsPixmapItem.
Here's the problem: The very first attempt at moving an item works as expected. However, for all subsequent moves the selected item immediately jumps to the upper left corner of the scene. Here is the code:
import sys
from PySide import QtGui,QtCore
class TestPixmapItem(QtGui.QGraphicsPixmapItem):
def __init__(self, imagename, position, parent=None):
QtGui.QGraphicsPixmapItem.__init__(self, parent)
px = QtGui.QPixmap(imagename)
self.setPixmap(px)
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
# set position
self.setPos(position.x(),position.y())
def mouseReleaseEvent(self,e):
self.setSelected(False)
def mouseMoveEvent(self, e):
QtGui.QGraphicsPixmapItem.mouseMoveEvent(self, e)
class GfxScene(QtGui.QGraphicsScene):
def __init__(self, parent=None):
#build parent user interface
super(GfxScene, self).__init__(parent)
def mousePressEvent(self, e):
if(self.itemAt(e.scenePos().x(),e.scenePos().y()) == None):
pixmapItem = TestPixmapItem('test.png',e.scenePos())
self.addItem(pixmapItem)
else:
QtGui.QGraphicsScene.mousePressEvent(self,e);
class MainForm(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
scene = GfxScene(self)
scene.setSceneRect(QtCore.QRect(0, 0, 800, 800))
view = QtGui.QGraphicsView()
view.setScene(scene)
view.setSceneRect(scene.sceneRect())
#view.setGeometry(QtCore.QRect(0, 0, 800, 800))
self.setCentralWidget(view)
def main():
#This function means this was run directly, not called from another python file.
app = QtGui.QApplication.instance()
if app == None:
app = QtGui.QApplication(sys.argv)
myapp = MainForm()
myapp.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Any help would be appreciated!
You should call the QtGui.QGraphicsPixmapItem.mouseReleaseEvent(self, e) when you override the mouse release event, see : http://goo.gl/ChSYP
import sys
from PySide import QtGui,QtCore
class TestPixmapItem(QtGui.QGraphicsPixmapItem):
def __init__(self, imagename, position, parent=None):
QtGui.QGraphicsPixmapItem.__init__(self, parent)
px = QtGui.QPixmap(imagename)
self.setPixmap(px)
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
# set position
self.setPos(position.x(),position.y())
def mouseReleaseEvent(self,e):
self.setSelected(False)
QtGui.QGraphicsPixmapItem.mouseReleaseEvent(self, e) // here calling the event
def mouseMoveEvent(self, e):
QtGui.QGraphicsPixmapItem.mouseMoveEvent(self, e)
class GfxScene(QtGui.QGraphicsScene):
def __init__(self, parent=None):
#build parent user interface
super(GfxScene, self).__init__(parent)
def mousePressEvent(self, e):
if(self.itemAt(e.scenePos().x(),e.scenePos().y()) == None):
pixmapItem = TestPixmapItem('test.png',e.scenePos())
self.addItem(pixmapItem)
else:
QtGui.QGraphicsScene.mousePressEvent(self,e);
class MainForm(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
scene = GfxScene(self)
scene.setSceneRect(QtCore.QRect(0, 0, 800, 800))
view = QtGui.QGraphicsView()
view.setScene(scene)
view.setSceneRect(scene.sceneRect())
#view.setGeometry(QtCore.QRect(0, 0, 800, 800))
self.setCentralWidget(view)
def main():
#This function means this was run directly, not called from another python file.
app = QtGui.QApplication.instance()
if app == None:
app = QtGui.QApplication(sys.argv)
myapp = MainForm()
myapp.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Resources