I have dynamically created buttons(QtoolButton) in gridLayout in pyQT.
How can I get the name of the button clicked in the layout?
I can't know the name before hand.
Is there is any trigger to accomplish the task?
Thanks in advance.
You can call self.sender() in a function connected to your button event to get the object that triggered the event. From there you can call the object's objectName() method to get the name.
Here's a quick example - the widget has 10 buttons and clicking on a button will update the label's text to show the button name.
import sys
from PyQt4.QtGui import QApplication, QWidget, QToolButton, QLabel, QVBoxLayout, QHBoxLayout
class Widget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.button_layout = QHBoxLayout()
self.widget_layout = QVBoxLayout()
for button_number in xrange(1, 11):
button = QToolButton()
button.setText(str(button_number))
button.setObjectName('Button%d' % button_number)
button.released.connect(self.button_released)
self.button_layout.addWidget(button)
self.status_label = QLabel('No button clicked')
self.widget_layout.addItem(self.button_layout)
self.widget_layout.addWidget(self.status_label)
self.setLayout(self.widget_layout)
def button_released(self):
sending_button = self.sender()
self.status_label.setText('%s Clicked!' % str(sending_button.objectName()))
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = Widget()
widget.show()
sys.exit(app.exec_())
I think you have to implement your iconlabel class derived from QToolButton:
Like This:
class IconLabel : public QToolButton
{
Q_OBJECT
public:
explicit IconLabel(QWidget *parent = 0);
bool event (QEvent* e );
QString name;
signals:
void clicked_signal(QString);
};
bool IconLabel::event (QEvent* e ) {
if ( e->type() == QEvent::Paint) {
return QToolButton::event(e);
}
if(e->type() == QEvent::MouseButtonPress)
{
emit clicked_signal(name);
return true;
}
return true;
}
connect(iconlabel, SIGNAL(clicked_signal(QString)), this, SLOT(getClickedButtonName(QString)));
Related
I would like my QTableWidget to trigger the edition callbacks when pressing Enter while editing item BUT I would like the editor to remain activated – like it would just select all of the item’s content like when you start editing the cell.
What is the best way to do this?
Thanks for having a look here.
You should modify the table's item delegate and use event filters to filter out Enter event and implement custom behavior:
class MyDelegate : public QStyledItemDelegate {
public:
MyDelegate(QObject* parent) : QStyledItemDelegate(parent) {}
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option,
const QModelIndex& index) const {
QWidget* editor = QStyledItemDelegate::createEditor(parent, option, index);
editor->installEventFilter(const_cast<MyDelegate*>(this));
return editor;
}
bool eventFilter(QObject* object, QEvent* event) {
QWidget* editor = qobject_cast<QWidget*>(object);
if (editor && event->type() == QEvent::KeyPress) {
QKeyEvent* key_event = static_cast<QKeyEvent*>(event);
if (key_event->key() == Qt::Key_Return) {
emit commitData(editor); //save changes
QLineEdit* line_edit = qobject_cast<QLineEdit*>(editor);
if (line_edit) {
line_edit->selectAll();
}
return true;
}
}
return false;
}
};
Usage:
ui->tableWidget->setItemDelegate(new MyDelegate(this));
PyQt5 code looks like this:
class GlyphCellItemDelegate(QStyledItemDelegate):
def eventFilter(self, editor, event):
if (event.type() == QEvent.KeyPress and event.key() == Qt.Key_Return):
self.commitData.emit(editor)
# Don't emit closeEditor, select contents instead
editor.selectAll()
return True
return False
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.
For a QFileDialog, is it possible to have either files or directories selectable, the choice being given to user on the same UI (like the way a user selects different filetypes amongst filters and the filelist updates accordingly)?
I have done some research and with some help from IRC ppl I found an easier solution. Basically adding a widget (checkbox, a suitable for this one) and connecting it to the file dialog does the work.
(It's actually someone else's answer which I have improved & furnished. Thanks to him ;). Posting answer here just for reference if someone else stumbles here).
from sys import argv
from PySide import QtGui, QtCore
class MyDialog(QtGui.QFileDialog):
def __init__(self, parent=None):
super (MyDialog, self).__init__()
self.init_ui()
def init_ui(self):
cb = QtGui.QCheckBox('Select directory')
cb.stateChanged.connect(self.toggle_files_folders)
self.layout().addWidget(cb)
def toggle_files_folders(self, state):
if state == QtCore.Qt.Checked:
self.setFileMode(self.Directory)
self.setOption(self.ShowDirsOnly, True)
else:
self.setFileMode(self.AnyFile)
self.setOption(self.ShowDirsOnly, False)
self.setNameFilter('All files (*)')
def main():
app = QtGui.QApplication(argv)
dialog = MyDialog()
dialog.show()
raise SystemExit(app.exec_())
if __name__ == '__main__':
main()
Yes, it is. Here is one way:
In the header, declare your QFileDialog pointer:
class buggy : public QWidget
{
Q_OBJECT
public:
buggy(QWidget *parent = 0);
QFileDialog *d;
public slots:
void createDialog();
void changeDialog();
};
In your implementation, set the QFileDialog::DontUseNativeDialog option (you must do this on Mac OS, but I haven't tested it elsewhere), then override the appropriate window flags to get your dialog to display as you like it.
Finally, add a button (or check box) that calls a function to change the file mode of your QFileDialog:
buggy::buggy(QWidget *){
//Ignore this, as its just for example implementation
this->setGeometry(0,0,200,100);
QPushButton *button = new QPushButton(this);
button->setGeometry(0,0,100,50);
button->setText("Dialog");
connect( button, SIGNAL(clicked()),this,SLOT(createDialog()));
//Stop ignoring and initialize this variable
d=NULL;
}
void buggy::createDialog(void){
d = new QFileDialog(this);
d->setOption(QFileDialog::DontUseNativeDialog);
d->overrideWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint | Qt::MacWindowToolBarButtonHint);
QPushButton *b = new QPushButton(d);
connect(b,SIGNAL(clicked()),this,SLOT(changeDialog()));
b->setText("Dir Only");
switch(d->exec()){
default:
break;
}
delete(d);
d=NULL;
}
//FUNCTION:changeDialog(), called from the QFileDialog to switch the fileMode.
void buggy::changeDialog(){
if(d != NULL){
QPushButton *pb = (QPushButton*)d->childAt(5,5);
if(pb->text().contains("Dir Only")){
pb->setText("File Only");
d->setFileMode(QFileDialog::Directory);
}else{
pb->setText("Dir Only");
d->setFileMode(QFileDialog::ExistingFile);
}
}
}
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)
I need to process some data AFTER a drop event has occured on a widget.
i.e a user selects some items in list A and drops them in list B. My program needs to compare the 2 lists after B has added the items selected from A.
Any ideas?
Here's a PyQt script that demonstrates two ways to trap drop events:
from PyQt4 import QtGui, QtCore
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
widget = QtGui.QWidget(self)
self.setCentralWidget(widget)
layout = QtGui.QVBoxLayout(widget)
self.listA = ListWidget(self)
self.listB = QtGui.QListWidget(self)
self.listB.viewport().installEventFilter(self)
for widget in (self.listA, self.listB):
widget.setAcceptDrops(True)
widget.setDragEnabled(True)
for item in 'One Two Three Four Five Six'.split():
widget.addItem(item)
layout.addWidget(widget)
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.Drop and
source is self.listB.viewport()):
self.listB.dropEvent(event)
if event.isAccepted():
print 'eventFilter', self.listB.count()
return True
return QtGui.QMainWindow.eventFilter(self, source, event)
class ListWidget(QtGui.QListWidget):
def __init__(self, parent):
QtGui.QListWidget.__init__(self, parent)
def dropEvent(self, event):
QtGui.QListWidget.dropEvent(self, event)
if event.isAccepted():
print 'dropEvent', self.count()
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
The event you're listening for is QDropEvent. To do some work on receipt of the event you'll need to either:
Reimplement QWidget::dropEvent on the target widget or
Use a separate controller widget as an event filter for the drop target (reimplement QObject::eventFilter on the controller and then install it on the target [installEventFilter]).
EDIT: Going into more depth:
Reimplementing dropEvent
Header:
cDropTarget : public QWidget
{
Q_OBJECT
public:
cDropTarget(QWidget *Parent = 0);
protected:
virtual void dropEvent(QDropEvent *event);
}
Implementation:
cDropTarget::cDropTarget(QWidget *Parent)
: QWidget(Parent)
{
setAcceptDrops(true);
}
void cDropTarget::dropEvent(QDropEvent *event)
{
//check that you want to process the drop event
//i.e. its mimeData()->hasFormat(that you can interpret)
//extract mimeData() from the drop event
//do something with it
}
Using a separate event filter
header:
cDropEventFilter : public QObject
{
...
protected:
virtual bool eventFilter(QObject *watched, QEvent *event);
...
}
Implementation:
bool cDropEventFilter::eventFilter(QObject *watched, QEvent *event)
{
if(event->type() == QEvent::DropEnter)
{
QDropEvent *DropEvent = static_cast<QDropEvent*>(event);
if (DropEvent->mimeData()->hasFormat(MIME_TYPE))
{
DropEvent->acceptProposedAction();
return true;
}
}
else
{
return QObject::eventFilter(watched, event);
}
//Handle all events not matching mimeData format MIME_TYPE
event->ignore();
return true;
}
Got it working!
My code for anyone interested:
class MyList(QListWidget):
def __init__(self , parent = None):
super(MyList, self).__init__(parent)
self.setAlternatingRowColors(True)
self.setDragDropMode( QAbstractItemView.InternalMove )
def dropEvent( self , event ):
# get index to insert at
insertPos = event.pos()
fromList = event.source()
insertRow = fromList.row( fromList.itemAt( insertPos ) )
lowestRow = insertRow
for item in fromList.selectedItems():
name = item.text()
sip.delete( item )
listItem = QListWidgetItem( name )
listItem.setTextAlignment( Qt.AlignHCenter )
self.insertItem( insertRow , listItem )
insertRow += 1
self.emit( SIGNAL("stuffDropped") , lowestRow )
event.accept()