Suppose there are checkboxes, options etc controls in a dialog box, how could I save the state of the dialog in Qt?
Should I use QSettings or something else?
Thanks.
I ran into the same problem.
Googling didn't help too much.
So in the end I wrote my own solution.
I've created a set of functions that read and write the state of each child control of a dialog at creation respectively destruction.
It is generic and can be used for any dialog.
It works like this:
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
QMoMSettings::readSettings(this);
}
Dialog::~Dialog()
{
QMoMSettings::writeSettings(this);
delete ui;
}
...
void QMoMSettings::readSettings(QWidget* window)
{
QSettings settings;
settings.beginGroup(window->objectName());
QVariant value = settings.value("pos");
if (!value.isNull())
{
window->move(settings.value("pos").toPoint());
window->resize(settings.value("size").toSize());
recurseRead(settings, window);
}
settings.endGroup();
}
void QMoMSettings::writeSettings(QWidget* window)
{
QSettings settings;
settings.beginGroup(window->objectName());
settings.setValue("pos", window->pos());
settings.setValue("size", window->size());
recurseWrite(settings, window);
settings.endGroup();
}
void QMoMSettings::recurseRead(QSettings& settings, QObject* object)
{
QCheckBox* checkbox = dynamic_cast<QCheckBox*>(object);
if (0 != checkbox)
{
checkbox->setChecked(settings.value(checkbox->objectName()).toBool());
}
QComboBox* combobox = dynamic_cast<QComboBox*>(object);
if (0 != combobox)
{
combobox->setCurrentIndex(settings.value(combobox->objectName()).toInt());
}
...
foreach(QObject* child, object->children())
{
recurseRead(settings, child);
}
}
void QMoMSettings::recurseWrite(QSettings& settings, QObject* object)
{
QCheckBox* checkbox = dynamic_cast<QCheckBox*>(object);
if (0 != checkbox)
{
settings.setValue(checkbox->objectName(), checkbox->isChecked());
}
QComboBox* combobox = dynamic_cast<QComboBox*>(object);
if (0 != combobox)
{
settings.setValue(combobox->objectName(), combobox->currentIndex());
}
...
foreach(QObject* child, object->children())
{
recurseWrite(settings, child);
}
}
Hope this helps someone after me.
QSettings will work fine for what you need, but you're essentially just serializing the options and reloading them at start up so there's plenty of documentation on doing it in Qt.
Related
I'm making a program where the user can add multiple people (participants) in a list. When the "Add" button is clicked, a new row is added and "edit" is called for the name field. All is well so far, but there is a thing I'd like to implement, and I can't seem to figure out how: when the user closes the editing field (presses enter or escape, clicks elsewhere, etc.) and if the name field remains empty, I'd like the row to be deleted. In other words, a name has to be filled in. Here is what I have so far:
void MainWindow::addParticipant()
{
QList<QStandardItem *> newRow;
newRow << new QStandardItem()
<< new QStandardItem();
participantModel->appendRow(newRow);
participantView->edit(participantModel->index(participantModel->rowCount()-1, 0));
}
Here participantModel is a QStandardItemModel and participantView is a QTreeView. I tried using signals and slots to detect when a row is empty and to delete it, but it hasn't worked and the syntax is elusive to me.
Ideally I'd be able to detect when the name field is not being edited anymore, so that I can delete the row if need be.
Here is ugly but working solution: subclass from QItemDelegate and check input data inside setModelData member function. As far setModelData has a const qualifier you can not modify model inside it, so you need some trick: in the following example the model is modified inside handler of closeEditor signal.
class MainWidget : public QWidget
{
Q_OBJECT
public:
MainWidget ()
{
QStandardItemModel * model = new QStandardItemModel ();
ItemDelegate * delegate = new ItemDelegate ();
table->setItemDelegate (delegate);
connect (delegate, & ItemDelegate::closeEditor, [=](){
if (isEmpty) {
model->removeRow (emptyRow);
isEmpty = false;
emptyRow = -1;
}
});
connect (delegate, & ItemDelegate::cellEdited, [=](const int row){
isEmpty = true;
emptyRow = row;
});
}
bool isEmpty;
int emptyRow;
};
class ItemDelegate : public QItemDelegate
{
Q_OBJECT
signals:
void cellEdited (int) const;
public:
void setModelData (QWidget * widget, QAbstractItemModel * model, const QModelIndex & index) const override
{
if (0 == index.column () ) {
if (QLineEdit * cellWidget = qobject_cast <QLineEdit *> (widget) ) {
if (cellWidget->text ().isEmpty () ) {
emit cellEdited (index.row () );
return;
}
}
}
QItemDelegate::setModelData (widget, model, index);
}
};
Complete example available at GitLab.
The comments/answers posted thus far have urged me to look more into item delegates. Quite embarrassingly, after relatively little googling I found the following solution for my problem:
void MainWindow::addParticipant()
{
QStyledItemDelegate *participantDelegate = new QStyledItemDelegate;
participantView->setItemDelegateForColumn(0, participantDelegate);
QList<QStandardItem *> newRow;
newRow << new QStandardItem()
<< new QStandardItem();
participantModel->appendRow(newRow);
connect(participantDelegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), this, SLOT(checkRow()));
participantView->edit(participantModel->index(participantModel->rowCount()-1, 0));
}
Apparently the closeEditor signal (only available to delegates) is exactly what I was looking for. When the editor is closed, the slot checkRow() checks if the name field of the participant is empty and decides whether or not to delete the row.
I've derived a class from QQuickPaintedItem in which I want to handle the mousePressEvent and the mouseReleasEvent (and also the mouseMoveEvent but that is not my prolem now).
The mousePressEvent gets called properly everytime the left mouse button is pressed. But the mouseReleaseEvent gets only called after a double click. What I expected is to get the event everytime the button is released. How can I change this?
This is what I do:
MyView::MyView(QQuickItem *parent):
QQuickPaintedItem(parent)
{
setAcceptedMouseButtons(Qt::LeftButton);
}
void MyView::mousePressEvent(QMouseEvent *evt)
{
//gets called after every single mouse click
qDebug("mousePressEvent");
if(evt->button() == Qt::LeftButton)
{
//do something...
evt->accept();
}
else
{
evt->ignore();
}
QQuickPaintedItem::mousePressEvent(evt);
}
void MyView::mouseReleaseEvent(QMouseEvent *evt)
{
//gets only called when releasing the mouse button after a double click
qDebug("mouseReleaseEvent");
if(evt->button() == Qt::LeftButton)
{
//do something...
evt->accept();
}
else
{
evt->ignore();
}
QQuickPaintedItem::mouseReleaseEvent(evt);
}
So I finally found the solution!
Calling the base class implementation is a bad idea since the base class simply calls ignore() on the event. Here is the base class implementation:
void QWindow::mousePressEvent(QMouseEvent *ev)
{
ev->ignore();
}
void QWindow::mouseReleaseEvent(QMouseEvent *ev)
{
ev->ignore();
}
So this is how it works:
MyView::MyView(QQuickItem *parent):
QQuickPaintedItem(parent)
{
setAcceptedMouseButtons(Qt::LeftButton);
}
void MyView::mousePressEvent(QMouseEvent *evt)
{
//gets called after every single mouse click
qDebug("mousePressEvent");
if(evt->button() == Qt::LeftButton)
{
//do something...
evt->accept();
}
else
{
evt->ignore();
}
//DON'T DO THIS:
//QQuickPaintedItem::mousePressEvent(evt);
}
void MyView::mouseReleaseEvent(QMouseEvent *evt)
{
//now gets called with every mouse release since we don't call the base class any more
qDebug("mouseReleaseEvent");
if(evt->button() == Qt::LeftButton)
{
//do something...
evt->accept();
}
else
{
evt->ignore();
}
//DON'T DO THIS:
//QQuickPaintedItem::mouseReleaseEvent(evt);
}
I've subclassed the QGroupBox class, with the checkable property enabled. I'm trying to override the behaviour of the toggle/checked events.
Here's the code:
class SideWidgetGroupBox: public QGroupBox
{
Q_OBJECT
public:
SideWidgetGroupBox(QWidget* parent = 0): QGroupBox(parent)
{
this->setCheckable(true);
connect(this, SIGNAL(toggled(bool)), this, SLOT(my_toggled(bool)));
}
private slots:
void my_toggled (bool on)
{
std::cout << "my toggled method" <<std::endl;
}
};
So far so good, my slot gets executed. However the groupboxs' contents also get enabled/disabled. Is there a way to prevent that? Or do I have to manually reset the original enabled/disabled state?
Is there a way to prevent enabling/disabling of a content?
Yes, but this way is not easy, because there is no QCheckBox there. What looks like a check box is an area of QGroupBox. And all events are processed by QGroupBox:
1. Override event method and prevent processing of QEvent::KeyRelease and QEvent::MouseRelease events by the base class.
bool SideWidgetGroupBox::event(QEvent *e)
{
switch (e->type()) {
case QEvent::KeyRelease:
case QEvent::MouseButtonRelease:
myHandler(e);
return true;
}
return QGroupBox::event(e);
}
2. In myHandler check whether space pressed or the mouse clicked on the checkbox. Store checkBox value and do what you need. Use this code to check what is under cursor:
QStyleOptionGroupBox box;
initStyleOption(&box);
QStyle::SubControl released = style()->hitTestComplexControl(QStyle::CC_GroupBox, &box,
event->pos(), this);
bool toggle = released == QStyle::SC_GroupBoxLabel || released == QStyle::SC_GroupBoxCheckBox;
if (toggle)
{
m_state = !m_state;
update();
}
3. Add method initStyleOption and set state to the state of the checkBox (you should store it by yourself):
void SideWidgetGroupBox::initStyleOption(QStyleOptionGroupBox *option) const
{
QGroupBox::initStyleOption(option);
QStyle::State flagToSet = m_state ? QStyle::State_On : QStyle::State_Off;
QStyle::State flagToRemove = m_state ? QStyle::State_Off : QStyle::State_On;
option->state |= flagToSet;
option->state &= ~flagToRemove;
option->state &= ~QStyle::State_Sunken;
}
4.Method initStyleOption in QGroupBox is not virtual that is why you need to reimplement paintEvent also:
void paintEvent(QPaintEvent *)
{
QStylePainter paint(this);
QStyleOptionGroupBox option;
initStyleOption(&option);
paint.drawComplexControl(QStyle::CC_GroupBox, option);
}
do I have to manually reset the original enabled/disabled state?
You can't do this with setEnabled because it checks current checked state and prevents enabling of children. Although you can call setEnabled for children directly using this->findChildren<QWidget*>
Suggestion
You can use ways described above or remove standard checkBox and(or) label and put your own QCheckBox over the group (without layout, of course) and use it as you want. If you group can be moved you will need to move the check box also.
How to make a QT dialog read-only? Any general way to implement it easily? For example
(1) set all its containing widgets disable. (how to implement it?)
(2) Intercept edit events like key pressed, mouse pressed but how not to intercept the one to close the dialog?
I think this feature should be very helpful.
Disabling the widgets can be done similar to the following:
void myDialog::disableWidgets()
{
QList<QWidget *> widgets = this->findChildren<QWidget *>();
foreach(QWidget* widget, widgets)
{
widget->setEnabled(false);
}
}
To intercept events, QDialog includes the function installEventFilter(QObject*).
This allows you to use a separate object to receive all events passed to the dialog. You can then choose to handle the event in the object, or pass it on to the dialog itself by calling the base class QObject::eventFilter
class MyEventHandler : public QObject
{
Q_OBJECT
protected:
bool MyEventHandler::eventFilter(QObject *obj, QEvent *event)
{
// handle key press events
if (event->type() == QEvent::KeyPress)
{
// Do something
// ...
return true; // event handled by the class
}
else
{ // ignore this event and pass it to the dialog as usual
return QObject::eventFilter(obj, event);
}
}
return false;
};
QDialog* dlg = new QDialog;
MyEventHandler evtHandler = new MyEventHandler;
dlg->installEventFilter(evtHandler);
Read-only is a strange term to apply to a dialog. Disabling all widgets as above does the trick. If you only wanted to make the input part of a QInputDialog read-only (while leaving scrollbars, buttons, etc. enabled), you could adapt that code as below:
QInputDialog dialog(this);
dialog.setOptions(QInputDialog::UsePlainTextEditForTextInput);
dialog.setWindowTitle("Title");
dialog.setLabelText("Label");
dialog.setTextValue("1\n2\n3\n");
QList<QWidget *> widgets = dialog.findChildren<QWidget *>();
foreach(QWidget* widget, widgets) {
if (strcmp(widget->metaObject()->className(),"QPlainTextEdit")==0) {
QPlainTextEdit *t = static_cast<QPlainTextEdit*>(widget);
t->setReadOnly(true);
}
}
dialog.exec();
Why doesn't drag and drop pictures work on this QTextEdit? I have tried everything.
here is the class TextEdit:
//textedit
class TextEdit : public QTextEdit
{
Q_OBJECT
public:
TextEdit(QWidget*parent) : QTextEdit(parent)
{
this->setAcceptDrops(true);
}
virtual void dragEnterEvent(QDragEnterEvent *e)
{
e->accept();
//QTextEdit::dragEnterEvent(e);
}
virtual void dragLeaveEvent(QDragLeaveEvent *e)
{
e->accept();
//QTextEdit::dragLeaveEvent(e);
}
//
virtual void dragMoveEvent(QDragMoveEvent *e)
{
e->accept();
// QTextEdit::dragMoveEvent(e);
}
virtual void dropEvent(QDropEvent *e)
{
QTextEdit::dropEvent(e);
}
bool canInsertFromMimeData(const QMimeData *source ) const
{
if (source->hasImage())
return true;
else
return QTextEdit::canInsertFromMimeData(source);
}
void insertFromMimeData( const QMimeData *source )
{
if (source->hasImage())
{
QImage image = qvariant_cast<QImage>(source->imageData());
QTextCursor cursor = this->textCursor();
QTextDocument *document = this->document();
document->addResource(QTextDocument::ImageResource, QUrl("image"), image);
cursor.insertImage("image");
}
}
};
context context context context context context context context context context context context context context context context context context context context context context context context context context context context context context context context
It depends on what application you are dragging the images from and what data that application decides to include in the operation. If it is not working for you, it is because whatever you are dropping contains no image data and probably only contains a URL or file path.
Dragging images from the file explorer under Windows 7 for me at least does not work but opening an image in the latest version of Firefox and dragging that onto the text edit does work. Try it :)