QTreeView: show "empty view" item? - qt

I want to show an item "There are no elements in this view" if the connected QTreeView model (set by QSortFilterProxyModel) has no elements to show.
How can I implement such things?
Thanks for small hints.

One of the solution is overriding the tree view's paint event and draw the custom text when there is no items in the view. You need to sub class the QTreeView in the following way:
class TreeView : public QTreeView
{
[..]
protected:
void paintEvent(QPaintEvent * event)
{
if (model() && model()->rowCount() > 0) {
QTreeView::paintEvent(event);
} else {
// If no items draw a text in the center of the viewport.
QPainter painter(viewport());
QString text(tr("There are no elements in this view"));
QRect textRect = painter.fontMetrics().boundingRect(text);
textRect.moveCenter(viewport()->rect().center());
painter.drawText(textRect, Qt::AlignCenter, text);
}
}
};

Related

Qt - Not correctly adding widgets to QHBoxLayout after clearing the layout

I have a QHBoxLayout in which I added some widgets. I need to be able to refresh the layout dynamically so I use this to clear the layout :
void ClearLayout(QLayout* layout)
{
if (!layout)
return;
QLayoutItem* item;
while ((item = layout->takeAt(0)) != nullptr)
{
delete item->widget();
ClearLayout(item->layout());
}
}
This indeed removes all widgets and layouts. After this layout->isEmpty() returns true and layout->count() returns 0.
However, when I try to add new widgets (same type of other previously added but new instance) It does not work !
AddWidget()
{
// DeviceWidget inherits QWidget
DeviceWidget* deviceWidget = new DeviceWidget;
deviceWidget->setFixedSize(150, 200);
connect(deviceWidget->GetSignalObject(), &DeviceObject::Selected, this,
&DeviceLayout::SelectedDevice);
layout->addWidget(deviceWidget, 0, Qt::AlignCenter);
}
This is the same function used previously to add the widgets to the layout and worked the first time at Construction:
MainLayout(QWidget* parent) : QHBoxLayout(parent)
{
layout = new QHBoxLayout;
addLayout(layout);
uint32 nb = GetDeviceNumber(); // returns 2
for (uint32 i = 0; i < deviceNb; ++i)
AddDeviceWidget();
}
After trying to add 2 widgets I have layout->isEmpty() returns true and layout->count() returns 2 so I'm confused …
thanks for any help provided :)
EDIT:
The problem seems to be comming from my DeviceWidget class since trying to add a simple QLabel to the cleared layout worked. Here's the DeviceWidget Constructor:
DeviceWidget::DeviceWidget(QWidget* parent) : QWidget(parent)
{
QVBoxLayout* vLayout = new QVBoxLayout;
QLabel* deviceIcon = new QLabel("DeviceIcon", this);
deviceIcon->setFixedSize(128, 128);
deviceIcon->setPixmap(QPixmap::fromImage(QImage("Resources/Icons/device.png")));
deviceIcon->setObjectName("DeviceIcon");
// StatusWidget inherits QWidget
// Just override paintEvent to display a colored filled disk
m_status = new StatusWidget(20, Status::Close, this);
m_status->setObjectName("DeviceStatus");
vLayout->addWidget(deviceIcon, 0, Qt::AlignCenter);
vLayout->addWidget(m_status, 0, Qt::AlignCenter);
// DeviceObjct inherits from QObject an add a signal
m_object = new DeviceObject(size());
// Function clearing the stylesheet background-color
Clear();
setLayout(vLayout);
installEventFilter(this);
setObjectName(QString("DeviceWidget"));
}
Commenting installEventFilter(this) make it work so I think I need to add an event filter to make it work but I don't know which one
As said in the Edit, The problem is coming from DeviceWidget added in the layout that override eventFilter. There is probably a way to add a case in eventFilter to make it work but in my case it was best either (1) or (2):
1. Remove eventFilter from class DeviceWidget and put it in class DeviceObject: m_object is present to emit a signal according to event:
DeviceObject.h:
DeviceObject(QObject* parent);
bool eventFilter(QObject* obj, QEvent* event) override;
signals:
void Select(uint32 i);
Then in class DeviceWidget still call installEventFilter but with m_object as parameter: installEventFilter(m_object);
For the other event (Enter/Leave) I overrode void enterEvent(QEvent* event) and void leaveEvent(QEvent* event) for class DeviceWidget. That's what lead me to the second option that seems better.
2. Completely remove eventFilter and installEventFilter as it is only used to emit a signal when widget is clicked and do things when cursor hovers the widget. Instead override enterEvent and leaveEvent for class DeviceWidget like said before for hover event.
Then in class DeviceObjecy override void mousePressEvent(QMouseEvent*) for clicked event.

