Update and sort Qt ComboBoxes alphabetically - c++

I want to write a program, working like a little navigation system uisng the Qt framework, but i still am very new to it.
I created a dialog with two Comboboxes. Each combobox contains all "citynames".
At the initialization the content of both boxes is sorted alphabetically.
If a name in the first box is selected, it should not be displayed in the second one and the other way too.
I successfully removed the item and added it again, if another item is selected, but know i cannot sort them anymore
This is what i tried so far for updating:
for(std::vector<City>::iterator iter = citylist.begin(); iter != citylist.end(); iter++){
if(ui->combo2->currentText() != (*iter).getName()
and ui->combo1->findText((*iter).getName()) == -1){
ui->combo1->addItem((*iter).getName(),QComboBox::InsertAlphabetically);
}
}
but it does not insert the items alphabetically...
so i tried to sort it afterwards:
QSortFilterProxyModel* proxy = new QSortFilterProxyModel(ui->combo1);
proxy->setSourceModel(ui->combo1->model());
// combo's current model must be reparented,
// otherwise QComboBox::setModel() will delete it
ui->combo1->model()->setParent(proxy);
ui->combo1->setModel(proxy);
// sort
ui->combo1->model()->sort(0);
But if i try to call this function an error occurs and the application terminates.
So is anyone out there, who is able to help me?

You were nearly there!
ui->comboBox1.addItem("myitem");
// qApp->processEvents(); not really needed
ui->comboBox1.model()->sort(0);

You're trying to use QComboBox's internal model as source model for proxy. This is not going to work because QComboBox owns its internal model and when you call QComboBox::setModel, previous model is deleted (despite you reset its parent). You need to create a separate source model. Conveniently, you can use one source model for both comboboxes if cities list is the same.
Using QSortFilterProxyModel for sorting is easy, but it's surprisingly hard to exclude one specific string with it. You can subclass QSortFilterProxyModel::filterAcceptsRow and implement the behavior you want. I decided to use a bit of black magic instead (see this answer).
Private class fields:
private:
QSortFilterProxyModel *proxy1, *proxy2;
Source:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QStandardItemModel* model = new QStandardItemModel(this);
foreach(QString name, QStringList()
<< "Paris"<< "London"<< "Moscow" << "Tokyo" << "Berlin" << "Amsterdam") {
model->appendRow(new QStandardItem(name));
}
proxy1 = new QSortFilterProxyModel();
proxy1->setSourceModel(model);
proxy1->sort(0);
ui->comboBox1->setModel(proxy1);
proxy2 = new QSortFilterProxyModel();
proxy2->setSourceModel(model);
proxy2->sort(0);
ui->comboBox2->setModel(proxy2);
connect(ui->comboBox1, &QComboBox::currentTextChanged,
this, &MainWindow::something_changed);
connect(ui->comboBox2, &QComboBox::currentTextChanged,
this, &MainWindow::something_changed);
something_changed();
}
void MainWindow::something_changed() {
ui->comboBox1->blockSignals(true); //prevent recursion
ui->comboBox2->blockSignals(true);
proxy2->setFilterRegExp(QString("^(?!(%1)$)").arg(
QRegExp::escape(ui->comboBox1->currentText())));
proxy1->setFilterRegExp(QString("^(?!(%1)$)").arg(
QRegExp::escape(ui->comboBox2->currentText())));
ui->comboBox1->blockSignals(false);
ui->comboBox2->blockSignals(false);
}
Tested in Qt 5.3.

Related

Selecting a QListWidgetItem when adding it

