Getting variable from widget in a QListWidget - c++

I have a custom QWidget class called VideoWidget. Its source file looks something like this:
VideoWidget::VideoWidget(QWidget *parent, string test) :
QWidget(parent)
{
pathname=test;
QLabel *label= new QLabel(pathname.c_str(), this);
//...
}
string VideoWidget::getFilePath(){
return pathname;
}
In my MainWindow class I add the VideoWidget to a QListWidget through looping through a xml file and getting the string argument from that file like this:
QDomNode node = rootXML.firstChild();
while( !node.isNull() )
{
if( node.isElement() )
{
QDomElement element = node.toElement();
VideoWidget* mytest = new VideoWidget(this, element.attribute( "Pathname", "not set").toStdString());
QListWidgetItem* item = new QListWidgetItem;
item->setSizeHint(QSize(150,100));
ui->myList->addItem(item);
ui->myList->setItemWidget(item,mytest);
}
node = node.nextSibling();
}
This correctly fills my QListWidget with the VideoWidget where all the labels have a different value.
Now I'd like to get the pathname variable everytime I doubleclick on a item in the QListWidget like this:
connect(ui->myList,SIGNAL(doubleClicked(QModelIndex)),this,SLOT(playClip(QModelIndex)));
void MainWindow::playClip(QModelIndex index){
QListWidgetItem* item = ui->myList->itemAt(0,index.row());
VideoWidget* widget = dynamic_cast<VideoWidget*>(ui->myList->itemWidget(item));
cout << widget->getFilePath() << endl;
}
My problem is that widget->getFilePath() always returns the same value for every clicked widget. It is the value of the first time I set pathname=test;. What am I missing here?

This is probably mistake:
QListWidgetItem* item = ui->myList->itemAt(0,index.row());
Method "itemAt" takes x and y coordinates, not indexes. Use "takeItem" instead.
Next thing I want to say is that this part:
ui->myList->itemWidget(item)
is useless. You can convert "item" directly.
And last - use qobject_cast since you use Qt. And never use dynamic_case (especially when you anyway do not check result against NULL).

Related

QListWidget deleting QListWidgetItem causes segmentaion fault

I have a C++ application where I am reading pcap file format and processing usb packets. For each packet, I would like to create a QListWidgetItem to which I am storing some data and then adding it to the QListWidget. Here is where the trouble begins. According to QListWidget documentation, after inserting item :
The list widget will take ownership of the item.
So what I thought is that its on QListWidget to delete all QListWidgetItems. The items are added fine, but when I close my app(I suppose thats when desctructor od QListWidget is called so he is calling desctructor of each QListWidgetItem) I get delete_scalar exception. According to call stack, deletion of some QListWidgetItem triggers it :
Snippet of my code (this function is being called for each packet in pcap file and is responsible for creating and adding items):
void ItemManager::ProcessPacket(QByteArray packetData)
{
const unsigned char* packet = (unsigned char*)packetData.data();
PUSBPCAP_BUFFER_PACKET_HEADER usbh = (PUSBPCAP_BUFFER_PACKET_HEADER)packet;
QListWidgetItem* item = new QListWidgetItem;
//set USBPCAP header to item
QByteArray usbhArray((const char*)packet, sizeof(USBPCAP_BUFFER_PACKET_HEADER));
item->setData(dataHolder->USBPCAP_HEADER_DATA, QVariant(usbhArray));
packet += sizeof(USBPCAP_BUFFER_PACKET_HEADER);
if (usbh->transfer == USBPCAP_TRANSFER_ISOCHRONOUS || usbh->transfer == USBPCAP_TRANSFER_CONTROL) //check for optional header data
{
int additionalDataSize = usbh->headerLen - sizeof(USBPCAP_BUFFER_PACKET_HEADER);
if (additionalDataSize > 0)
{
//set additional header data to item
QByteArray additionalDataArray((const char*)(packet), additionalDataSize);
item->setData(dataHolder->TRANSFER_OPTIONAL_HEADER, QVariant(additionalDataArray));
packet += additionalDataSize;
}
else
{
item->setData(dataHolder->TRANSFER_OPTIONAL_HEADER, QVariant()); //QVariant creates invalid QVariant, later i just need to check with QVariant::isValid()
}
}
else
{
item->setData(dataHolder->TRANSFER_OPTIONAL_HEADER, QVariant());
}
//set leftover data to item
QByteArray leftoverDataArray((const char*)packet, usbh->dataLength);
item->setData(dataHolder->TRANSFER_LEFTOVER_DATA, QVariant(leftoverDataArray));
listWidget->insertItem(listWidget->count(), item);
}
Calling of ProcessPacket function :
void ItemManager::ProcessFile(QString filename, bool liveReading)
{
if (fileReader.OpenNewFile(filename))
{
if (fileReader.ReadFileHeader())
{
while (!stopButtonClicked)
{
while (!fileReader.EndOfFile())
{
QByteArray packetData = fileReader.GetPacket();
if (!pauseButtonClicked)
{
ProcessPacket(packetData);
}
}
parent->Refresh(); //this is just calling QCoreApplication::processEvents();
if (!atBottomOfList)
{
listWidget->scrollToBottom();
}
if (liveReading)
{
Sleep(50);
}
else
{
return;
}
}
}
}
}
EDIT
I found out that this problem is happening only when appending to QListWidget through ItemManager class. In my main Q_OBJECT class USB_Packet_Analyzer(which holds QlistWidget that I am appending to) i have slot on_OpenButton_clicked() which looks like this :
void USB_Packet_Analyzer::on_OpenButton_clicked()
{
QString fil = QFileDialog::getOpenFileName(this, "Select source file", ".", "Pcap files (*.pcap)");
ItemManager manager(ui.listWidget,this);
manager.ProcessFile(fil, ui.liveCaptureRadioButton->isChecked());
}
where in ItemManager class constructor looks like this :
ItemManager::ItemManager(QListWidget* listWidget, USB_Packet_Analyzer* parent)
{
this->stopButtonClicked = false;
this->pauseButtonClicked = false;
this->atBottomOfList = false;
this->listWidget = listWidget;
this->parent = parent;
this->dataHolder = DataHolder::GetDataHolder();
}
Now, if I add items in on_OpenButton_clicked() slot and close the app, everything is fine. But when I create ItemManager instance and append items in that class, the error occurs. Could it be that I am not allowed to pass QListWidget* as parameter ?
You have to create a new item inside your loop. You must not reuse the pointer because no content is copied, only the pointer is stored within the QListWidget
To this day I am not completely sure what caused the error, but it was fixed after I modified USBPCAP structs which I were using. They use all kind of stuff like #pragma pack(1) , #pragma pack(push) and so on. It looks like I was missing some of those, which might result in some undefined behaviour while using them.

