How to refresh TreeModel in Qt? - qt

I have a TreeModel which has some data loaded in it. There is a radio button in my application, which when clicked should update the TreeModel with a new data.
I have tried the following things on switching the radio button and none is working:
emit layoutChanged
emit layoutChanged();
emit dataChanged
emit dataChanged(QModelIndex(), QModelIndex());
Referenced from:
What does 'bottomRight' mean when using dataChanged() with a QTreeView in Qt?
Recursively visiting each node in the tree and emitting dataChanged
void TreeView::getLastExpandedState(const QModelIndex& parent)
{
bool isExpand = isExpanded(parent);
if (!isExpand) {
return;
}
int rows = model()->rowCount(parent);
for (int rowNum = 0; rowNum < rows ; ++rowNum) {
QModelIndex childIndex = model()->index(rowNum, 0, parent);
model->emitChange(parent,childIndex);
getLastExpandedState(childIndex);
}
}
void TreeModel::emitChange(const QModelIndex& parent,const QModelIndex& childIndex) {
emit dataChanged(parent,childIndex);
}
How to solve that?

The answer you reference is at best confusing and probably wrong.
Your model code needs to emit a dataChanged signal for any rectangle of cells where the data returned by the model data method has changed. See, for example, this question and answer, which goes into some detail.
Note that the parameters in the dataChanged signal should not be parent and child - they should be "topLeft" and "bottomRight". They need to have the same parent and also to be different. So you don't need to iterate over the rows and emit the signal for each row. You can send a combined signal for all the rows that have changed.
But you do need to send a signal for each parent (where data has changed) that signals the top left cell and bottom right cell for that parent. So you might end up creating an index for the top left cell for that parent at (0, 0, parent) and another for the bottom right cell at (rows, cols, parent) and then sending the signal for that pair of indices.

Related

QTableView scrolling stopped after dynamic resizing

I have a QTableView with a custom QSortFilterProxyModel for searching and sorting and a QSqlQueryModel for populating the table.
void ProxyModel::searchTable(QString name, QString type, QString date, QString time ){
if(name_ != name)
name_ = name;
if(type_ != type)
type_ = type;
if(date_ != date)
date_ = date;
if(time_ != time)
time_ = time;
invalidateFilter();
}
bool ProxyModel::filterAcceptsRow(int source_row,
const QModelIndex &source_parent) const{
QModelIndex indName = sourceModel()->index(source_row,
0, source_parent);
QModelIndex indType= sourceModel()->index(source_row,
4, source_parent);
QModelIndex indDate = sourceModel()->index(source_row,
2, source_parent);
QModelIndex indTime = sourceModel()->index(source_row,
3, source_parent);
if(
sourceModel()->data(indName).toString().toLower().contains(name_.toLower())
&&sourceModel()->data(indType).toString().toLower().contains(type_.toLower())
&&sourceModel()->data(indDate).toString().toLower().contains(date_.toLower())
&&sourceModel()->data(indTime).toString().toLower().contains(time_.toLower())
)
{
emit adjust();
return true;
}
return false;
}
After a successful search, I emit a signal from my proxy model to a slot where it adjusts the table height to fit the size of the rows.
connect(proxyModel, SIGNAL(adjust()), this, SLOT(dataChanged()));
And when the search button is clicked
connect(ui->searchBtn, &QToolButton::clicked, this, &AllVisitedPlaces::getSearchOptions);
I call the proxy model searchTable method with search parameters
void AllVisitedPlaces::getSearchOptions()
{
proxyModel->searchTable(ui->nameLineEdit->text(),
ui->typeLineEdit->text(),
ui->dateLineEdit->text(),
ui->timeLineEdit->text());
adjustTableSize();
}
void AllVisitedPlaces::dataChanged()
{
adjustTableSize();
this->verticalHeader->setSectionResizeMode(QHeaderView::ResizeToContents);
}
void AllVisitedPlaces::adjustTableSize()
{
QRect rect = ui->table->geometry();
int height = 0;
for (int i =0; i < proxyModel->rowCount() ; i++)
height+= ui->table->rowHeight(i);
rect.setHeight(18 + ui->table->horizontalHeader()->height() + height);
ui->table->setGeometry(rect);
verticalHeader->setSectionResizeMode(QHeaderView::Stretch);
}
The problem is, when the table re-sizes , I lose scrolling.
How can I fix that ?
Before re-sizing :
After re-sizing :
Why would you expect scrolling when you've resized the table to fit its contents?
The likely problem is that your Ui is broken in other ways and the table is obscured: it has been resized, but you can't see that because whatever widget the table view sits in doesn't manage the table widget properly. But we can't tell for sure because you didn't minimize your code to provide a complete compileable example of what you're doing. We're talking about <100 lines of code in all - surely it wouldn't be a big deal to just paste such a main.cpp in your question. See e.g. example 1 or example 2.
This is a bad design anyway since you're presuming that the table will fit on screen. Yet it won't: once the resizing works, you'll end up with a vertically huge window that cannot be used as some of its corners will extend past the screen, with no way to reach them to see the contents or to resize the window.
Finally, once you properly use layouts in your Ui design, the setGeometry() call on any widget below top-level is a no-op: it's the layout that controls the child widget geometry. The solution then is not to set the widget's geometry, but to set its minimum size instead.
You're facing an XY Problem: you're dead set on a solution, without telling us what it is that you're trying to achieve and making sure first that what you're after makes sense (as in: that it will actually lead to a usable Ui!).