I have a QListWidget in a Form with a QListWidgetItem displaying "Add new". When I click on it, I'd like a serie of things to happen:
A QInputDialog::getText asks for the content of the new item.
A new item is added to the list with the given text.
The list is sorted, except for the "Add new" that stays at the end (this is done by removing the "Add new" item, sorting, and adding the removed item again).
The new item is selected.
That last part is the one I'm having trouble. I've tried many different approaches, all leading to the same result: the item I want selected has a dashed border and it understood as selected (by ui->list->selectedItems() for example), but the selection color stays on the last item before the "Add new".
What I tried
item->setSelected(true);
ui->list->setCurrentItem(item);
ui->list->setCurrentRow(ui->list->row(item);
What I noticed
When the debugger is on with a breakpoint that slowly goes through those steps, I notice everything seems to work nicely, but the UI isn't updated before the function I'm calling is done.
Also, when I want to select a given item from the list from a slot called by another button click, it works correctly with item->setSelected(true); (and the others too).
My guess: I can't select the item in the same function I add it because I can't graphically select something that isn't there yet.
Any guess on how to achieve this?
Note
If you're experiencing the same problem, please read the comment on the selected answer, it was actually a signal problem!
Did you try to select added item and after that set current row to row index of added item. This works in my example.
Example: mainwindow.cpp
#include <QInputDialog>
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->listWidget->addItem("Add New");
connect(ui->listWidget, SIGNAL(itemClicked(QListWidgetItem *)), this, SLOT(slot_itemClicked(QListWidgetItem *)));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::slot_itemClicked(QListWidgetItem *item)
{
if (item && (item->text() == "Add New"))
{
QString text = QInputDialog::getText(this, "Input item text", "Text: ");
QListWidgetItem *newItem = new QListWidgetItem(text);
// Add new item and sort list
ui->listWidget->addItem(newItem);
ui->listWidget->sortItems();
// Move "Add New" item to list end
item = ui->listWidget->takeItem(ui->listWidget->row(item));
ui->listWidget->addItem(item);
// Select new item
// Set current row to index of new item row
newItem->setSelected(true);
ui->listWidget->setCurrentRow(ui->listWidget->row(newItem));
}
}
If you can select item from regular slot so just emit dummy signal from very short timer. Like this
//add item
//...
QTimer::singleShot(1, this, SLOT(MySlotForSelectItem())); // 1 ms timer
MainWindow::MySlotForSelectItem()
{
//select item
}

Strange behavior with QTreeView / QFileSystemModel / QLineEdit

I have a QTreeView linked to a QFileSystemModel. I also have a QLineEdit with two purposes :
when the user click the QTreeView, the QLineEdit is populate with the item clicked (path & filename if applied)
when the user edit the QLineEdit and press enter, I want to :
a. synchronize the QTreeView to that position if the path exist
b. if the path do not exist, I want to repopulate the QLineEdit with the current position of the QTreeView
Everything is working well except when the condition 2a above is met, I want to :
return focus on QTreeView
expand the current folder
resize the 1st column to contents
and scroll the view to that index (scroll to top)
Here is the code I put in place :
QDirectorySelector::QDirectorySelector(QString const & title, QWidget *parent)
: QWidget(parent)
{
mDirectoryModel = new QFileSystemModel;
mDirectoryModel->setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
mDirectoryModel->setRootPath("");
mDirectoryTitle = new QLabel(title);
mDirectoryView = new QTreeView;
mDirectoryView->setModel(mDirectoryModel);
mDirectoryView->setSortingEnabled(true);
mDirectoryView->sortByColumn(0, Qt::AscendingOrder);
mDirectoryView->setSelectionMode(QAbstractItemView::ExtendedSelection);
mDirectoryEdit = new QLineEdit;
QVBoxLayout * layout = new QVBoxLayout;
layout->addWidget(mDirectoryTitle);
layout->addWidget(mDirectoryView);
layout->addWidget(mDirectoryEdit);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
connect(mDirectoryView, &QTreeView::clicked, this, &QDirectorySelector::synchronizeEditFromTree);
connect(mDirectoryEdit, &QLineEdit::returnPressed, this, &QDirectorySelector::synchronizeTreeFromEdit);
connect(this, &QDirectorySelector::synchronizationDone, this, &QDirectorySelector::reactToExpansionStep1, Qt::QueuedConnection);
connect(this, &QDirectorySelector::synchronizationStep1Done, this, &QDirectorySelector::reactToExpansionStep2, Qt::QueuedConnection);
}
void QDirectorySelector::synchronizeEditFromTree(QModelIndex const & index)
{
QString directory{ mDirectoryModel->fileInfo(index).absoluteFilePath() };
mDirectoryEdit->setText(directory);
}
void QDirectorySelector::synchronizeTreeFromEdit()
{
if (QDir(mDirectoryEdit->text()).exists()) {
mDirectoryView->setFocus();
mDirectoryView->setCurrentIndex(mDirectoryModel->index(mDirectoryEdit->text()));
mDirectoryView->setExpanded(mDirectoryView->currentIndex(), true);
emit synchronizationDone(mDirectoryView->currentIndex());
} else {
selectDirectory(mDirectoryView->currentIndex());
}
}
void QDirectorySelector::reactToExpansionStep1(QModelIndex const & index)
{
mDirectoryView->resizeColumnToContents(0);
emit synchronizationStep1Done(mDirectoryView->currentIndex());
}
void QDirectorySelector::reactToExpansionStep2(QModelIndex const & index)
{
mDirectoryView->scrollTo(mDirectoryView->currentIndex(), QAbstractItemView::PositionAtTop);
}
If I type an existing path and press enter I got this unexpected behavior :
the focus is done (ok)
the current index of the view is done correctly (ok)
the expension of the sub folder is done (ok)
the resize of the 1st column is done only at the path level without including the expended part (not expected)
the scroll to is not done at all (not ok)
At this point, if I return to the QLineEdit and press enter again, everything is done correctly. In fact, I observed this pattern, if the specified folder was never "open" (seen/loaded/expended), I get the unexpected behavior. If the specified was "open" (seen/loaded/expended), I get the expected behavior.
I also try to call two times the code in the finishDirectoryEditing without any success. Everything is the same.
Is anyone have any Idea of what is happening here? And better, any suggestion to solve this unexpected behavior.
I think that the QTreeView working with the QFileSystemModel is already too awkward in term of behavior. I mean, it never hide the expansion mark of an empty folder. May be it's possible to change this but I got no success either with the fetch more strategy I found elsewhere on the net.
Thanks to all!

Qt RightClick on QListWidget Opens Contextmenu and Delete Item

I want to know how can I open a popup menu when I right click on the table items. In the popup menu some actions like add and delete should be given, which will create a new row or delete the selected row.
I am a new in the Qt world, so if anybody can give me the full details (with code if possible) then I will be really grateful towards him/her.
Thank you.
My goal: Only in the area of QListWidget and only if you click on an item, the menu with Delete will be opened.
Edit : Ok I solved the problem with the QListWidget and the menu. Now the following must be accomplished:
If you click on an item with the right mouse button, and then click Delete, then the item will be deleted.
My code:
void ProvideContextMenu(const QPoint &); // MainWindow.h
// In MainWindow.cpp
ui->listFiles->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->listFiles, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(ProvideContextMenu(const QPoint &)));
void MainWindow::ProvideContextMenu(const QPoint &pos)
{
QPoint item = ui->listFiles->mapToGlobal(pos);
QMenu submenu;
submenu.addAction("ADD");
submenu.addAction("Delete");
QAction* rightClickItem = submenu.exec(item);
if (rightClickItem && rightClickItem->text().contains("Delete") )
{
ui->listFiles->takeItem(ui->listFiles->indexAt(pos).row());
}
}
Edit2 : Ok I solved the whole problem :D. I uploaded my code, if somebody needs something like that it can help him/her.
Firstly you need to create slot for opening context menu:
void showContextMenu(const QPoint&);
At constructor of your class, which used QListWidget, set context menu policy to custom and connect QListWidget::customContextMenuRequested(QPoint) signal and showContextMenu() slot like this:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
setupUi(this);
listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
connect(listWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
}
Then need to realize context menu opening:
void MainWindow::showContextMenu(const QPoint &pos)
{
// Handle global position
QPoint globalPos = listWidget->mapToGlobal(pos);
// Create menu and insert some actions
QMenu myMenu;
myMenu.addAction("Insert", this, SLOT(addItem()));
myMenu.addAction("Erase", this, SLOT(eraseItem()));
// Show context menu at handling position
myMenu.exec(globalPos);
}
After this we need to realize slots for adding and removing QListWidget elements:
void MainWindow::eraseItem()
{
// If multiple selection is on, we need to erase all selected items
for (int i = 0; i < listWidget->selectedItems().size(); ++i) {
// Get curent item on selected row
QListWidgetItem *item = listWidget->takeItem(listWidget->currentRow());
// And remove it
delete item;
}
}
As you can see we iterate all selected items (for set multiple selection mode use setSelectionMode() method) and delete it by ourself, because docs says that
Items removed from a list widget will not be managed by Qt, and will
need to be deleted manually.
Adding some items is easier, my solution with static variable for different item caption looks like:
void MainWindow::addItem()
{
static int i = 0;
listWidget->addItem(QString::number(++i));
}
To simplify your code use Qt5 sytax for signals and slots. It eliminates the need to create intermediate slots.
I hope it helps to you.
It's much simpler than the accepted answer. You don't need to deal with creating a context menu or cursor positions or any of that. Instead of Qt::CustomContextMenu, use Qt::ActionsContextMenu and just add your actions directly to the widget:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
ui->setupUi(this);
// you can create the actions here, or in designer
auto actInsert = new QAction("Insert", this);
auto actDelete = new QAction("Delete", this);
// you can set up slot connections here or in designer
connect(actInsert, SIGNAL(triggered()), this, SLOT(addItem()));
connect(actDelete, SIGNAL(triggered()), this, SLOT(eraseItem()));
// and this will take care of everything else:
listWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
listWidget->addActions({ actInsert, actDelete });
}
void MainWindow::addItem () {
...; // add an item
}
void MainWindow::eraseItem () {
...; // erase an item
}
Everything above except addActions (I think) can also be done in Designer.
Alternatively, if you don't want to add actual slot functions for whatever reason, you can do everything in a lambda on connect, too:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
ui->setupUi(this);
// you can create the actions here, or in designer
auto actInsert = new QAction("Insert", this);
auto actDelete = new QAction("Delete", this);
connect(actInsert, &QAction::triggered, [=]() {
...; // add an item
});
connect(actDelete, &QAction::triggered, [=]() {
...; // erase an item
});
// and this will take care of everything else:
listWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
listWidget->addActions({ actInsert, actDelete });
}
The signal/slot option is more organized and flexible, but the lambda option is good for short highly specialized bits of code (or binding to functions that aren't slots).
This works for context menus on any widget. Also, the same QAction can be used on multiple widgets.

Why QLineEdit with QCompleter for auto completion doesn't shows up?

I have two QLineEdits in the program being lineEdit and fileName_Edit. lineEdit holds path to a directory (taken from user). Then user enters the name of file in the fileName_Edit. I want to show suggestions to user when he is entering the file name in fileName_Edit. I tried to implement QCompleter like this:
(dirContents is a QStringList which holds the contents of the directory specified by user in lineEdit)
void MainWindow::on_lineEdit_textChanged(const QString &arg1)
{
QCompleter *fileEditCompleter = new QCompleter(dirContents, this);
fileEditCompleter->setCaseSensitivity(Qt::CaseInsensitive);
fileEditCompleter->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
ui->fileName_Edit->setCompleter(fileEditCompleter);
}
Program compiles successfully but, the completer doesn't shows up. Even if I try to connect textChanged signal to the function like following, it doesn't shows up.
QObject::connect(&MainWindow::ui->lineEdit, SIGNAL(&textChanged(QString)), this,SLOT(&MainWindow::on_lineEdit_editingFinished()));
EDIT: Adding above line gives an error saying:
Expected constructor, destructor or type-conversion before ( token
Any help will be greatly appreciated.
Try to do it simply first, if the code works, then everything is good and you can start improving it.
In constructor:
QDir dir("G:/2");//path here
QStringList dirContents = dir.entryList(QStringList(), QDir::Files);
qDebug() << dirContents;//make sure that you list isn't empty, or use isEmpty method
QCompleter *fileEditCompleter = new QCompleter(dirContents, this);
fileEditCompleter->setCaseSensitivity(Qt::CaseInsensitive);
fileEditCompleter->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
ui->lineEdit->setCompleter(fileEditCompleter);
If this will work on your computer then you can be sure that your system and project are good, and start improve it (change list etc). And try not to use global variables.
If you want do it dynamically, create a simple model and when you will set new QStringList to it, your completer always will display new data
QDir dir("G:/2");
QStringList dirContents = dir.entryList(QStringList(), QDir::Files);
mdl = new QStringListModel(dirContents,this);//QStringListModel *mdl in header
QCompleter *fileEditCompleter = new QCompleter(mdl, this);
fileEditCompleter->setCaseSensitivity(Qt::CaseInsensitive);
fileEditCompleter->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
ui->lineEdit->setCompleter(fileEditCompleter);
When you want change data when for example, user clicks button or something else, you can do:
QDir dir("G:/2/tmp");
mdl->setStringList(dir.entryList(QStringList(), QDir::Files));
Now your completer has new data.
Converting comment to an answer, as requested...
Try to set completer before providing QLineEdit to user. For example - in constructor of MainWindow. It is not correct to set it in textChanged slot.
MainWindow::MainWindow()
: QWidget(nullptr)
, ui( new ui_MainWindow() )
{
ui->setupUi(this);
//...
QCompleter *fileEditCompleter = new QCompleter(dirContents, this);
fileEditCompleter->setCaseSensitivity(Qt::CaseInsensitive);
fileEditCompleter->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
ui->fileName_Edit->setCompleter(fileEditCompleter);
}
void MainWindow::on_lineEdit_textChanged(const QString &arg1)
{
// Do nothing here
}

How to add a QString to a QListView using a QLineEdit

I want to use a QLineEdit to write a QString, then using a QPushButton, I want to add an item (a string) to a listView
Here is what I got:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
model = new QStringListModel(this);
QStringList list;
list << "Some Item";
model->setStringList(list);
ui->listView->setModel(model);
ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
}
void MainWindow::on_pushButton_3_clicked()
{
//add
int row = model->rowCount(); // model = new QStringListModel
model->insertRow(row);
QModelIndex index = model->index(row);
ui->listView->setCurrentIndex(index);
ui->listView->edit(index); // instead of edit, I'd like to ... add a QString
}
Problem is I don't want to be able to edit (this was all I was manage to do by myself). I want now to instead add an item at CurrentIndex, and that item to be the text field of lineEdit. How do I get access to that field ? is it lineEdit->text() ? and how do I add that to the list view ?
I simply do not know how to add anything to a list. I found edit by mistake, and google has not helped so far. I'm hoping Stack Overflow can, and will.
EDIT: I have decided to try this, but it doesn't seem to be the best solution
void MainWindow::on_pushButton_3_clicked()
{
//add
QStringList list;
list = model->stringList();
list.append(ui->lineEdit->text());
model->setStringList(list);
}
But this seems to be an odd way of doing things. Also it seems to include a newline for some reason.
There is already an example of how to use a QStringListModel here: https://stackoverflow.com/a/5825113/496445
model->insertRow(model->rowCount());
QModelIndex index = model->index(model->rowCount()-1)
model->setData(index, str);
Note that in this suggested approach, you do not need the QStringList, unless you already had one for another reason and want to initialize with it.
When you use a Q*View instead of a Widget, you will be dealing with the model directly for data instead of the view. The view will be notified when the model changes. In this case, you would probably be accessing your lineEdit like this:
QString str = ui->lineEdit->text();
Another way; right-click to listView and select "morph into" -> "QListWidget"
At this time you can see this function "lst->addItem("str");"