如何向不在模型中的QML组合框添加额外项目



我有一个QMLComboBox,它连接了一个QAbstractListModel

ComboBox {
model: customListModel
}

我希望它在下拉列表中显示一个不在模型中的额外项目。

例如,假设customListModel中有两个项目:Apple和Orange。在下拉列表中,它应该显示以下选项:

  • 全选
  • 苹果
  • 橙色

我不能将它添加到模型中,因为它包含自定义对象,并且我在程序中的其他几个地方使用了这个模型,它会把一切都搞砸。

我该如何添加此";全选;选项添加到ComboBox???

一种方法是创建某种代理模型

  1. 您可以导出自己的QAbstractProxyModel;全选;项添加到数据中。这可能是一个更复杂的选择,但也更有效。可以在这里找到以这种方式创建代理的示例。

  2. 您也可以在QML中进行代理。它看起来像这样:

Combobox {
model: ListModel {
id: proxyModel
ListElement { modelData: "Select All" }
Component.onCompleted: {
for (var i = 0; i < customListModel.count; i++) {
proxyModel.append(customModel.get(i);
}
}
}
}

解决方案是自定义弹出窗口以添加标题。

您可以实现整个弹出组件,或者利用其contentItemListView的事实,并使用header属性:

ListModel {
id: fruitModel
ListElement {
name: "Apple"
}
ListElement {
name: "Orange"
}
}
ComboBox {
id: comboBox
model: fruitModel
textRole: "name"
Binding {
target: comboBox.popup.contentItem
property: "header"
value: Component {
ItemDelegate {
text: "SELECT ALL"
width: ListView.view.width
onClicked: doSomething()
}
}
}
}

我发现自己最近也想做一些类似的事情,并惊讶于没有简单的方法来做到这一点;有很多方法可以做到这一点,但并没有专门的API,甚至没有小部件。

我已经尝试了这里提到的两个答案,并想对它们进行总结,同时为每种方法提供完整的示例。我的要求是有一个";无";条目,所以我的答案是在这个上下文中,但你可以很容易地用";全选;。

使用QSortFilterProxyModel

这方面的C++代码是基于@SvenA的这个答案(感谢您分享工作代码!)。

优点:

  • 为了避免重复太多:这样做的好处是在另一种方法中没有缺点。例如:键导航可以工作,不需要触摸任何样式的东西,等等。这两个单独的原因就是你想要选择这种方法的很大原因,即使这确实意味着写(或复制粘贴:))模型代码需要额外的工作(这是你只需要做一次的事情)

缺点:

  • 由于您使用CCD_;无";条目,您必须将其视为一个特殊条目,而不像-1索引,后者已经被建立为意味着没有选择任何条目。这意味着需要一些额外的JavaScript代码来处理所选择的索引,但在单击时,头方法也需要这样做
  • 对于一个额外的条目来说,这是一个很大的代码,但同样如此;您应该只需要做一次,然后就可以重用它
  • 就模型操作而言,这是一个额外的间接级别。假设大多数ComboBox模型都相对较小,这不是问题。在实践中,我怀疑这会成为一个瓶颈
  • 从概念上讲;无";条目可以被视为一种元数据;也就是说,它不属于模型本身,所以这个解决方案在概念上可能不太正确

main.qml:

