-1

I am writing a Qt C++ app to show byte values and its hexdump right next to it. For that I am creating a new dialog, I have 2 QTableViews(each for byte representation or hexdump) and 1 class which inherits from QAbstractTableModel and 1 class which inherits from QStyledItemDelegate.

Now I would like to add functionality, that when I select something in first QTableView, it will also be selected in second QTableView.

In Qt's documentaion on model-view programming they do something similiar using command secondTableView->setSelectionModel(firstTableView->selectionModel()) But that doesn't work for me. I am also highlighting cells using my delegate(now its just in prototype phase) using paint method. Could it be because of this ?

My code :

dialog :

#include "dataviewer.h"
    
    DataViewer::DataViewer(QListWidgetItem* item, QCheckBox* dataHighLighter, QByteArray* headerArray, QWidget *parent)
        : QDialog(parent)
    {
        setupUi(this);
        byteTableViewer = std::make_unique<DataViewerModel>(item, headerArray, false, this);
        hexTableViewer = std::make_unique<DataViewerModel>(item, headerArray, true, this);
        dataDelegate = std::make_unique<DataViewerDelegate>(dataHighLighter, this);
        InitializeTableViewer(Ui::DataViewer::byteTableView, byteTableViewer.get(), false);
        InitializeTableViewer(Ui::DataViewer::hexTableView, hexTableViewer.get(), true);
        connect(byteTableView->verticalScrollBar(), SIGNAL(valueChanged(int)), hexTableView->verticalScrollBar(), SLOT(setValue(int)));
        connect(hexTableView->verticalScrollBar(), SIGNAL(valueChanged(int)), byteTableView->verticalScrollBar(), SLOT(setValue(int)));
    }
    
    void DataViewer::InitializeTableViewer(QTableView* table, DataViewerModel* viewer, bool hexa)
    {
        table->setModel(viewer);
        table->horizontalHeader()->setMinimumSectionSize(5);
        table->verticalHeader()->setMinimumSectionSize(5);
        table->setItemDelegate(dataDelegate.get());
        table->resizeColumnsToContents();
        table->resizeRowsToContents();
        table->setShowGrid(false);
        if (hexa)
            table->verticalHeader()->setVisible(false);
        table->verticalScrollBar()->setFixedSize(QSize(20, table->height()));
        table->horizontalHeader()->setVisible(false);
    }

model :

DataViewerModel::DataViewerModel(QListWidgetItem* item, QByteArray* headerArray, bool hexdump, QWidget* parent) : QAbstractTableModel(parent)
{
    this->item = item;
    this->headerArray.replace(0, headerArray->size() - 1, *headerArray);
    this->hexdump = hexdump;
    BYTES_ON_ROW = 16;
}

int DataViewerModel::rowCount(const QModelIndex& parent) const
{
    QByteArray leftoverData = item->data(TRANSFER_LEFTOVER_DATA).toByteArray();
    int size = leftoverData.size() + headerArray.size();

    return (size / BYTES_ON_ROW == 0) ? size/BYTES_ON_ROW : (size/BYTES_ON_ROW) + 1; //4B on one line
}


int DataViewerModel::columnCount(const QModelIndex& parent) const
{
    return BYTES_ON_ROW; //16B on one line
}

QVariant DataViewerModel::data(const QModelIndex& index, int role) const
{
    QByteArray leftoverData = item->data(TRANSFER_LEFTOVER_DATA).toByteArray();
    int size = leftoverData.size() + headerArray.size() - 2; //-2 for both '\0' in byteArrays
    int ind = (index.row() * BYTES_ON_ROW) + index.column();
    QDataStream stream1(leftoverData);
    QDataStream stream2(headerArray);

    if (role == Qt::TextAlignmentRole)
        return Qt::AlignCenter;


    if (role == Qt::DisplayRole)
    {
        if (!index.isValid())
        {
            return QVariant();
        }
        if (ind > size + 1)
        {
            return QVariant();
        }
        else
        {
            std::stringstream stream;
            if (ind < headerArray.size())
            {
                int hex = headerArray.at(ind);
                hex = (hex < 0) ? hex + 256 : hex;
                if (hexdump)
                {
                    if (hex < 0x20 || hex > 0x7e)
                    {
                        return QVariant(".");
                    }
                    else
                    {
                        return QVariant(QChar(hex));
                    }
                }
                stream << std::uppercase << std::setw(2) << std::setfill('0') << std::hex << hex;
                return QVariant(stream.str().c_str());
            }
            else
            {
                int hex = leftoverData.at(ind - headerArray.size());
                hex = (hex < 0) ? hex + 256 : hex;
                if (hexdump)
                {
                    if (hex < 0x20 || hex > 0x7e)
                    {
                        return QVariant(".");
                    }
                    else
                    {
                        return QVariant(QChar(hex));
                    }
                }
                stream << std::uppercase << std::setw(2) << std::setfill('0') << std::hex << hex;
                return QVariant(stream.str().c_str());
            }
        }
    }
    else
    {
        return QVariant();
    }
}

QVariant DataViewerModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Vertical && role == Qt::DisplayRole)
    {
        std::stringstream stream;
        stream << std::uppercase << std::setw(6) << std::setfill('0') << std::hex << section;
        return QVariant(stream.str().c_str());
    }

    return QVariant();
}

delegate :

#include "dataviewerdelegate.h"

DataViewerDelegate::DataViewerDelegate(QCheckBox* dataHighLighter, QObject *parent)
    : QStyledItemDelegate(parent)
{
    this->dataHighLighter = dataHighLighter;
    headerColor = headerColor.red();
    additionalDataColor = additionalDataColor.green();
    leftoverDataColor = leftoverDataColor.blue();
}