QT5 C++, Is there a way I can get the current text of a widget within a qlist container

I need to retrieve the text in the order in which they appear on the user form. I am trying as below:
QString line = "QLineEdit";
QString combo = "QComboBox";
QList<QWidget *> childWidgets = ui->frame_3->findChildren<QWidget *>();
QStringList data;
for(auto widget : childWidgets){
if(widget->metaObject()->className() == line || widget->metaObject()->className() == combo){
data.append(widget->text()); //append the text of the lineEdits and ComboBoxes to data
}
}
I get the following compile error from the above code:
"no member named 'text' in QWidget
Since you pointed out the QWidget base class does not have a text member function you will need to access the QComboBox and QLineEdit directly to get the current text.
QList<QWidget *> childWidgets = ui->frame_3->findChildren<QWidget *>();
QStringList data;
for(auto widget : childWidgets){
auto combo = dynamic_cast<QComboBox*>(widget);
if (combo) {
data << combo->currentText(); // currentText() returns the text from the combobox
}
else {
auto lineEdit = dynamic_cast<QLineEdit*>(widget);
if (lineEdit) {
data << lineEdit->text(); // A line edit has a text() member.
}
}
}
This code does not handle ordering. I believe the order is in the same order as added to the parent.

Qt5 : Get value of item clicked in a listview

