我正在开发一个名为“Configuration Manager”的窗口,它允许用户:
- 查看
ListView
组件中具有特定扩展名的文件夹上的所有文件, - 根据用户在
TextEdit
组件中输入的文本过滤所显示的项目, - 按最后修改日期对显示的项目进行排序,
- 只选择一个项目来加载相关文件,
- 选择一个或多个删除相关文件,
- 输入名称以在新文件中保存数据,
列表视图显示过滤/排序的项目。当用户点击列表视图中的一个项目时,我会改变这个项目的颜色。如果他点击另一个项目,前一个项目的颜色会回到默认值,新项目的颜色会改变。
数据存储在后端的一个从QAsbtractListModel
继承的类中,我使用另一个从QSortFilterProxyModel
继承的类来显示过滤和排序的元素。
列表视图最多可以显示100个元素,因此我添加了一个滚动条,以便能够选择显示在其上的任何委托。
如果我选择第一个项目,颜色会正常更新。如果我点击第一个项目旁边的另一个项目,这两个项目的颜色变化(以前和新的)也可以。我有一个问题,如果一个选择一个项目的顶部,滚动底部的列表视图,并选择另一个项目。这样,以前的项目没有更新,仍然是与选定的颜色。在后端,通过一些qquery,我可以确认前一个项目没有更新,因为方法setData没有像我选择一个项目时那样被调用(在GUI上更新颜色,在模型上为这个元素在后端更新布尔值)。
我这里说的不是用键盘的CTRL或CTRL键进行多重选择,但问题是一样的,只有一些项目被更新。
我想我缺少了列表视图的一些属性,因为似乎不是所有的委托都在该高速缓存中(所以在滚动和选择新项目时不会更新)。
我添加下面的代码,我现在不能做更短。提前感谢您的任何帮助。
主文件
- main.cpp
#include <QGuiApplication>
#include <QQuickView>
#include <QQmlContext>
#include <QQmlApplicationEngine>
#include "FilenameListModel.h"
#include "FilterProxyModel.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
FilenameListModel model;
FilterProxyModel filterModel;
filterModel.setSourceModel(&model);
engine.rootContext()->setContextProperty("_FilteredRunwayConfigurationFilesModel", &filterModel);
engine.load(QUrl("qrc:/main.qml"));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
字符串
- main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5
import QtQml.Models 2.12
import QtQuick.Layouts 1.12
ApplicationWindow
{
id: root
property bool isSaving: false
minimumWidth: 600
minimumHeight: 400
visible: true
modality: Qt.ApplicationModal
Component.onCompleted:
{
// Update the backend with current saving mode
_FilteredRunwayConfigurationFilesModel.isSavingMode = isSaving
}
DelegateModel
{
id: delegateModel
model: _FilteredRunwayConfigurationFilesModel
delegate: Rectangle
{
id: item_delegate
property bool checked: false
width: ListView.view.width - 10
height: name.implicitHeight
color: model.isChecked? "lightgreen" : "lightblue"
radius: 10
onCheckedChanged:
{
listViewFilter.checkChanged()
}
Connections
{
target: listViewFilter
// Called after the slot onCheckOne of the listViewFilter component
onCheckOne:
{
model.isChecked = (idx === index)
checked = model.isChecked
}
onCheckMul:
{
if (idx > listViewFilter.mulBegin)
{
model.isChecked = (index >= listViewFilter.mulBegin && index <= idx)
checked = model.isChecked
}
else
{
model.isChecked = (index <= listViewFilter.mulBegin && index >= idx)
checked = model.isChecked
}
}
}
Connections
{
target: textFilterString
// Called after this signal has been catched by the textFilterString component
onTextChanged:
{
// Reset all status
model.isChecked = false
checked = model.isChecked
}
}
Rectangle
{
id: leftRectangle
anchors
{
top: parent.top
bottom: parent.bottom
left: parent.left
right: rightRectangle.left
}
color: "transparent"
Text
{
id: name
anchors.fill: parent
anchors.margins: 5
verticalAlignment: Qt.AlignVCenter
horizontalAlignment: Qt.AlignHCenter
text: "%1".arg(model.name)
}
}
Rectangle
{
id: rightRectangle
anchors
{
top: parent.top
bottom: parent.bottom
right: parent.right
}
color: "transparent"
width: parent.width * 0.3
Text
{
id: date
anchors.fill: parent
anchors.margins: 5
verticalAlignment: Qt.AlignVCenter
horizontalAlignment: Qt.AlignHCenter
text:
{
var text = "%1".arg(model.date)
return text
}
}
}
MouseArea
{
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked:
{
if (!isSaving)
{
switch(mouse.modifiers)
{
case Qt.ControlModifier:
model.isChecked = !model.isChecked;
checked = model.isChecked
break
case Qt.ShiftModifier:
listViewFilter.checkMul(index)
break
default:
listViewFilter.checkOne(index)
listViewFilter.mulBegin = index
break
}
}
else
{
textFilterString.text = model.name
}
}
}
}
}
ColumnLayout
{
anchors.fill: parent
anchors.margins: 5
TextField
{
id: textFilterString
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
placeholderText: isSaving ? "Write the filename with the '.atols' extension" : "Write the begin of the filename"
onTextChanged:
{
_FilteredRunwayConfigurationFilesModel.filenameBeginning = text;
listViewFilter.checkChanged()
}
}
RowLayout
{
Layout.fillWidth: true
Layout.preferredHeight: textFilterString.height
Layout.alignment: Qt.AlignHCenter
Button
{
id: buttonLoad
Layout.preferredWidth: 100
Layout.preferredHeight: parent.height
text: "LOAD"
enabled: false
visible: !isSaving
onClicked:
{
_FilteredRunwayConfigurationFilesModel.loadConfigurationFile(listViewFilter.selectedIndex)
}
}
Button
{
id: buttonDelete
Layout.preferredWidth: 100
Layout.preferredHeight: parent.height
text: "DELETE"
enabled: false
visible: !isSaving
onClicked:
{
_FilteredRunwayConfigurationFilesModel.deleteConfigurationFile(listViewFilter.selectedIndex)
}
}
Button
{
id: buttonSave
Layout.preferredWidth: 100
Layout.preferredHeight: parent.height
text: "SAVE"
enabled: textFilterString.text
visible: isSaving
onClicked:
{
// TODO
}
}
}
ListView
{
id: listViewFilter
property int mulBegin: 0
property var selectedIndex: []
signal checkOne(int idx)
signal checkMul(int idx)
// Called first when executing from a delegate item
onCheckOne:
{
listViewFilter.mulBegin = idx
}
function checkChanged()
{
listViewFilter.selectedIndex = []
// Check how many items are selected in the list
var nSelected = 0
for (var i = 0; i < listViewFilter.count; i++)
{
if (listViewFilter.contentItem.children[i] && listViewFilter.contentItem.children[i].checked)
{
nSelected++
listViewFilter.selectedIndex.push(i)
}
}
// Enable or disable buttons to load or delete the selected items
var loadEnable = false
var deleteEnable = false
switch(nSelected)
{
case 1: // One item selected => Load and Delete available
loadEnable = true
deleteEnable = true
break
case 0: // No items selected => Nothing available
loadEnable = false
deleteEnable = false
break
default: // Multiple items selected => Only Delete available
loadEnable = false
deleteEnable = true
break
}
buttonLoad.enabled = loadEnable
buttonDelete.enabled = deleteEnable
}
Layout.fillWidth: true
Layout.fillHeight: true
model: delegateModel
ScrollBar.vertical: ScrollBar
{
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
policy: listViewFilter.contentHeight > listViewFilter.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
active: true
}
}
TextField
{
Layout.fillWidth: true
Layout.preferredHeight: textFilterString.height
text: "No files with this filter"
verticalAlignment: Qt.AlignTop
horizontalAlignment: Qt.AlignHCenter
visible: listViewFilter.count === 0
}
}
}
型
基础型号
- FilenameListModel.h
#ifndef FILENAMELISTMODEL_H
#define FILENAMELISTMODEL_H
#include <QAbstractListModel>
#include <QDate>
class FilenameListModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit FilenameListModel(QObject *parent = nullptr);
public:
enum Roles
{
NameRole = Qt::UserRole,
DateRole,
CheckedRole
};
int rowCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override;
QHash<int, QByteArray> roleNames() const override;
private:
struct Entry
{
QString name;
QDate date;
bool isChecked;
};
QVector<Entry> m_entries;
};
#endif // FILENAMELISTMODEL_H
型
- FilenameListModel.cpp
#include "FilenameListModel.h"
#include <QDebug>
FilenameListModel::FilenameListModel(QObject *parent)
: QAbstractListModel (parent)
{
m_entries = {
Entry{"Tata", QDate(2022, 5, 14)},
Entry{"Tititata", QDate(2020, 3, 12)},
Entry{"Toto", QDate(2018, 1, 10)},
Entry{"Tata", QDate(2019, 2, 11)},
Entry{"Toto", QDate(2021, 4, 13)},
Entry{"Titi", QDate(2023, 6, 15)},
Entry{"Tititata", QDate(2020, 3, 12)},
Entry{"Toto", QDate(2018, 1, 10)},
Entry{"Tata", QDate(2019, 2, 11)},
Entry{"Toto", QDate(2021, 4, 13)},
Entry{"Titi", QDate(2023, 6, 15)},
Entry{"Tititata", QDate(2020, 3, 12)},
Entry{"Toto", QDate(2018, 1, 10)},
Entry{"Tata", QDate(2019, 2, 11)},
Entry{"Toto", QDate(2021, 4, 13)},
Entry{"Titi", QDate(2023, 6, 15)},
Entry{"Tititata", QDate(2020, 3, 12)},
Entry{"Toto", QDate(2018, 1, 10)},
Entry{"Tata", QDate(2019, 2, 11)},
Entry{"Toto", QDate(2021, 4, 13)},
Entry{"Titi", QDate(2023, 6, 15)},
Entry{"Tititata", QDate(2020, 3, 12)},
Entry{"Toto", QDate(2018, 1, 10)},
Entry{"Tata", QDate(2019, 2, 11)},
Entry{"Toto", QDate(2021, 4, 13)},
Entry{"Titi", QDate(2023, 6, 15)},
Entry{"Tititata", QDate(2020, 3, 12)},
Entry{"Toto", QDate(2018, 1, 10)},
Entry{"Tata", QDate(2019, 2, 11)},
Entry{"Toto", QDate(2021, 4, 13)},
Entry{"Titi", QDate(2023, 6, 15)},
Entry{"Tititata", QDate(2020, 3, 12)},
Entry{"Toto", QDate(2018, 1, 10)},
Entry{"Tata", QDate(2019, 2, 11)},
Entry{"Toto", QDate(2021, 4, 13)},
Entry{"Titi", QDate(2023, 6, 15)},
Entry{"Tititata", QDate(2020, 3, 12)},
Entry{"Toto", QDate(2018, 1, 10)},
Entry{"Tata", QDate(2019, 2, 11)},
Entry{"Toto", QDate(2021, 4, 13)},
Entry{"Titi", QDate(2023, 6, 15)},
Entry{"Tititata", QDate(2020, 3, 12)},
Entry{"Toto", QDate(2018, 1, 10)},
Entry{"Tata", QDate(2019, 2, 11)},
Entry{"Toto", QDate(2021, 4, 13)},
Entry{"Titi", QDate(2023, 6, 15)},
Entry{"Tititata", QDate(2020, 3, 12)},
Entry{"Toto", QDate(2018, 1, 10)},
Entry{"Tata", QDate(2019, 2, 11)},
Entry{"Toto", QDate(2021, 4, 13)},
Entry{"Titi", QDate(2023, 6, 15)},
Entry{"Tititata", QDate(2020, 3, 12)},
Entry{"Toto", QDate(2018, 1, 10)},
Entry{"Tata", QDate(2019, 2, 11)},
Entry{"Toto", QDate(2021, 4, 13)},
Entry{"Titi", QDate(2023, 6, 15)}
};
}
int FilenameListModel::rowCount( const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return m_entries.count();
}
QVariant FilenameListModel::data(const QModelIndex &index, int role) const
{
if ( !index.isValid() )
return QVariant();
const Entry &entry = m_entries.at(index.row());
switch(role)
{
case NameRole:
return entry.name;
case DateRole:
return entry.date.toString("yyyy-MM-dd - hh::mm::ss");
case CheckedRole:
{
return entry.isChecked;
}
default:
return QVariant();
}
}
bool FilenameListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
// Method called when QML change a parameter of the model
if (!hasIndex(index.row(), index.column(), index.parent()) || !value.isValid())
return false;
if (m_entries[index.row()].isChecked != value.toBool())
qDebug() << "backend:" << index.row() << "/" << value.toBool();
m_entries[index.row()].isChecked = value.toBool();
emit dataChanged(index, index, { role });
return true;
}
QHash<int, QByteArray> FilenameListModel::roleNames() const
{
static QHash<int, QByteArray> mapping {
{NameRole, "name"},
{DateRole, "date"},
{CheckedRole, "isChecked"}
};
return mapping;
}
型
代理模式
- FilterProxyModel.h
#ifndef FILTERPROXYMODEL_H
#define FILTERPROXYMODEL_H
#include <QSortFilterProxyModel>
class FilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY(QString filenameBeginning WRITE setFilenameBeginning READ getFilenameBeginnning NOTIFY filenameBeginningChanged)
Q_PROPERTY(bool isSavingMode WRITE setIsSavingMode NOTIFY isSavingModeChanged)
public:
explicit FilterProxyModel(QObject *parent = nullptr);
void setFilenameBeginning(QString newStr);
QString getFilenameBeginnning();
void setIsSavingMode(bool newMode);
public slots:
Q_INVOKABLE void deleteConfigurationFile(QVector<int> indexToDelete);
Q_INVOKABLE void loadConfigurationFile(QVector<int> indexToLoad);
signals:
void filenameBeginningChanged();
void isSavingModeChanged();
protected:
bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override;
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
private:
QString m_filenameBeginning;
bool m_isSavingMode;
};
#endif // FILTERPROXYMODEL_H
型
- FilterProxyModel.cpp
#include "FilterProxyModel.h"
#include "FilenameListModel.h"
#include <QDebug>
FilterProxyModel::FilterProxyModel(QObject *parent)
: QSortFilterProxyModel (parent)
{
m_isSavingMode = false;
sort(0, Qt::SortOrder::DescendingOrder);
}
bool FilterProxyModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const
{
const QDate leftDate = sourceLeft.data(FilenameListModel::DateRole).toDate();
const QDate rightDate = sourceRight.data(FilenameListModel::DateRole).toDate();
return leftDate < rightDate;
}
bool FilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
// In saving mode, keep all files on the list
if (m_isSavingMode)
return true;
// Otherwise, filter the rows
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
const QString name = index.data(FilenameListModel::NameRole).toString();
return strncmp(name.toLower().toStdString().c_str(), m_filenameBeginning.toLower().toStdString().c_str(), m_filenameBeginning.size()) == 0;
}
void FilterProxyModel::setFilenameBeginning(QString newStr)
{
m_filenameBeginning = newStr;
emit filenameBeginningChanged();
invalidateFilter();
}
QString FilterProxyModel::getFilenameBeginnning()
{
return m_filenameBeginning;
}
void FilterProxyModel::deleteConfigurationFile(QVector<int> indexToDelete)
{
for (int i = 0; i < indexToDelete.size(); i++)
{
qDebug() << this->data(this->index(indexToDelete[i], 0), FilenameListModel::Roles::NameRole);
}
}
void FilterProxyModel::loadConfigurationFile(QVector<int> indexToLoad)
{
// The input should be with only one element on it
qDebug() << this->data(this->index(indexToLoad[0], 0), FilenameListModel::Roles::DateRole);
}
void FilterProxyModel::setIsSavingMode(bool newMode)
{
m_isSavingMode = newMode;
}
型
1条答案
按热度按时间64jmpszr1#
感谢@JarMan的建议,我简化了QML部分,在listview的委托上没有更多的算法部分。现在后端更新了我想要的模型上每个项目的角色
CheckedRole
。滚动不再是一个问题。我把代码的修改放在这里:
主文件
字符串
基础型号
代理模式
型
型