read folder tree from a Rest API, then show them to user
Example json response after call API:
[
{"name":"/folder1/file1.txt";"size":"1KB"},
{"name":"/folder1/file2.txt";"size":"1KB"},
{"name":"/folder1/sub/file3.txt";"size":"1KB"},
{"name":"/folder2/file4.txt";"size":"1KB"},
{"name":"/folder2/file5.txt";"size":"1KB"}
]
I only want to make GUI like below image:
There are 2 options:
QTreeView
QTreeWidget
In this photo, I used QTreeWidget (with static data).
Currently, I don't know make data model for this.
I made TreeModel to set data for QtreeView.
But When them shown in GUI, All of the data only show in column Name.
I copied the code from http://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html
But now I can't resolve this problem. I need an example source code for this.
Plus, I also only want show a tree view simply.
don't use QFileSystem because data get from Rest API.
What you have to do is parsing the json by getting the name of the file and the size, then you separate the name using the "/", and add it to the model in an appropriate way.
#include <QApplication>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QStandardItemModel>
#include <QTreeView>
#include <QFileIconProvider>
QStandardItem * findChilItem(QStandardItem *it, const QString & text){
if(!it->hasChildren())
return nullptr;
for(int i=0; i< it->rowCount(); i++){
if(it->child(i)->text() == text)
return it->child(i);
}
return nullptr;
}
static void appendToModel(QStandardItemModel *model, const QStringList & list, const QString & size){
QStandardItem *parent = model->invisibleRootItem();
QFileIconProvider provider;
for(QStringList::const_iterator it = list.begin(); it != list.end(); ++it)
{
QStandardItem *item = findChilItem(parent, *it);
if(item){
parent = item;
continue;
}
item = new QStandardItem(*it);
if(std::next(it) == list.end()){
item->setIcon(provider.icon(QFileIconProvider::File));
parent->appendRow({item, new QStandardItem(size)});
}
else{
item->setIcon(provider.icon(QFileIconProvider::Folder));
parent->appendRow(item);
}
parent = item;
}
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QStandardItemModel model;
model.setHorizontalHeaderLabels({"Name", "Size"});
const std::string json = R"([
{"name":"/folder1/file1.txt";"size":"1KB"},
{"name":"/folder1/file2.txt";"size":"1KB"},
{"name":"/folder1/sub/file3.txt";"size":"1KB"},
{"name":"/folder2/file4.txt";"size":"1KB"},
{"name":"/folder2/file5.txt";"size":"1KB"}
])";
QJsonParseError parse;
// The string is not a valid json, the separator must be a comma
// and not a semicolon, which is why it is being replaced
QByteArray data = QByteArray::fromStdString(json).replace(";", ",");
QJsonDocument const& jdoc = QJsonDocument::fromJson(data, &parse);
Q_ASSERT(parse.error == QJsonParseError::NoError);
if(jdoc.isArray()){
for(const QJsonValue &element : jdoc.array() ){
QJsonObject obj = element.toObject();
QString name = obj["name"].toString();
QString size = obj["size"].toString();
appendToModel(&model, name.split("/", QString::SkipEmptyParts), size);
}
}
QTreeView view;
view.setModel(&model);
view.show();
return a.exec();
}
Note: the semicolon is not a valid separator for the json, so I had to change it.
Related
I want to show only mounted drives sorted by drive letter in windows using Qt 5.10. Here is my test code:
#include <QtWidgets>
#include <QDebug>
QStringList mountedDrives;
class FSFilter : public QSortFilterProxyModel
{
public:
FSFilter(QObject *parent) : QSortFilterProxyModel(parent) {}
protected:
bool filterAcceptsRow(int row, const QModelIndex &par) const override
{
QModelIndex idx = sourceModel()->index(row, 0, par);
QString path = idx.data(QFileSystemModel::FilePathRole).toString();
QString path2 = idx.data(Qt::DisplayRole).toString();
bool mounted = mountedDrives.contains(path);
if (mounted) {
qDebug() << "QFileSystemModel::FilePathRole =" << path
<< " Qt::DisplayRole =" << path2;
return true;
}
else return false;
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// get mounted drives only
foreach (const QStorageInfo &storage, QStorageInfo::mountedVolumes()) {
if (storage.isValid() && storage.isReady()) {
if (!storage.isReadOnly()) {
mountedDrives << storage.rootPath();
}
}
}
QFileSystemModel *fsModel = new QFileSystemModel;
fsModel->setRootPath("");
FSFilter *fsFilter = new FSFilter(fsModel);
fsFilter->setSourceModel(fsModel);
fsFilter->setSortRole(QFileSystemModel::FilePathRole); // now it works
fsModel->fetchMore(fsModel->index(0,0));
QTreeView tree;
tree.setModel(fsFilter); // different sort if use
tree.setModel(fsFilter);
tree.setSortingEnabled(true);
tree.sortByColumn(0, Qt::AscendingOrder);
tree.resize(800, 400);
tree.setColumnWidth(0, 300);
tree.setWindowTitle(QObject::tr("File System Test"));
tree.show();
return app.exec();
}
This works fine, producing this output, except it is not sorted by drive letter:
Running the same code, but setting the treeview model as
tree.setModel(fsModel); // use QFileSystemModel, not QSortFilterProxyModel
results in the following, where the items are sorting the way I want:
I thought this might be a role issue. Here is the output from my qDebug statements:
QFileSystemModel::FilePathRole = "C:/" Qt::DisplayRole = "C:"
QFileSystemModel::FilePathRole = "D:/" Qt::DisplayRole = "Data
(D:)"
QFileSystemModel::FilePathRole = "E:/" Qt::DisplayRole = "Photos
(E:)"
QFileSystemModel::FilePathRole = "F:/" Qt::DisplayRole =
"Archive (F:)"
QFileSystemModel::FilePathRole = "H:/" Qt::DisplayRole = "Old D
(H:)"
QFileSystemModel::FilePathRole = "I:/" Qt::DisplayRole = "Old C
(I:)"
So I included this line
fsFilter->setFilterRole(QFileSystemModel::FilePathRole);
but this did not help.
Solution (corrected in code listing above):
fsFilter->setSortRole(QFileSystemModel::FilePathRole);
You should reimplement virtual function lessThan in FSFilter, by default it sort alphabetically, but in this case you need take into account first and last letters in drive name.
We have a QSortFilterProxyModel installed on a QTableView and two (or more) QLineEdit for filtering the view (based on the text of these QLineEdits)
In our view we have a slot that tells us the string of lineedits and the current column that we want. Something like this :
void onTextChange(int index, QString ntext) {
filter.setFilterKeyColumn(index);
filter.setFilterRegExp(QRegExp(ntext, Qt::CaseInsensitive));
}
On the first column we have names in the second we have year of birthday.
Now we enter a year for column 2 (for example 1985). Until now filtering is ok but when we switch to the first lineedit and enter a name (for example john) the previous filtering based on year will reset.
How could we change this behaviour for our custom QSortFilterProxyModel ?
(Actually when we change the filter keycolumn the filtermodel must filter existing view not reset it)
Update...
Based on #Mike's answer :
If you interacting with unknown column count using QMap<int, QRegExp> will help you
You can subclass QSortFilterProxyModel, to make it take two separate filters (one for the name and the other for the year), and override filterAcceptsRow to return true only when both filters are satisfied.
The Qt documentation's Custom Sort/Filter Model Example shows a subclassed QSortFilterProxyModel that can take filters for dates in addition to the main string filter used for searching.
Here is a fully working example on how to make a subclassed QSortFilterProxyModel apply two separate filters for one table:
#include <QApplication>
#include <QtWidgets>
class NameYearFilterProxyModel : public QSortFilterProxyModel{
Q_OBJECT
public:
explicit NameYearFilterProxyModel(QObject* parent= nullptr):
QSortFilterProxyModel(parent){
//general parameters for the custom model
nameRegExp.setCaseSensitivity(Qt::CaseInsensitive);
yearRegExp.setCaseSensitivity(Qt::CaseInsensitive);
yearRegExp.setPatternSyntax(QRegExp::RegExp);
nameRegExp.setPatternSyntax(QRegExp::RegExp);
}
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override{
QModelIndex nameIndex= sourceModel()->index(sourceRow, 0, sourceParent);
QModelIndex yearIndex= sourceModel()->index(sourceRow, 1, sourceParent);
QString name= sourceModel()->data(nameIndex).toString();
QString year= sourceModel()->data(yearIndex).toString();
return (name.contains(nameRegExp) && year.contains(yearRegExp));
}
public slots:
void setNameFilter(const QString& regExp){
nameRegExp.setPattern(regExp);
invalidateFilter();
}
void setYearFilter(const QString& regExp){
yearRegExp.setPattern(regExp);
invalidateFilter();
}
private:
QRegExp nameRegExp;
QRegExp yearRegExp;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//set up GUI
QWidget w;
QVBoxLayout layout(&w);
QHBoxLayout hLayout;
QLineEdit lineEditName;
QLineEdit lineEditYear;
lineEditName.setPlaceholderText("name filter");
lineEditYear.setPlaceholderText("year filter");
lineEditYear.setValidator(new QRegExpValidator(QRegExp("[0-9]*")));
lineEditYear.setMaxLength(4);
hLayout.addWidget(&lineEditName);
hLayout.addWidget(&lineEditYear);
QTableView tableView;
layout.addLayout(&hLayout);
layout.addWidget(&tableView);
//set up models
QStandardItemModel sourceModel;
NameYearFilterProxyModel filterModel;;
filterModel.setSourceModel(&sourceModel);
tableView.setModel(&filterModel);
QObject::connect(&lineEditName, &QLineEdit::textChanged,
&filterModel, &NameYearFilterProxyModel::setNameFilter);
QObject::connect(&lineEditYear, &QLineEdit::textChanged,
&filterModel, &NameYearFilterProxyModel::setYearFilter);
//fill with dummy data
QVector<QString> names{"Danny", "Christine", "Lars",
"Roberto", "Maria"};
for(int i=0; i<100; i++){
QList<QStandardItem*> row;
row.append(new QStandardItem(names[i%names.size()]));
row.append(new QStandardItem(QString::number((i%9)+1980)));
sourceModel.appendRow(row);
}
w.show();
return a.exec();
}
#include "main.moc"
Based on #Hayt's answer and comment. Since you want to have two separate filters on your model, You can have two chained QSortFilterProxyModel(one does the filtering based on the name, and the other does the filtering based on the year using the first filtering model as the source model).
Here is a fully working example on how to have two separate filters for one table:
#include <QApplication>
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//set up GUI
QWidget w;
QVBoxLayout layout(&w);
QHBoxLayout hLayout;
QLineEdit lineEditName;
QLineEdit lineEditYear;
lineEditName.setPlaceholderText("name filter");
lineEditYear.setPlaceholderText("year filter");
lineEditYear.setValidator(new QRegExpValidator(QRegExp("[0-9]*")));
lineEditYear.setMaxLength(4);
hLayout.addWidget(&lineEditName);
hLayout.addWidget(&lineEditYear);
QTableView tableView;
layout.addLayout(&hLayout);
layout.addWidget(&tableView);
//set up models
QStandardItemModel sourceModel;
QSortFilterProxyModel yearFilterModel;
yearFilterModel.setSourceModel(&sourceModel);
QSortFilterProxyModel nameFilterModel;
//nameFilterModel uses yearFilterModel as source
nameFilterModel.setSourceModel(&yearFilterModel);
//tableView displayes the last model in the chain nameFilterModel
tableView.setModel(&nameFilterModel);
nameFilterModel.setFilterKeyColumn(0);
yearFilterModel.setFilterKeyColumn(1);
nameFilterModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
yearFilterModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
QObject::connect(&lineEditName, &QLineEdit::textChanged, &nameFilterModel,
static_cast<void (QSortFilterProxyModel::*)(const QString&)>
(&QSortFilterProxyModel::setFilterRegExp));
QObject::connect(&lineEditYear, &QLineEdit::textChanged, &yearFilterModel,
static_cast<void (QSortFilterProxyModel::*)(const QString&)>
(&QSortFilterProxyModel::setFilterRegExp));
//fill with dummy data
QVector<QString> names{"Danny", "Christine", "Lars",
"Roberto", "Maria"};
for(int i=0; i<100; i++){
QList<QStandardItem*> row;
row.append(new QStandardItem(names[i%names.size()]));
row.append(new QStandardItem(QString::number((i%9)+1980)));
sourceModel.appendRow(row);
}
w.show();
return a.exec();
}
If you want to connect the 2 inputs with a "and" filter you can simply layer them.
Something like this should work.
QSortFilterProxyModel namefilter;
nameFilter.setFilterKeyColumn(nameColum);
QSortFilterProxyModel yearFilter;
yearFilter.setFilterKeyColumn(yearColumn);
yearFilter.setSourceModel(model);
nameFilter.setSourceModel(&yearFilter);
view.setSource(&nameFilter);
//....
void onTextChange(int index, QString ntext)
{
switch(index)
{
case yearColumn:
yearFilter.setFilterRegExp(QRegExp(ntext, Qt::CaseInsensitive));
break;
case nameColum:
namefilter.setFilterRegExp(QRegExp(ntext, Qt::CaseInsensitive));
break;
}
}
I know this is an old thread, but here is a more generic version of the QSortFilterProxyModel with a multicolumn filter implementation. I saw someone's comment (Mike) that eluded to a solution like this, but I didn't see any code examples for it.
This design allows you to indicate weather the model is multifilter when creating an SortFilterProxyModel object. The reason for this is so that you can add other custom behavior without having to create a separate QSortFilterProxyModel subclass. In other words, if you create other custom behaviors by overriding QSortFilterProxyModel functions in this manner, you can pick and choose which custom sort/filter behaviors you want, and which standard sort/filter behaviors you want, for a given object. Obviously if you don't need or want that kind of flexibility with your subclass you can make it your own with a few small adjustments.
Header:
class SortFilterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit SortFilterProxyModel(bool multiFilterModel, QObject *parent = nullptr);
void setMultiFilterRegularExpression(const int &column, const QString &pattern);
void clearMultiFilter();
protected:
virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
private:
QMap<int, QRegularExpression> m_multiFilterMap;
bool m_multiFilterModel = false;
signals:
};
Implementation:
#include "sortfilterproxymodel.h"
//The constructor takes one additional argument (multiFilterModel) that
//will dictate filtering behavior. If multiFilterModel is false the
//setMultiFilterRegularExpression and clearMultifilter will do nothing.
SortFilterProxyModel::SortFilterProxyModel(bool multiFilterModel, QObject *parent) : QSortFilterProxyModel(parent)
{
m_multiFilterModel = multiFilterModel;
}
//This loads the QMap with the column numbers and their corresponding filters.
//This member function that should be called from your main to filter model.
void SortFilterProxyModel::setMultiFilterRegularExpression(const int &column, const QString &pattern)
{
if(!m_multiFilterModel) //notifying that this does nothing and returning
{
qDebug() << "Object is not a multiFilterModel!";
return;
}
QRegularExpression filter;
filter.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
filter.setPattern(pattern);
m_multiFilterMap.insert(column, filter);
invalidateFilter(); //this causes filterAcceptsRow to run
}
//This will effectively unfilter the model by making the pattern for all
//existing regular expressions in the QMap to an empty string, and then invalidating the filter.
//This member function should be called from main to clear filter.
void SortFilterProxyModel::clearMultiFilter()
{
if(!m_multiFilterModel) //notifying that this does nothing and returning
{
qDebug() << "Object is not a multiFilterModel!";
return;
}
QMap<int, QRegularExpression>::const_iterator i = m_multiFilterMap.constBegin();
while(i != m_multiFilterMap.constEnd())
{
QRegularExpression blankExpression("");
m_multiFilterMap.insert(i.key(), blankExpression);
i++;
}
invalidateFilter(); //this causes filterAcceptsRow to run
}
//This checks to see if the model should be multifiltered, else it will
//work like the standard QSortFilterProxyModel.
bool SortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if(m_multiFilterModel)
{
QMap<int, QRegularExpression>::const_iterator i = m_multiFilterMap.constBegin();
while(i != m_multiFilterMap.constEnd())
{
QModelIndex index = sourceModel()->index(source_row, i.key(), source_parent);
QString indexValue = sourceModel()->data(index).toString();
if(!indexValue.contains(i.value()))
{
return false; //if a value doesn't match returns false
}
i++;
}
return true; //if all column values match returns true
}
//This is standard QSortFilterProxyModel behavoir. It only runs if object is not multiFilterModel
QModelIndex index = sourceModel()->index(source_row, filterKeyColumn(), source_parent);
QString indexValue = sourceModel()->data(index).toString();
return indexValue.contains(filterRegularExpression());
}
I am developing a GUI for a SQLite database in Qt5. I use QSqlQueryModel and QTableView for storing and displaying the data.
I then created a custom delegate to replace the numeric values of certain columns with their literals in the table view (e.g. 1 = "Hello", 2 = "World") using a switch statement.
The delegate displays the data as it should and works functionally. However, the columns that the custom delegate paints over have a different format compared to the default paint method of QStyledItemDelegate. The values are up in the top left rather than centre left, the altered column no longer automatically expands the column to display the full values, and the cells in column do not turn blue or have the dotted outline when selected.
I created this example program:
#include <QApplication>
#include <QModelIndex>
#include <QPainter>
#include <QStandardItemModel>
#include <QStyledItemDelegate>
#include <QTableView>
class TestDelegate: public QStyledItemDelegate {
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
const Q_DECL_OVERRIDE
{
if (index.column() == 0) {
int value = index.model()->data(index, Qt::DisplayRole).toInt();
QString str;
switch (value) {
case 1:
str = "Hello0000";
break;
case 2:
str = "World0000";
break;
}
if (option.state.testFlag (QStyle::State_Selected)) {
painter->fillRect(option.rect, option.palette.highlight());
qApp->style()->drawItemText(painter, option.rect, option.displayAlignment, option.palette, true, str, QPalette::HighlightedText);
} else {
painter->drawText(option.rect, option.displayAlignment, str);
}
} else {
return QStyledItemDelegate::paint(painter, option, index);
}
}
};
int main(int argc, char **argv) {
QApplication app(argc, argv);
QStandardItemModel model(2, 2);
model.setHorizontalHeaderItem(0, new QStandardItem(QString("A")));
model.setHorizontalHeaderItem(1, new QStandardItem(QString("B")));
model.setData(model.index(0, 0, QModelIndex()), 1);
model.setData(model.index(1, 0, QModelIndex()), 2);
model.setItem(0, 1, new QStandardItem(QString("Hello")));
model.setItem(1, 1, new QStandardItem(QString("World0000")));
QTableView view;
view.setItemDelegate(new TestDelegate);
view.setModel(&model);
view.resizeColumnsToContents();
view.show();
app.exec();
}
This fixes the text alignment by adding options.displayAlignment to painter->drawText(); I have also added additional code in the if(option.state & QStyle::State_Selected) statement that paints the cell according to its selection state. So if it isn't selected the text is black, if it is the text turns white and the background blue. However I still cannot get the columns to expand to fit the cells' content or add a dotted line around the outside of the cell as it does with the standard delegate.
Is there a simple way to maintain the default style of the table view when using my custom paint method?
The delegate is a rather circuitous and unnecessary way of going about it. We already have a view that paints the elements perfectly fine, no need to redo that. We only need to pass modified data to the view. Thus we insert a QIdentityProxyModel viewmodel between the source and the view.
// https://github.com/KubaO/stackoverflown/tree/master/questions/proxy-reformat-39244309
#include <QtWidgets>
class RewriteProxy : public QIdentityProxyModel {
QMap<QVariant, QVariant> m_read, m_write;
int m_column;
public:
RewriteProxy(int column, QObject * parent = nullptr) :
QIdentityProxyModel{parent}, m_column{column} {}
void addReadMapping(const QVariant & from, const QVariant & to) {
m_read.insert(from, to);
m_write.insert(to, from);
}
QVariant data(const QModelIndex & index, int role) const override {
auto val = QIdentityProxyModel::data(index, role);
if (index.column() != m_column) return val;
auto it = m_read.find(val);
return it != m_read.end() ? it.value() : val;
}
bool setData(const QModelIndex & index, const QVariant & value, int role) override {
auto val = value;
if (index.column() == m_column) {
auto it = m_write.find(value);
if (it != m_write.end()) val = it.value();
}
return QIdentityProxyModel::setData(index, val, role);
}
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QStandardItemModel model{2,2};
model.setData(model.index(0, 0), 1);
model.setData(model.index(1, 0), 2);
model.setData(model.index(0, 1), "Zaphod");
model.setData(model.index(1, 1), "Beeblebrox");
RewriteProxy proxy{0};
proxy.setSourceModel(&model);
proxy.addReadMapping(1, "Hello");
proxy.addReadMapping(2, "World");
QTableView ui;
ui.setModel(&proxy);
ui.show();
return app.exec();
}
I'm trying to create an application based on model/view concept.
i need to open some directory, find all imgs in it and show them in MainWindow (subclass of QMainWindow).
The architecture is something like this:
1) via QDir create QStringList of "good" file names (using file names filter by extentions).
2) create QStandardItemModel and fill it with QStandardItem (QIcon(QImage(fileName).scaled(QSize)), fileName).
3) use QListView to show data from the model.
but there is some problems.
first of all - theModel.columnCount is, e.g., 52 but only one picture is shown on the screen and without its name.
can someone help me:
1) how to fill model correctly? my approach:
QDir dirs(dir);
QStringList imgs = dirs.entryList(QStringList() << "*.jpg" << "*.jpeg" << "*.bmp" << "*.png");
itemModel->clear();
QList<QStandardItem *> listItem;
for(int i = 0; i < imgs.count(); ++i){
QImage image = QImage(dir + "/" + imgs.at(i)).scaled(QSize(size().width()/4, size().height()/4));
QStandardItem *item = new QStandardItem();
item->setIcon(QIcon(QPixmap::fromImage(image)));
item->setData(imgs.at(i));
listItem << item;
}
itemModel->appendRow(listItem);
this code is in one slot of the MainWindow class.
2) as I understand, my view is automatically updated, so it should show all data from the model.
am I right, or some code is necessary?
3) maybe I haven't done somethings in initialization of the model and the view (the code is in te constructor of class MainWindow):
itemModel = new QStandardItemModel(this);
listView = new QListView(this);
listView->setModel(itemModel);
// listView->setFlow(QListView::LeftToRight);
// listView->setLayoutMode(QListView::Batched);
listView->setViewMode(QListView::IconMode);
listView->setResizeMode(QListView::Adjust);
// listView->setGridSize(QSize(size().width()/4, size().height()/4));
listView->setIconSize(QSize(size().width()/4, size().height()/4));
setCentralWidget(listView);
Since you determined that you needed appendColumn the last bit would be to add the QIcon as data with the Qt::DecorationRole. The follow works for me for viewing images in the same folder the program is run (though I don't know why it is shown with a grid layout).
#include <QApplication>
#include <QStandardItemModel>
#include <QListView>
#include <QDir>
#include <QStringList>
#include <QList>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QStandardItemModel* itemModel = new QStandardItemModel();
QListView* listView = new QListView();
QDir dirs(".");
QStringList imgs = dirs.entryList(QStringList() << "*.jpg" << "*.jpeg" << "*.bmp" << "*.png");
QList<QStandardItem *> listItem;
for(int i = 0; i < imgs.count(); ++i){
QImage image = QImage(dirs.absoluteFilePath(imgs.at(i))).scaled(QSize(80, 60));
QStandardItem *item = new QStandardItem();
item->setData(QVariant(QPixmap::fromImage(image)), Qt::DecorationRole);
listItem << item;
}
itemModel->appendColumn(listItem);
listView->setModel(itemModel);
listView->setViewMode(QListView::IconMode);
listView->show();
a.exec();
}
I have three comboboxes with three similar options, 'one', 'two', 'three', and I want to prevent the same option in different combobox.
Example:
combobox1 : 'one',
so when I go choose in combobox2 and combobox3 there's only 'two' and 'three' to choose from.
I know I could do this in a combination of for loop and if, but can anyone help me with some trick I could use here?
Write your own class MyComboBox which is derived from QComboBox.
Implement a slot in MyComboBox that would do something like this:
void excludeIndex(const QString & text)
{
// Get the list of all the options for the ComboBox
QStringList list = getIndices();
// Remove one (the only) item 'text' from the list
list.removeOne( text );
// Clear the ComboBox and fill with the new values
this->clear();
this->addItems( list );
}
In your parent widget/application, connect the signal void currentIndexChanged(const QString & text) from each 'sending' MyComboBox or QComboBox to this slot of the 'receiving' MyComboBox.
If you need to exclude the values from multiple other ComboBoxes, then it might be better to implement the slot in the parent Widget. That way, you can loop over all the 'receiving' ComboBoxes. Per ComboBox you will read all the current values of the other ComboBoxes and remove those values from list. Your slot in the parent Widget will not need an input QString anymore.
How about using only one combobox? There are only six possible options:
one two three
one three two
two one three
two three one
three one two
three two one
It would be much easier for the user to use only one combobox instead of using three comboboxes whose available options are continuously changing.
Here is one solution.
Basically it initializes all of the boxes with all the items, and as a box's current text is changed, it removes that text from the other boxes. If the previous text that the box contained wasn't blank, it adds it back into all the other boxes.
If you care to test it with more than 8, change the variable m_numOfBoxes in mainwindow.cpp to some other value.
main.cpp
#include <QApplication>
#include "mainwindow.h"
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>
#include <QComboBox>
#include <QList>
#include <QStringList>
#include <QMap>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void on_comboBox_changed();
private:
QList <QComboBox *> m_boxes;
QMap <QComboBox *, QString> m_previousText;
int m_numOfBoxes;
QStringList m_allItems;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include <QVBoxLayout>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QVBoxLayout * vbox = new QVBoxLayout();
m_numOfBoxes = 8;
m_allItems << "";
for(int i = 0; i< m_numOfBoxes; i++)
{
m_allItems << QString::number(i+1);
}
for(int i = 0; i< m_numOfBoxes; i++)
{
QComboBox * temp = new QComboBox;
QObject::connect(temp, SIGNAL(currentIndexChanged(int)), this, SLOT(on_comboBox_changed()));
vbox->addWidget(temp);
temp->addItems(m_allItems);
m_boxes.append(temp);
m_previousText[temp] = "";
}
this->setCentralWidget(new QWidget());
this->centralWidget()->setLayout(vbox);
}
MainWindow::~MainWindow()
{
}
void MainWindow::on_comboBox_changed()
{
QComboBox * editedBox = qobject_cast <QComboBox *> (QObject::sender());
QString currentText = editedBox->currentText();
if(currentText == m_previousText[editedBox])
{
return;
}
foreach(QComboBox * box, m_boxes)
{
if(box == editedBox)
{
continue;
}
if(currentText != "")
{
// remove the current text from the other boxes
int index = box->findText(currentText);
if(index != -1)
{
box->removeItem(index);
}
}
if(m_previousText[editedBox] != "")
{
// add the previous text back into the lists for the other boxes
box->addItem(m_previousText[editedBox]);
qDebug() << "Adding back" << m_previousText[editedBox];
}
}
m_previousText[editedBox] = currentText;
}
Note that when an item is added back into a box's list, it just gets tacked onto the end of the list, so over time the order of your items may get scrambled.
Hope that helps.