How to update a TableView with progress data for multiple ProgressBars?

I've started to extend the qGet DownloadManager to emit the progress of a TransferItem, so that i can connect to it. I'm inserting the progress data into a cell of a TableView model for display with an Delegate, finally the delegate paints the progress bar. That works in theory, but i'm running into the following
Problem: when there are multiple downloads in parallel, then i get progress updates from both signals into both cells!
Both progress bars show progress data, but the signal is kind of mixed and not unique to the current index (QModelIndex index / index.row()).
(Please ignore the small transitioning problem between UserRoles (after clicking the download button "ActionCell" is displayed and then "Install", before the "ProgressBar" shows up.). That is not the main problem here. My question is about the index problem.) The text "112" and "113" is the int index.row.
Questions:
How to update a TableView with progress data for multiple ProgressBars?
What must i change to render a progress bar for each download?
Source
Emit progress of a download
I've added the following things to re-emit the signal through the classes, until it bubbles up to the top, where it becomes connectable from the GUI.
a connection from QNetworkReply - downloadProgress(qint64,qint64) to TransferItem - updateDownloadProgress(qint64,qint64)
void TransferItem::startRequest()
{
reply = nam.get(request);
connect(reply, SIGNAL(readyRead()), this, SLOT(readyRead()));
connect(reply, SIGNAL(downloadProgress(qint64,qint64)),
this, SLOT(updateDownloadProgress(qint64,qint64)));
connect(reply, SIGNAL(finished()), this, SLOT(finished()));
timer.start();
}
the SLOT function TransferItem - updateDownloadProgress(qint64,qint64) as receiver calculates the progress and stores it in progress (QMap<QString, QVariant>).
After the calculation the downloadProgress(this) signal is emitted.
// SLOT
void TransferItem::updateDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
progress["bytesReceived"] = QString::number(bytesReceived);
progress["bytesTotal"] = QString::number(bytesTotal);
progress["size"] = getSizeHumanReadable(outputFile->size());
progress["speed"] = QString::number((double)outputFile->size()/timer.elapsed(),'f',0).append(" KB/s");
progress["time"] = QString::number((double)timer.elapsed()/1000,'f',2).append("s");
progress["percentage"] = (bytesTotal > 0) ? QString::number(bytesReceived*100/bytesTotal).append("%") : "0 %";
emit downloadProgress(this);
}
QString TransferItem::getSizeHumanReadable(qint64 bytes)
{
float num = bytes; QStringList list;
list << "KB" << "MB" << "GB" << "TB";
QStringListIterator i(list); QString unit("bytes");
while(num >= 1024.0 && i.hasNext()) {
unit = i.next(); num /= 1024.0;
}
return QString::fromLatin1("%1 %2").arg(num, 3, 'f', 1).arg(unit);
}
When a new download is enqueued, i'm connecting the emitted downloadProgress(this) to the Slot DownloadManager - downloadProgress(TransferItem*). (dl is DownloadItem which extends TransferItem).
void DownloadManager::get(const QNetworkRequest &request)
{
DownloadItem *dl = new DownloadItem(request, nam);
transfers.append(dl);
FilesToDownloadCounter = transfers.count();
connect(dl, SIGNAL(downloadProgress(TransferItem*)),
SLOT(downloadProgress(TransferItem*)));
connect(dl, SIGNAL(downloadFinished(TransferItem*)),
SLOT(downloadFinished(TransferItem*)));
}
Finally, i'm re-emitting the download progress one more time:
void DownloadManager::downloadProgress(TransferItem *item)
{
emit signalProgress(item->progress);
}
Now the TableView with Delegate, doDownload(index) and ProgressBarUpdater
QTableView
with added QSortFilterProxyModel (for case-insensitivity)
with added ColumnDelegate, which renders DownloadButton and ProgressBar based on custom UserRoles. The delegate handles the button click: the SIGNAL downloadButtonClicked(index) is emited from the editorEvent(event, model, option, index) method.
actionDelegate = new Updater::ActionColumnItemDelegate;
ui->tableView->setItemDelegateForColumn(Columns::Action, actionDelegate);
connect(actionDelegate, SIGNAL(downloadButtonClicked(QModelIndex)), this, SLOT(doDownload(QModelIndex)));
The doDownload method receives the index and fetches the download URL from the model. Then the URL is added to the DownloadManager
and i'm setting up a ProgressBarUpdater object to set the progress data to the model at the given index. Finally i'm, connecting downloadManager::signalProgress to progressBar::updateProgress and invoke the downloadManager::checkForAllDone to start the download processing.
void UpdaterDialog::doDownload(const QModelIndex &index)
{
QUrl downloadURL = getDownloadUrl(index);
if (!validateURL(downloadURL)) return;
QNetworkRequest request(downloadURL);
downloadManager.get(request); // QueueMode is Parallel by default
ProgressBarUpdater *progressBar = new ProgressBarUpdater(this, index.row());
progressBar->setObjectName("ProgressBar_in_Row_" + QString::number(index.row()) );
connect(&downloadManager, SIGNAL(signalProgress(QMap<QString, QVariant>)),
progressBar, SLOT(updateProgress(QMap<QString, QVariant>)));
QMetaObject::invokeMethod(&downloadManager, "checkForAllDone", Qt::QueuedConnection);
}
The model update part: the ProgressBarUpdater takes the index and the progress and should update the model at the given index.
ProgressBarUpdater::ProgressBarUpdater(UpdaterDialog *parent, int currentIndexRow) :
QObject(parent), currentIndexRow(currentIndexRow)
{
model = parent->ui->tableView_1->model();
}
void ProgressBarUpdater::updateProgress(QMap<QString, QVariant> progress)
{
QModelIndex actionIndex = model->index(currentIndexRow, UpdaterDialog::Columns::Action);
// set progress to model
model->setData(actionIndex, progress, ActionColumnItemDelegate::DownloadProgressBarRole);
model->dataChanged(actionIndex, actionIndex);
}
The rendering part: i'm rendering the fake ProgressBar from the delegate; fetching the progress data with index.model()->data(index, DownloadProgressBarRole).
void ActionColumnItemDelegate::drawDownloadProgressBar(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionProgressBarV2 opt;
opt.initFrom(bar);
opt.rect = option.rect;
opt.rect.adjust(3,3,-3,-3);
opt.textVisible = true;
opt.textAlignment = Qt::AlignCenter;
opt.state = QStyle::State_Enabled | QStyle::State_Active;
// get progress from model
QMap<QString, QVariant> progress =
index.model()->data(index, DownloadProgressBarRole).toMap();
QString text = QString::fromLatin1(" %1 %2 %3 %4 %5 ")
.arg(QString::number(index.row()))
.arg(progress["percentage"].toString())
.arg(progress["size"].toString())
.arg(progress["speed"].toString())
.arg(progress["time"].toString());
opt.minimum = 0;
opt.maximum = progress["bytesTotal"].toFloat();
opt.progress = progress["bytesReceived"].toFloat();
opt.text = text;
bar->style()->drawControl(QStyle::CE_ProgressBar,&opt,painter,bar);
}
I've added QString::number(index.row() to the progress bar text, so that each ProgressBar gets its row number rendered. In other words: the rendering is unique to the row, but the incoming progress data is somehow mixed.
I'm stuck on the index problem for a while now. Thank you in advance for your help.
Update: The issue is resolved!
Thank you very much ddriver!! I followed your suggestions and fixed it:
The DownloadManager tracks the progress for all transfers, and you keep each transfer item's data in the respective TransferItem.
The logical thing IMO would be to have a connection from each TransferItem to the corresponding ProgressBarUpdater, and emit from the transfer item.
However, in your case, you are reporting progress not from each individual transfer item, but from the download manager. So each time you are emitting a progress, you are emitting the progress for a particular transfer item to all progress bars.
connect(&downloadManager, SIGNAL(signalProgress(QMap<QString, QVariant>)),
progressBar, SLOT(updateProgress(QMap<QString, QVariant>)));
So instead of a
TransferItem --progress--> CorrespondingUI
you have a:
TransferItem --transferItem--> DownloadManager --progress--> AllUIs
This leads to having one single and varying progress for all progress bars, which corresponds to the last download that happen to report progress before the UI updated. Which is why you get no more variation after the first download is completed, as the manager only updates the progress for the second.
Finally, i'm re-emitting the download progress one more time:
void DownloadManager::downloadProgress(TransferItem *item)
{
emit signalProgress(item->progress);
}
And who exactly needs an anonymous progress, containing no information whatsoever to which transfer it applies? Aside from the bug of course.
Would you be so nice to explain, how to simplify it?
I was at the end of my mental rope yesterday when I commented, on a clear head it doesn't look all that overdone, but still I'd probably go for something more streamlined, involving 3 key components only:
DownloadsManager -> DownloadController -> UI
-> DownloadController -> UI
It just seems redundant to have a DownloadItem and then also a TransferItem, considering that a download is a transfer.
The model and view are totally unnecessary as well, as is storing the progress in the model rather than just having it as a member of the progress bar. You could go with just a regular widget for each download, and place them in a vertical layout.
Update:
The excessive, unnecessary compartmentalization has led to a level of fragmentation which makes it hard to get to the data, needed to make it work once you put everything together. The main issue is you have no way to tie a transfer item to the right progress bar updater, and since you still haven't posted all of the relevant code, the simplest possible solution I can offer involves the following minor changes:
// in DownloadManager
void signalProgress(QMap<QString, QVariant>); // this signal is unnecessary, remove
void DownloadManager::downloadProgress(TransferItem *item) // change this
{
registry[item->request.url()]->updateProgress(item->progress);
}
QMap<QUrl, ProgressBarUpdater *> registry; // add this
// in UpdaterDialog
void UpdaterDialog::doDownload(const QModelIndex &index)
{
QUrl downloadURL = getDownloadUrl(index);
if (!validateURL(downloadURL)) return;
QNetworkRequest request(downloadURL);
downloadManager.get(request); // QueueMode is Parallel by default
ProgressBarUpdater *progressBar = new ProgressBarUpdater(this, index.row());
progressBar->setObjectName("ProgressBar_in_Row_" + QString::number(index.row()) );
// remove the connection - source of the bug, instead register the updater
downloadManager.registry[downloadURL] = progressBar;
QMetaObject::invokeMethod(&downloadManager, "checkForAllDone", Qt::QueuedConnection);
}
That's pretty much it, the progress updater is associated with the URL, and in DownloadManager::downloadProgress instead of emitting the progress to all progress updaters, you simply lookup the one which actually corresponds to the particular download, and only update its progress. It is somewhat clumsy, but as I said, if your design is proper, it would not be needed and you wouldn't have the problem in the first place.
There are other solutions as well:
change the DownloadManager's signal to void signalProgress(TransferItem *), and the body of downloadProgress toemit signalProgress(item); , change to void ProgressBarUpdater::updateProgress(TransferItem *), and in the body compare the transfer item's request's url to the one in the model at the currentIndexRow, and only model-setData() if it is the same. This solution is not very efficient, since it will emit to all progress updaters just to modify one.
cut out the middleman, what I've been suggesting from the start, make DownloadManager ::get() return a pointer to the DownloadItem/TransferItem created in its body, then in UpdaterDialog::doDownload() you can connect the transfer item directly to the appropriate progress updater, so you will no longer need DownloadManager::downloadProgress() and the signalProgress signal, you only need to change the signature of the signal in TransferItem to void downloadProgress(QMap<QString, QVariant>); and emit the progress rather than the item. This is actually the most efficient solution, as it involves nothing extra, jsut the removal of unnecessary stuff.

