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

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
}

Related

Qt push button is calling 2 slots and I only need one

I need to call a 2 functions with different buttons
I have this code:
signalMapperSelections = new QSignalMapper();
QPushButton *selected_type_button = new QPushButton();
selected_type_button->setObjectName("selected_type_button");
selected_type_button->setText(get_selected_type().replace(" ", "\n"));
selected_type_button->setMinimumHeight(80);
selected_type_button->setMinimumWidth(80);
selected_type_button->setMaximumHeight(80);
selected_type_button->setMaximumWidth(80);
selected_type_button->setStyleSheet(style_toolbutton);
ui->verticalLayout_selections->addWidget(selected_type_button);
connect(selected_type_button, SIGNAL(clicked()), signalMapperSelections, SLOT(map()));
signalMapperSelections->setMapping(selected_type_button, get_selected_type());
connect(signalMapperSelections, SIGNAL(mapped(QString)), this, SLOT(show_brands(QString)));
QPushButton *selected_brand_button = new QPushButton();
selected_brand_button->setObjectName("selected_brand_button");
selected_brand_button->setText(get_selected_brand().replace(" ", "\n"));
selected_brand_button->setMinimumHeight(80);
selected_brand_button->setMinimumWidth(80);
selected_brand_button->setMaximumHeight(80);
selected_brand_button->setMaximumWidth(80);
selected_brand_button->setStyleSheet(style_toolbutton);
ui->verticalLayout_selections->addWidget(selected_brand_button);
connect(selected_brand_button, SIGNAL(clicked()), signalMapperSelections, SLOT(map()));
signalMapperSelections->setMapping(selected_brand_button, get_selected_brand());
connect(signalMapperSelections, SIGNAL(mapped(QString)), this, SLOT(show_models(QString)));
When I click "selected_type_button" I only want to run "show_brands". But it's running both functions, "show_brands" and "show_models"...
I tried Qt::UniqueConnection, but it doesn't fix this problem.
I think this is happening because both buttons are using the same signal... But I don't know how to fix it.
How can I fix this?
When I click "selected_type_button" I only want to run "show_brands".
There is no reason to use QSignalMapper in your situation at all. You just have to connect clicked signal from selected_type_button to the show_brands slot, and clicked signal from selected_brand_button to the show_models slot.
QString is the selected type or selected brand....
This QString argument has nothing to do with the clicked signal's source (So, it does not need any mapping using QSignalMapper, read about QSignalMapper in the docs here). The mapping you are currently using is set up at the connection time (not at emit time) , this means that get_selected_type()/get_selected_brand() will return the selected items at the time of calling setMapping (this is obviously not what you meant).
To get the item at the time of clicking the button, you can call your get_selected_type()/get_selected_brand() functions in your slots directly, your code will be something like this:
QPushButton *selected_type_button = new QPushButton();
selected_type_button->setObjectName("selected_type_button");
selected_type_button->setText(get_selected_type().replace(" ", "\n"));
selected_type_button->setMinimumHeight(80);
selected_type_button->setMinimumWidth(80);
selected_type_button->setMaximumHeight(80);
selected_type_button->setMaximumWidth(80);
selected_type_button->setStyleSheet(style_toolbutton);
ui->verticalLayout_selections->addWidget(selected_type_button);
//Qt 5 new connect syntax (replace ClassName with the current class's name)
connect(selected_type_button, &QPushButton::clicked, this, &ClassName::show_brands);
QPushButton *selected_brand_button = new QPushButton();
selected_brand_button->setObjectName("selected_brand_button");
selected_brand_button->setText(get_selected_brand().replace(" ", "\n"));
selected_brand_button->setMinimumHeight(80);
selected_brand_button->setMinimumWidth(80);
selected_brand_button->setMaximumHeight(80);
selected_brand_button->setMaximumWidth(80);
selected_brand_button->setStyleSheet(style_toolbutton);
ui->verticalLayout_selections->addWidget(selected_brand_button);
//replace ClassName with the current class's name)
connect(selected_brand_button, &QPushButton::clicked, this, &ClassName::show_models);
and your show_brands slot should look something like:
//no need for the QString argument
void ClassName::show_brands(){
QString selectedType= get_selected_type();
//show_brands here
}
the same thing for show_models slot:
void ClassName::show_models(){
QString selectedBrand= get_selected_brand();
//show_models here
}

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!

Update and sort Qt ComboBoxes alphabetically

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.

QCheckbox name access

I generate checkboxes as follows:
foreach(QString filt, types){
QCheckBox *checkbox = new QCheckBox(filt, this);
checkbox->setChecked(true);
vbox->addWidget(checkbox);
}
I need to get access to these checkboxes by name but they are all called the same?
I need to read the text they display.
How can I go about this?
Is it possible to run a for loop and attach the value of i onto the end of the checkbox. So in effect, the checkbox would be called checkbox[0], checkbox [1], etc?
EDIT:
I've changed the code to the following:
for(int i=0; i<types.count(); ++i)
{
QString filt = types[i];
*checkboxCount = *checkboxCount + 1;
QCheckBox *typecheckbox[i] = new QCheckBox(filt, this);
typecheckbox[i]->setChecked(true);
vbox->addWidget(typecheckbox[i]);
}
I thought this was a way to dynamically name the checkboxes so I can loop through them to get the text value from them.
I'm getting the error 'variable-sized object may not be initialized' on this line QCheckBox *typecheckbox[i] = new QCheckBox(filt, this);
Any ideas to a solution/ alternate approach?
If you want to access the checkboxes later, you can just use the find children method as follows:
QStringList myStringList;
QList<QCheckBox *> list = vbox->findChildren<QCheckBox *>();
foreach (QCheckBox *checkBox, list) {
if (checkBox->isChecked())
myStringList.append(checkBox->text());
}

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");"