DataViewerDelegate::~DataViewerDelegate()
{
}

void DataViewerDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    if (dataHighLighter->isChecked())
    {
        auto a = (index.row() * 4) + index.column();

        if (a > 5)
        {
            QColor background = QColor(135, 206, 255);
            painter->fillRect(option.rect, background);
        }
        else
        {
            QColor background = QColor(25, 123, 145);
            painter->fillRect(option.rect, background);
        }
    }

    QStyledItemDelegate::paint(painter, option, index);
}

QSize DataViewerDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    return QSize(30, 30);
}

EDIT:

So I've tried adding slot updateSelection to my dialog, and connect it selectionModel of both of my instances of DataViewerModel like this :

I am connecting them in dialogs DataViewer constructor :

connect(byteTableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(updateSelection(QItemSelection, QItemSelection)));
connect(hexTableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(updateSelection(QItemSelection, QItemSelection)));

And my updateSelection looks like this :

void DataViewer::updateSelection(const QItemSelection& selected, const QItemSelection& deselected)
{
    QModelIndexList items = selected.indexes();

    for (const QModelIndex& index : qAsConst(items))
    {
        QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
        byteTableViewer->setData(index, text);
        hexTableViewer->setData(index, text);
    }
}

Of course I've added private slots : void updateSelection(const QItemSelection& selected, const QItemSelection& deselected); in my DataViewer header. Unfortunately, this didn't work either. Are there any other suggestions what can I do ?

Peter
  • 57
  • 1
  • 7
  • For this to work i think both your model need to be in the same order, both line need to be aligned – Paltoquet Nov 23 '20 at 21:50
  • @DraykoonD I am sorry but I think that I dont understand what you are saying. I have only one model, and this model is set to both QTableView. Can you please specify what do you mean by "line" ? – Peter Nov 23 '20 at 23:39
  • I do not see where you set the selection model in your code. So, please prepare a [mcve]. – scopchanov Nov 24 '20 at 04:12
  • ```byteTableViewer = std::make_unique(item, headerArray, false, this); hexTableViewer = std::make_unique(item, headerArray, true, this);``` I see 2 models, I am kind new to model/view but I see selectionmodel just as a list of row selected, if both your model doesn't match wont't work. – Paltoquet Nov 24 '20 at 08:07
  • @scopchanov As I've said, I was trying to do it as in Qts documentation using `secondTableView->setSelectionModel(firstTableView->selectionModel())` but hat didn't work. I was doing this in `DataViewer` constructor. – Peter Nov 24 '20 at 08:08
  • @DraykoonD Well yes, `byteTableViewer` and `hexTableViewer` are 2 instances, but of a same model class `DataViewerModel`. So what you are telling me, is that if i have 2 instances of a same model, each for one view, i cannot connect their `selectionModel` ? – Peter Nov 24 '20 at 08:15
  • They can be both of the same class, but if each instance has its own data in a different order I don't see how qt will link the dot between your different rows. If it's your case I advise you to go for a simpler approach and add a slot for each of your model model ```selectionChanged``` – Paltoquet Nov 24 '20 at 08:25
  • @DraykoonD I've edited my question. Is that what you've meant ? – Peter Nov 24 '20 at 11:25
  • I would have tried to call the select method on the other selectionModel. ```otherTableView->selectionModel()->select(modelIndex);``` I don't know if your selection code works but if there is some issue, i would have moved ```QStyledItemDelegate::paint(painter, option, index);``` at the top of your paint method (first draw the item like usual then add your custom rect). – Paltoquet Nov 24 '20 at 11:53
  • @DraykoonD calling `otherTableView->selectionModel()->select(modelIndex);` from my slot definitely got me somewhere, and it looks like my second table is reacting to selection in first table. But selection in second table looks different, like its only shading background to the same color as main dialog has. It looks like this : https://imgur.com/3nuLxvp – Peter Nov 24 '20 at 12:17

1 Answers1

1

If your 2 instances of models doesn't have the same data it will not work.

I will go with a simpler method and use a custom slot connected to one of your selection model.

connect(byteTableView->selectionModel(), &SelectionModel::selectionChanged, this, &DataViewer::updateSelection);

and in your update Selection:

void DataViewer::updateSelection(const QItemSelection& selected, const QItemSelection& deselected)
{
    QModelIndexList items = selected.indexes();

    auto oppositeSelectionModel = getOppositeSelectionModel();
    for (const QModelIndex& index : qAsConst(items))
    {
        oppositeSelectionModel->select(index);
    }
}

Now for your last problem the color of your cell is linked to the state of checkbox

if (dataHighLighter->isChecked())

I think you want to know if this index belong to the selectionModel, I am sure you can figure something.

Paltoquet
  • 1,184
  • 1
  • 10
  • 18
  • The `updateSelection` definitely did the trick. But that color problem must be in something else. This image : https://imgur.com/3nuLxvp actually shows the state when `dataHighLighter` is turned off. That shady effect is caused when I select something in table on the left(byteTableView). The problem also is, that it stays like that after releasing selection. I've tried updating second table view in `updateSelection` with `hexTableView->update()` but that didnt work. After I click on second table, the shady effect is gone though. – Peter Nov 24 '20 at 15:14
  • I don't think you can avec two widget selected at the same time. What you see is what happens when you change focus. You can play with selectionModel clearSelection to remove thoose columns. My guess is if you want to keep the blue color you will have to have a custom paint event in your delegate which handle the selected columns differently. – Paltoquet Nov 25 '20 at 08:26