I'm making a Qt5.7 application where I am populating a QListView after reading stuff from a file. Here's the exact code of it.
QStringListModel *model;
model = new QStringListModel(this);
model->setStringList(stringList); //stringList has a list of strings
ui->listView->setModel(model);
ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers); //To disable editing
Now this displays the list just fine in a QListView that I have set up. What I need to do now is to get the string that has been double clicked and use that value elsewhere. How do I achieve that?
What I tried doing was to attach a listener to the QListView this way
... // the rest of the code
connect(ui->listView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(fetch()));
...
And then I have the function fetch
void Window::fetch () {
qDebug() << "Something was clicked!";
QObject *s = sender();
qDebug() << s->objectName();
}
However the objectName() function returns "listView" and not the listView item or the index.
The signal already provides you with a QModelIndex which was clicked.
So you should change your slot to this:
void Window::fetch (QModelIndex index)
{
....
QModelIndex has now a column and a row property. Because a list has no columns you are interessted in the row. This is the index of the item clicked.
//get model and cast to QStringListModel
QStringListModel* listModel= qobject_cast<QStringListModel*>(ui->listView->model());
//get value at row()
QString value = listModel->stringList().at(index.row());
You should add the index as parameter of your slot. You can use that index to access the list
Your code should be some thing like this.
void Window::fetch (QModelIndex index) {
/* Do some thing you want to do*/
}

QT - Can I dynamically point to GUI's labels using a variable passed into a method?

I'm creating an inventory and want to reference my inventory Forms QLabels with the name of the item passed into my method.
The QLabels and the names of the items are the same so I wanted to reduce if statements by referencing a QLabel depending on the name of the item passed in.
void Zork::on_btnTake_clicked()
{
Item *item = new Item(ui->takeItem->currentText().toStdString());
Colin->addItemToInventory(item);
inventory_screen->addItem(item->getDescription()); //Enables the Item in the Inventory
currentRoom->deleteItem(item);
ui->takeItem->removeItem(ui->takeItem->currentIndex()); //Only remove the item in the foreground of combobox at the time take button was clicked
}
Calls this Method addItemToInventory(item):
void Inventory_Ui:: addItem(string itemName){
myUi->itemName->setText(QString::fromStdString(itemName));
}
I am unable to do so as itemName is not a member of Ui, although itemName does contain members of Ui.
Example:
In my Inventory_Ui Form I have 6 labels, one named broken_bottle. I want to pass broken_bottle into the method's parameter itemName and them use that as a reference so at run-time it would look like myUi->broken_bottle->setText...
I think I follow what you are asking to do. I would access it with findChild or findChildren.
http://doc.qt.io/qt-5/qobject.html#findChild
http://doc.qt.io/qt-5/qobject.html#findChildren
QList <QLabel *> labels = ui->myForm()->findChildren<QLabel*>();
QLabel* targetLabel = 0;
foreach(QLabel * label, labels)
{
if(label->text() == ui->takeItem->currentText())
{
targetLabel = label;
break;
}
}
if(targetLabel != 0)
{
// ... do something with targetLabel
}
or...
QList <QLabel *> labels = ui->myForm()->findChildren<QLabel*>();
foreach(QLabel * label, labels)
label->setObjectName(label->text());
// later
QLabel * targetLabel = ui->myForm()->findChild<QLabel*>("text of label");
if(targetLabel)
// use it
Or if you are connecting all these labels to the same slot, you can figure some of it out using the sender static method.
http://doc.qt.io/qt-5/qobject.html#sender
void MainWindow::on_takeItem_currentTextChanged()
{
QLabel* targetLabel = qobject_cast<QLabel*>QObject::sender();
if(targetLabel)
// do something with it
}
Hope that helps.

Removing item from QListWidget from inside a Widget

I have a QListWidget in my MainWindow that displays a list of VideoWidgets (a custom QWidget).
VideoWidget has a clickable label where on clicking the label it should delete a file and then remove the QListItem which holds the VideoWidget from the QListWidget. Here is my VideoWidget class:
VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent)
{
ClickableLabel *smallRed = new ClickableLabel(this)
//...
QObject::connect(smallRed,SIGNAL(clicked()),this,SLOT(removeVideo()));
}
void VideoWidget::removeVideo(){
//...code to remove a file
QListWidget* list = myParent->getList();
QListWidgetItem* item = list->takeItem(list->currentIndex().row());
myList->removeItemWidget(item);
}
The problem is that clicking the smallRed label will not select its item in the QListWidget which means that list->currentIndex().row() will return -1. Clicking anywhere else in the Widget does select the current item. For the code to work I currently have to first click anywhere in the VideoWidget and then click its ClickableLabel. Is there any way I can achieve the same effect with one single click on my ClickableLabel?
From your previous qestion, we suggested use signal and slots. For example:
for(int r=0;r<3;r++)
{
QListWidgetItem* lwi = new QListWidgetItem;
ui->listWidget->addItem(lwi);
QCheckBox *check = new QCheckBox(QString("checkBox%1").arg(r));
check->setObjectName("filepath");
connect(check,SIGNAL(clicked()),this,SLOT(echo()));
ui->listWidget->setItemWidget(lwi,check);
}
Slot:
void MainWindow::echo()
{
qDebug() << sender()->objectName() << "should be remmoved";
}
It is not unique way to solve this problem, but it shows all main things, with signals and slots mechanism, objectName and sender() you can achieve all what you need.
sender() return object which send signal, you can cast it, but if you need only objectName you should not cast.