How to set QTableWidget to only allow selection by headers?

I'm trying to disable users from selecting individual cells in the table widget and I only want to be able to select column and row headers, with their own separate selection behavior. Here's what I tried:
ui->tableWidget->setSelectionMode(QAbstractItemView::NoSelection);
ui->tableWidget->horizontalHeader()->setSelectionMode(QAbstractItemView::SingleSelection);
ui->tableWidget->verticalHeader()->setSelectionMode(QAbstractItemView::MultiSelection);
But it's not allowing me to select anything, and I can't find a method to set the selection behavior for only cells. Anyone?
EDIT:
I tried connecting to the sectionClicked signal of the table widgets vertical and horizontal headers, and those seem to be emitting even with the table widget's selection set to none, but they don't remain highlighted.
setSelectionMode as default is NoSelection to ignore all the selection on widget. Then connect as below code to trigger hhSelected and vhSelected slots. In those slots you just set the corresponding selectionMode and SelectionBehavior.
SO_Qt::SO_Qt(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
ui.setupUi(this);
ui.tableWidget->setSelectionMode(QAbstractItemView::NoSelection);
QHeaderView* hh = ui.tableWidget->horizontalHeader();
bool success = connect(hh, SIGNAL(sectionClicked( int )), this, SLOT(hhSelected(int)));
QHeaderView* vh = ui.tableWidget->verticalHeader();
success = connect(vh, SIGNAL(sectionClicked( int )), this, SLOT(vhSelected(int)));
}
void SO_Qt::hhSelected( int index )
{
ui.tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
ui.tableWidget->setSelectionBehavior(QAbstractItemView::SelectColumns);
ui.tableWidget->selectColumn(index);
}
void SO_Qt::vhSelected( int index )
{
ui.tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
ui.tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
ui.tableWidget->selectRow(index);
}