import QtQuick 2.15
import QtQuick.Controls 2.15
import App 1.0
ApplicationWindow {
width: 640
height: 480
visible: true
title: ""None" entry (proxy) currentIndex=" + comboBox.currentIndex + " highlightedIndex=" + comboBox.highlightedIndex
ComboBox {
id: comboBox
textRole: "display"
model: ProxyModelNoneEntry {
sourceModel: MyModel {}
}
}
}

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QSortFilterProxyModel>
#include <QDebug>
class MyModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit MyModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
private:
QVector<QString> mData;
};
MyModel::MyModel(QObject *parent) :
QAbstractListModel(parent)
{
for (int i = 0; i < 10; ++i)
mData.append(QString::fromLatin1("Item %1").arg(i + 1));
}
int MyModel::rowCount(const QModelIndex &) const
{
return mData.size();
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid))
return QVariant();
switch (role) {
case Qt::DisplayRole:
return mData.at(index.row());
}
return QVariant();
}
class ProxyModelNoneEntry : public QSortFilterProxyModel
{
Q_OBJECT
public:
ProxyModelNoneEntry(QString entryText = tr("(None)"), QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &child) const override;
private:
QString mEntryText;
};
ProxyModelNoneEntry::ProxyModelNoneEntry(QString entryText, QObject *parent) :
QSortFilterProxyModel(parent)
{
mEntryText = entryText;
}
int ProxyModelNoneEntry::rowCount(const QModelIndex &/*parent*/) const
{
return QSortFilterProxyModel::rowCount() + 1;
}
QModelIndex ProxyModelNoneEntry::mapFromSource(const QModelIndex &sourceIndex) const
{
if (!sourceIndex.isValid())
return QModelIndex();
else if (sourceIndex.parent().isValid())
return QModelIndex();
return createIndex(sourceIndex.row()+1, sourceIndex.column());
}
QModelIndex ProxyModelNoneEntry::mapToSource(const QModelIndex &proxyIndex) const
{
if (!proxyIndex.isValid())
return QModelIndex();
else if (proxyIndex.row() == 0)
return QModelIndex();
return sourceModel()->index(proxyIndex.row() - 1, proxyIndex.column());
}
QVariant ProxyModelNoneEntry::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid))
return QVariant();
if (index.row() == 0) {
if (role == Qt::DisplayRole)
return mEntryText;
else
return QVariant();
}
return QSortFilterProxyModel::data(createIndex(index.row(),index.column()), role);
}
Qt::ItemFlags ProxyModelNoneEntry::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
if (index.row() == 0)
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
return QSortFilterProxyModel::flags(createIndex(index.row(),index.column()));
}
QModelIndex ProxyModelNoneEntry::index(int row, int column, const QModelIndex &/*parent*/) const
{
if (row > rowCount())
return QModelIndex();
return createIndex(row, column);
}
QModelIndex ProxyModelNoneEntry::parent(const QModelIndex &/*child*/) const
{
return QModelIndex();
}
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<ProxyModelNoneEntry>("App", 1, 0, "ProxyModelNoneEntry");
qmlRegisterType<MyModel>("App", 1, 0, "MyModel");
qmlRegisterAnonymousType<QAbstractItemModel>("App", 1);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
#include "main.moc"

使用ListView的标头

优点:

  • CCD_ 10索引——它已经被建立为意味着没有选择任何项目——可以用来指代";无";条目
  • 无需在C++中设置QSortFilterProxyModel子类并将其公开给QML
  • 从概念上讲;无";条目可以被视为一种元数据;也就是说,它不属于模型本身,所以这个解决方案在概念上更正确

缺点:

  • 不可能选择";无";带有箭头键导航的条目。我曾短暂地尝试过解决这个问题(请参阅注释掉的代码),但没有成功
  • 必须模仿";当前项目";代理组件具有的样式。这取决于风格;如果您自己编写样式,那么您可以将delegate组件移动到它自己的文件中,并将其重新用于标头。然而,如果你使用别人的风格,你就不能这样做,必须从头开始写(不过,你通常只需要这样做一次)。例如,对于Default("Basic",在Qt 6中)样式,它意味着:
    • 设置适当的font.weight
    • 设置highlighted
    • 设置hoverEnabled
  • 必须自己设置displayText
  • 由于标题项不被视为ComboBox项,highlightedIndex属性(只读)将不会对其进行说明。可以通过在委托中将highlighted设置为hovered来解决此问题
  • 单击标题时必须执行以下操作:
    • 设置currentIndex(即单击时设置为-1)
    • 关闭组合框的弹出窗口
    • 手动发射activated()

main.qml:

import QtQuick 2.0
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: ""None" entry (header) currentIndex=" + comboBox.currentIndex + " highlightedIndex=" + comboBox.highlightedIndex
Binding {
target: comboBox.popup.contentItem
property: "header"
value: Component {
ItemDelegate {
text: qsTr("None")
font.weight: comboBox.currentIndex === -1 ? Font.DemiBold : Font.Normal
palette.text: comboBox.palette.text
palette.highlightedText: comboBox.palette.highlightedText
highlighted: hovered
hoverEnabled: comboBox.hoverEnabled
width: ListView.view.width
onClicked: {
comboBox.currentIndex = -1
comboBox.popup.close()
comboBox.activated(-1)
}
}
}
}
ComboBox {
id: comboBox
model: 10
displayText: currentIndex === -1 ? qsTr("None") : currentText
onActivated: print("activated", index)
//        Connections {
//            target: comboBox.popup.contentItem.Keys
//            function onUpPressed(event) { comboBox.currentIndex = comboBox.currentIndex === 0 ? -1 : comboBox.currentIndex - 1 }
//        }
}
}

结论

我同意";无";以及";全选;是比模型数据更多的元数据。从这个意义上说,我更喜欢header方法。在我研究这个问题的特定用例中,我不允许键导航,而且我已经重写了ComboBox的delegate属性,所以我可以为header重用该代码。

但是,如果您需要键导航,或者不想为header重新实现delegate,那么QSortFilterProxyModel方法将更实用。

相关内容

  • 没有找到相关文章

最新更新