我正在尝试调整 QComboBox 的 ui,以便用户可以从下拉列表中删除项目(无需先选择它们)。
背景是我正在使用QComboBox来指示当前打开的是哪个数据文件。我还使用它作为最近打开的文件的缓存。我希望用户能够删除他不想再列出的条目。这可以通过点击删除键、上下文菜单或任何易于实现的方式来实现。我不想首先依赖于选择该项目。在 Firefox 中也可以找到类似的行为,可以删除条目字段的旧缓存建议。
我正在考虑对 QComboBox 使用的列表视图进行子类化,但是,我没有找到足够的文档来帮助我开始。
如果有任何提示和建议,我将不胜感激。我正在使用 PyQt,但 C++ 示例没有问题。
我使用 installEventFilter 文档中的代码解决了这个问题。
//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);
comboBox->removeItem(int index) // removes item at index
您可以使用专门的类来自动化流程,因此最终可以节省时间。
例如,有一个名为 KrHistoryComboBox 的类(继承自 KHistoryComboBox 类),在名为 Krusader 的程序中使用。
虽然这次,对于这个答案:以下代码是直接继承自
QComboBox
的版本(尽管QComboBox
不能做与KHistoryComboBox
一样多的事情),以及它的使用示例:
文件main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
文件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
文件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;
}
文件 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
文件 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<KHistoryComboBox *>(obj);
if (comboBox != nullptr) {
QString entryToDelete = comboBox->currentText();
// Delete the current item
comboBox->removeItem(comboBox->currentIndex());
// The item has to be deleted also from the completion list
comboBox->completionObject()->removeItem(entryToDelete);
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) {
QString entryToDelete = itemView->currentIndex().data().toString();
// Delete the current item from the popup list
itemView->model()->removeRow(itemView->currentIndex().row());
// The item has to be deleted also from the completion list of the KHistoryComboBox
if (itemView->parent() != nullptr) {
auto comboBox = qobject_cast<KHistoryComboBox *>(itemView->parent()->parent());
if (comboBox != nullptr) {
comboBox->completionObject()->removeItem(entryToDelete);
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));
}
文件 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
文件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>
这是在按 Shift+Del 之前正在执行的示例程序的屏幕截图(这将删除名为“two”的选项):
注意:当前答案中的一些源代码基于 https://doc.qt.io/qt-5/qobject.html#installEventFilter,https://forum.qt.io/post/160618以及https://stackoverflow.com/a/26976984中名为“nwp”的用户的出色工作(尽管该答案并不如果弹出列表被看到,并且它有“内存泄漏”(对象被构造但没有被销毁),则包含删除弹出列表元素的代码,因此如果开发人员稍后添加像
~DeleteHighlightedItemWhenShiftDelPressedEventFilter() { QTextStream(stdout) << "DESTRUCTED" << endl; }
这样的析构函数看到析构函数的代码从未被执行,因此存在内存泄漏;目前我还没有得到 stackoverflow 点以便在中添加注释https://stackoverflow.com/a/26976984)。
很抱歉这么晚才看到这个帖子,但我想贡献一些我发现的其他方法,以防万一其他人像我一样在寻找它。这些方法已经使用 Qt 5.6 进行了测试。我不能保证它们在其他版本中也能工作。
一种可能是监听 QCombobox 的 view() 的“pressed()”信号。这样我们就可以使用鼠标右键从列表中删除项目。我很惊讶地发现 view() 始终可用,从不为 NULL,并且可以在显示时删除项目,因此以下方法效果很好:
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());
}
}
第二个选项是安装事件过滤器,但也要安装到视图中。这样我们就可以使用删除键或其他任何键来删除项目。测试 NULL 指针和无效行索引可能是个好主意,但为了清楚起见,我省略了这一点。
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);
}
就是这样。
您可以通过以下方式删除 QComboBox 的活动选定值:
ui->comboBox->removeItem(ui->comboBox->currentIndex());
如果您可以接受先选择条目, 除了组合框之外,还可以接受“删除”按钮, 您可以使用合适的插槽扩展 QComboBox。
class IQComboBox : public QComboBox
{
Q_OBJECT
public :
IQComboBox(QWidget *parent = nullptr) : QComboBox(parent) {}
public slots :
void remove_current_item(void) { removeItem(currentIndex()); }
};
然后将按钮的“释放”信号与新插槽连接。
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()));