Reputation: 553
I am trying to tweak the ui of a QComboBox in a way that the user can remove items from the drop down list (without first selecting them).
The background is that I am using the QComboBox to indicate which data file is open right now. I am also using it as a cache for recently opened files. I would like the user to be able to remove entries he does not want to have listed anymore. This could be either by just hitting the delete key, or a context menu, or whatever is straightforward to implement. I do not want to rely on selecting the item first. A similar behavior can be found in Firefox, where old cached suggestions for an entry filed can be deleted.
I was considering subclassing the list view used by QComboBox, however, I did not find enough documentation to get me started.
I would be grateful for any hints and suggestions. I am using PyQt, but have no problems with C++ samples.
Upvotes: 5
Views: 19392
Reputation: 163
You can use a specialized class that automates processes, therefore it saves time in the end.
For example, there's a class named KrHistoryComboBox (which inherits from the KHistoryComboBox class) that is used in the program named Krusader.
Although this time, for this answer: the following code is a version that inherits directly from a QComboBox
(though a QComboBox
can not do as many things as a KHistoryComboBox
), and one example of its use:
File main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
File mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
File mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "krhistorcombobox.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
// Creates a new editable comboBox, and populates it with data
KrHistorComboBox *combox;
combox = new KrHistorComboBox(this);
combox->setEditable(true);
QStringList elementsToAdd = {"one", "two", "three", "four", "five", "six"};
combox->insertItems(0, elementsToAdd);
}
MainWindow::~MainWindow()
{
delete ui;
}
File krhistorcombobox.h
/*****************************************************************************
* Copyright (C) 2018-2019 Shie Erlich <[email protected]> *
* Copyright (C) 2018-2019 Rafi Yanai <[email protected]> *
* Copyright (C) 2018-2019 Krusader Krew [https://krusader.org] *
* *
* This file is part of Krusader [https://krusader.org]. *
* *
* Krusader is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 2 of the License, or *
* (at your option) any later version. *
* *
* Krusader is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with Krusader. If not, see [http://www.gnu.org/licenses/]. *
*****************************************************************************/
#ifndef KRHISTORCOMBOBOX_H
#define KRHISTORCOMBOBOX_H
// QtWidgets
#include <QComboBox>
/**
* A specialized version of a QComboBox, e.g. it deletes the current
* item when the user presses Shift+Del
*/
class KrHistorComboBox : public QComboBox
{
Q_OBJECT
public:
explicit KrHistorComboBox(QWidget *parent = nullptr);
};
#endif // KRHISTORCOMBOBOX_H
File krhistorcombobox.cpp
/*****************************************************************************
* Copyright (C) 2018-2019 Shie Erlich <[email protected]> *
* Copyright (C) 2018-2019 Rafi Yanai <[email protected]> *
* Copyright (C) 2018-2019 Krusader Krew [https://krusader.org] *
* *
* This file is part of Krusader [https://krusader.org]. *
* *
* Krusader is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 2 of the License, or *
* (at your option) any later version. *
* *
* Krusader is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with Krusader. If not, see [http://www.gnu.org/licenses/]. *
*****************************************************************************/
#include "krhistorcombobox.h"
// QtCore
#include <QEvent>
// QtGui
#include <QKeyEvent>
// QtWidgets
#include <QAbstractItemView>
/**
* A KrHistorComboBox event filter that e.g. deletes the current item when Shift+Del is pressed
* There was more information in https://doc.qt.io/qt-5/qobject.html#installEventFilter,
* https://forum.qt.io/post/160618 and
* https://stackoverflow.com/questions/17820947/remove-items-from-qcombobox-from-ui/52459337#52459337
*/
class KHBoxEventFilter : public QObject
{
Q_OBJECT
public:
explicit KHBoxEventFilter(QObject *parent = nullptr) : QObject(parent) {}
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
};
bool KHBoxEventFilter::eventFilter(QObject *obj, QEvent *event)
{
// Reminder: If this function is modified, it's important to investigate if the
// changes must also be applied to `KHBoxListEventFilter::eventFilter(QObject *obj, QEvent *event)`
if (event->type() == QEvent::KeyPress) {
auto keyEvent = static_cast<QKeyEvent *>(event);
if ((keyEvent->modifiers() == Qt::ShiftModifier ||
keyEvent->modifiers() == (Qt::ShiftModifier | Qt::KeypadModifier)) &&
keyEvent->key() == Qt::Key::Key_Delete) {
auto comboBox = qobject_cast<QComboBox *>(obj);
if (comboBox != nullptr) {
// Delete the current item
comboBox->removeItem(comboBox->currentIndex());
return true;
}
}
}
// Perform the usual event processing
return QObject::eventFilter(obj, event);
}
/**
* An event filter for the popup list of a KrHistorComboBox, e.g. it deletes the current
* item when the user presses Shift+Del
*/
class KHBoxListEventFilter : public QObject
{
Q_OBJECT
public:
explicit KHBoxListEventFilter(QObject *parent = nullptr) : QObject(parent) {}
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
};
bool KHBoxListEventFilter::eventFilter(QObject *obj, QEvent *event)
{
// Reminder: If this function is modified, it's important to investigate if the
// changes must also be applied to `KHBoxEventFilter::eventFilter(QObject *obj, QEvent *event)`
if (event->type() == QEvent::KeyPress) {
auto keyEvent = static_cast<QKeyEvent *>(event);
if ((keyEvent->modifiers() == Qt::ShiftModifier ||
keyEvent->modifiers() == (Qt::ShiftModifier | Qt::KeypadModifier)) &&
keyEvent->key() == Qt::Key::Key_Delete) {
auto itemView = qobject_cast<QAbstractItemView *>(obj);
if (itemView->model() != nullptr) {
// Delete the current item from the popup list
itemView->model()->removeRow(itemView->currentIndex().row());
return true;
}
}
}
// Perform the usual event processing
return QObject::eventFilter(obj, event);
}
#include "krhistorcombobox.moc" // required for class definitions with Q_OBJECT macro in implementation files
KrHistorComboBox::KrHistorComboBox(QWidget *parent): QComboBox(parent)
{
installEventFilter(new KHBoxEventFilter(this));
QAbstractItemView *itemView = view();
if (itemView != nullptr)
itemView->installEventFilter(new KHBoxListEventFilter(this));
}
File krexample.pro
#-------------------------------------------------
#
# Project created by QtCreator 2018-09-22T18:33:23
#
#-------------------------------------------------
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = untitled
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
krhistorcombobox.cpp \
mainwindow.cpp
HEADERS += \
krhistorcombobox.h \
mainwindow.h
FORMS += \
mainwindow.ui
File mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle" >
<string>MainWindow</string>
</property>
<widget class="QMenuBar" name="menuBar" />
<widget class="QToolBar" name="mainToolBar" />
<widget class="QWidget" name="centralWidget" />
<widget class="QStatusBar" name="statusBar" />
</widget>
<layoutDefault spacing="6" margin="11" />
<pixmapfunction></pixmapfunction>
<resources/>
<connections/>
</ui>
This is a screenshot of the example program being executed, before pressing Shift+Del (which would remove the option named "two"):
Note: Some source code in the present answer is based on https://doc.qt.io/qt-5/qobject.html#installEventFilter, https://forum.qt.io/post/160618 and the good work by the user named "nwp" in https://stackoverflow.com/a/26976984 (although that answer does not include code to delete an element of the popup list if the popup list is being seen, and it has a "memory leak" (an object is constructed but not destroyed) therefore if a developer adds a destructor like e.g. ~DeleteHighlightedItemWhenShiftDelPressedEventFilter() { QTextStream(stdout) << "DESTRUCTED" << endl; }
the developer later sees that the code of the destructor is never executed, and so there are memory leaks; currently I haven't got stackoverflow points in order to add a comment in https://stackoverflow.com/a/26976984).
Upvotes: 2
Reputation: 1
If you could live with selecting the entry first, and could accept a "Remove" button besides the combobox, you could extend QComboBox with a suitable slot.
class IQComboBox : public QComboBox
{
Q_OBJECT
public :
IQComboBox(QWidget *parent = nullptr) : QComboBox(parent) {}
public slots :
void remove_current_item(void) { removeItem(currentIndex()); }
};
Then connect the button's "released" signal with the new slot.
IQComboBox combo;
QPushButton remove_button(tr("Remove"));
remove_button.setToolTip(tr("Remove the current item from the list."));
connect(&remove_button, SIGNAL(released()),
&combo, SLOT(remove_current_item()));
Upvotes: 0
Reputation: 343
You can delete the active selected value of a QComboBox by:
ui->comboBox->removeItem(ui->comboBox->currentIndex());
Upvotes: 0
Reputation: 81
Sorry for being that late to this thread, but I'd like to contribute some other methods I found, just in case that someone else is looking for it like me. The methods have been tested with Qt 5.6. I cannot guarantee that they will work in other versions.
One possibility is to listen to the "pressed()" signal of the QCombobox' view(). That way we could use the right mouse button to remove items from the list. I was surprised to see that the view() is always available, never NULL, and that items can be deleted while it is displayed, so the following works quite well:
class MyCombobox : public QComboBox
{
Q_OBJECT
public: MyCombobox(QWidget *pParent = NULL);
protected slots: void itemMouseDown(const QModelIndex &pIndex);
};
MyCombobox::MyCombobox(QWidget *pParent)
{
connect( QComboBox::view(), SIGNAL(pressed(const QModelIndex &)),
this, SLOT(itemMouseDown(const QModelIndex &)) );
}
void MyCombobox::itemMouseDown(const QModelIndex &pIndex)
{
if( QApplication::mouseButtons() == Qt::RightButton )
{
QComboBox::model()->removeRow(pIndex.row());
}
}
A second option is to install an event filter, but also into the view. That way we can use the delete key or anything else to remove items. It might be a good idea to test for NULL pointers and invalid row indices, but I omitted that for clarity.
class MyCombobox : public QComboBox
{
Q_OBJECT
public: MyCombobox(QWidget *pParent = NULL);
protected: bool eventFilter(QObject *pWatched, QEvent *pEvent);
};
MyCombobox::MyCombobox(QWidget *pParent)
{
QComboBox::view()->installEventFilter(this);
}
bool MyCombobox::eventFilter(QObject *pWatched, QEvent *pEvent)
{
if( pEvent->type() == QEvent::KeyPress )
{
QKeyEvent *tKeyEvent = static_cast<QKeyEvent*>(pEvent);
if( tKeyEvent->key() == Qt::Key_Delete )
{
QComboBox::model()->removeRow(QComboBox::view()->currentIndex().row());
return true;
}
}
return QObject::eventFilter(pWatched, pEvent);
}
That's it.
Upvotes: 1
Reputation: 9991
I solved this problem using code from the installEventFilter documentation.
//must be in a header, otherwise moc gets confused with missing vtable
class DeleteHighlightedItemWhenShiftDelPressedEventFilter : public QObject
{
Q_OBJECT
protected:
bool eventFilter(QObject *obj, QEvent *event);
};
bool DeleteHighlightedItemWhenShiftDelPressedEventFilter::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key::Key_Delete && keyEvent->modifiers() == Qt::ShiftModifier)
{
auto combobox = dynamic_cast<QComboBox *>(obj);
if (combobox){
combobox->removeItem(combobox->currentIndex());
return true;
}
}
}
// standard event processing
return QObject::eventFilter(obj, event);
}
myQComboBox->installEventFilter(new DeleteHighlightedItemWhenShiftDelPressedEventFilter);
Upvotes: 6