Question

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.

Was it helpful?

Solution

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

OTHER TIPS

comboBox->removeItem(int index) // removes item at index

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 <krusader@users.sourceforge.net>      *
* Copyright (C) 2018-2019 Rafi Yanai <krusader@users.sourceforge.net>       *
* 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 <krusader@users.sourceforge.net>      *
* Copyright (C) 2018-2019 Rafi Yanai <krusader@users.sourceforge.net>       *
* 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)
{
    if (event->type() == QEvent::KeyPress) {
        auto keyEvent = static_cast<QKeyEvent *>(event);
        if (keyEvent->modifiers() == Qt::ShiftModifier && 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)
{
    if (event->type() == QEvent::KeyPress) {
        auto keyEvent = static_cast<QKeyEvent *>(event);
        if (keyEvent->modifiers() == Qt::ShiftModifier && 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"):

The program 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).

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.

You can delete the active selected value of a QComboBox by:

ui->comboBox->removeItem(ui->comboBox->currentIndex());
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top