How to make QPushButtons to add text into QLineEdit box?

I used Qt Creator to make a "keyboard" window with sixty QPushButtons and one QLineEdit. How can I make the buttons to add characters into QLineEdit text box? If I press a QPushButton with the label 'Q' on it, I want the program to add the Unicode character 'Q' on the text box.
One way to do this would be to just connect the 'clicked' signal from all the buttons to a slot, and then handle the adding of the character there.
For example, if the all keyboard buttons are inside a layout called 'buttonLayout', in your MainWindow constructor you can do this:
for (int i = 0; i < ui->buttonLayout->count(); ++i)
{
QWidget* widget = ui->buttonLayout->itemAt( i )->widget();
QPushButton* button = qobject_cast<QPushButton*>( widget );
if ( button )
{
connect( button, SIGNAL(clicked()), this, SLOT(keyboardButtonPressed()) );
}
}
Then in the slot implementation, you can use QObject::sender(), which returns the object that sent the signal:
void MainWindow::keyboardButtonPressed()
{
QPushButton* button = qobject_cast<QPushButton*>( sender() );
if ( button )
{
ui->lineEdit->insert( button->text() );
}
}
OPTION 1 - Multiple signals and slots
Connect all pushbuttons clicked() signal to a slot
// Let button01 be the A
connect(ui->button01, SIGNAL(clicked()), this, SLOT(buttonClicked()));
...
// Let button 60 be the Q
connect(ui->button60, SIGNAL(clicked()), this, SLOT(buttonClicked()));
In the buttonClicked() slot you have to figure out which button was clicked and append the corresponding letter to the line edit.
void buttonClicked()
{
QObject* callingButton = QObject::sender();
if (callingButton == button01)
ui->lineEdit->setText(ui->lineEdit->text()+ "A");
...
else if (callingButton == button60)
ui->lineEdit->setText(ui->lineEdit->text()+ "Q");
}
OPTION 2 - Subclass QPushButton
You could subclass QPushButton in order to avoid the multiple ifs. In your subclass just catch the mouse release event and emit a new signal which will contain the button's text
void KeyboardButton::mouseReleaseEvent(QMouseEvent* event)
{
emit clicked(buttonLetter); // Where button letter a variable of every item of your subclass
}
Similarly connect the clicked(QString) signal with a slot
connect(ui->button01, SIGNAL(clicked(QString)), this, SLOT(buttonClicked(QString)));
...
connect(ui->button60, SIGNAL(clicked(QString)), this, SLOT(buttonClicked(QString)));
void buttonClicked(QString t)
{
ui->lineEdit->setText(ui->lineEdit->text()+ t);
}
I have created an application with a similar issue, trying to convert the qpushbutton text to the qlineedit itself. The key is how you initialize the buttons and to use polymorphism in your function. To create an emit signal wont work for individual characters. The .digitValue will work if the case if for numerics (which the buttons would be of type int), but qt doesnt have a character value (I should say after 6hrs of reading qt doc and another 4 of trying different combinations it would not work), I think it has to do with how many bits it takes to store each variable type in an array. I even tried converting the button->text to QString to use with the emit function as a signal prototyped.
I do not know what your button layout is, but I will give you a synopsis of what I did. I first created a global, static const char array containing all the letters needed e.g.
static const char vowelarray[] = "AEIOU";
Then initialized the QPushButtons with in the MainWindow function, using iteration, setting a for loop's terminating condition equal to the size char array (in your case 60?). This all depends on your button layout though. I personally created a void function (setLocation) for the button->setGeometry of each button and iterated the setGeometry, and then passed the function to the MainWindow Function, at end of fucntion. The following code was used to initialize the buttons, connect signals to slots, and use polymorphism to connect to lineedit.
for (int i = 0; i < 26; i++){
characterButton[i] = new QPushButton(chararry[i], this); `
characterButton[i] -> setStyleSheet("QPushButton{background: grey; color: brown}");
connect(characterButton[i],SIGNAL(released(),this,SLOT(characterPushed()));
}
setLocation();
Then created a void function (e.g. void MainWindow::characterPuched()) where the following code was used:
void MainWindow::characterPushed(){
QPushButton *characterButton = (QPushButton*) sender();
if (characterButton )
{
lineEdit -> setText(letters.insert(letters.size(), characterButton -> text()));
}
lineEdit -> setText(letters);
}
of course letters was a global variable as well as:
QString letters = "";
and of course the QPushButtons and the function were prototype in the header file as a private variables and slots, e.g.
private:
QPushButton *characterButton[26];
the variable 'letters' was used to extract and input text to and from the line edit for further functions throughout the application.
Best Luck!!``

Why doesn't QTableView row count update?

I created a QAbstractTableModel called PresetTableModel, and connected it to a QTableView. I implemented the rowCount, columnCount, and data functions. Everything works if I have rowCount return a fixed number, but as soon as I get it to return a variable value, the list view doesn't show any rows. The debug statement in the code below shows the size value starting at 0, but changing to 9 once the list gets populated.
int PresetTableModel::rowCount(const QModelIndex & /*parent*/) const
{
qDebug() << preset_list.count();
return preset_list.size();
}
Is there something else I need to do to force it to update the number of rows?
When modifying the underlying data, you must use the model's notification mechanism to notify the views. E.g, when appending data:
beginInsertRows(QModelIndex(), preset_list.size(), preset_list.size()+1); //notify that two rows will be appended (rows size() and size() + 1)
preset_list.append(something);
preset_list.append(somethingelse);
endInsertRows(); //notify views that you're done with modifying the underlying data
Accordingly, you have to call beginRemoveRows() and endRemoveRows() when removing rows, and emit dataChanged() when existing entries are updated.
On a side note, your rowCount() method should read
if (!parent.isValid())
return preset_list.size(); //top-level: return list size
else
return 0; //list item: no further children (flat list)
to limit the depth. Otherwise each item in the list has again preset_list.size() entries.
i use:
void refresh() {
emit dataChanged(index(0, 0),
index(rowCount(), columnCount())); // update whole view
emit layoutChanged();
}

Resources