Override checkable QGroupBox toggle behaviour

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.

Adding a right-click menu for specific items in QTreeView

I'm writing a Qt desktop application in c++ with Qt Creator.
I declared in my main window a treeView, and a compatible model.
Now, I would like to have a right-click menu for the tree item. Not for all of the items, but for a part of them, for example: for the tree elements with an even index.
I tried adding a simple context menu with the following code:
in the .h file:
QStandardItemModel* model;
QMenu* contextMenu;
QAction* uninstallAction;
private slots:
void uninstallAppletClickedSlot();
and in the .cpp file:
in the constructor:
ui->treeView->setModel(model);
contextMenu = new QMenu(ui->treeView);
ui->treeView->setContextMenuPolicy(Qt::ActionsContextMenu);
uninstallAction = new QAction("Uninstall TA",contextMenu);
ui->treeView->addAction(uninstallAction);
connect(uninstallAction, SIGNAL(triggered()), this, SLOT(uninstallAppletClickedSlot()));
and a slot:
void MainWindow::uninstallAppletClickedSlot()
{
}
this code gives me a context menu with the wanted action, but do you have any idea how can I add this action only for the QStandardItems with the even indexes??
BTW, I'm adding items to the treeView by the following way:
void MainWindow::AddItem(QString name)
{
QStandardItem *parentItem = model->invisibleRootItem();
QStandardItem *app = new QStandardItem(name);
parentItem->appendRow(app);
}
I googled a lot, but found nothing :(
thanks in advance!
I would do this in the following way:
Configure the context menu
ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->treeView, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onCustomContextMenu(const QPoint &)));
Implement the context menu handling
void MainWindow::onCustomContextMenu(const QPoint &point)
{
QModelIndex index = ui->treeView->indexAt(point);
if (index.isValid() && index.row() % 2 == 0) {
contextMenu->exec(ui->treeView->viewport()->mapToGlobal(point));
}
}

How to make a Qt dialog read-only?

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

How to let QComboBox have context menu?

I have a Qt combo box. When it pops, items are listed down. When right clicking an item, I hope a context menu to pop up. Any way to implement it? I find a function onContextMenuEvent under QComboBox. Does it help? Thanks.
You can obtain the list widget using QComboBox::view. You can add a context menu to the list as usual. But also you should install event filter on the view's viewport and block right click events because such events cause popup list to close.
In the initialization:
QAbstractItemView* view = ui->comboBox->view();
view->viewport()->installEventFilter(this);
view->setContextMenuPolicy(Qt::CustomContextMenu);
connect(view, SIGNAL(customContextMenuRequested(QPoint)),
this, SLOT(list_context_menu(QPoint)));
Event filter:
bool MainWindow::eventFilter(QObject *o, QEvent *e) {
if (e->type() == QEvent::MouseButtonRelease) {
if (static_cast<QMouseEvent*>(e)->button() == Qt::RightButton) {
return true;
}
}
return false;
}
In the slot:
void MainWindow::list_context_menu(QPoint pos) {
QAbstractItemView* view = ui->comboBox->view();
QModelIndex index = view->indexAt(pos);
if (!index.isValid()) { return; }
QMenu menu;
QString item = ui->comboBox->model()->data(index, Qt::DisplayRole).toString();
menu.addAction(QString("test menu for item: %1").arg(item));
menu.exec(view->mapToGlobal(pos));
}
In this example items are identified by their displayed texts. But you also can attach additional data to items using QComboBox::setItemData. You can retrieve this data using ui->comboBox->model()->data(...) with the role that was used in setItemData.

Resources