[Solved] - QTableView - custom selection indicator (QRubberBand)



  • I have the following QTableView:
    https://www.dropbox.com/s/7rpylmlhgvr62ut/repeatDraw.png

    I would like to implement a way to represent a "repeat" section like in the image above.
    Right now I need to copy the row many times if I want a "repeat" section, what I would like is something like :
    -select a few row
    -click a repeat button and enter a number
    -something in the QtableView representing the repeat is displayed (color, line or whatever)

    Is there something existing in QTableView that I could use to do that?
    I thought of using a QTreeView but it's way overkill for my needs.

    Thanks!


  • Lifetime Qt Champion

    Hi,

    AFAIK no there's not. You could use e.g. a QRubberBand for the additional "selection" effect

    Hope it helps



  • Thanks SGaist, I would have never found this usefull class myself!
    With this I think I can achieve what I want!

    So far I got a working example drawing a QRubberBand on every row of my QtableView when I click a "repeat button". It felt a bit hacky to get the QTableView total columns width, maybe i'm missing a function in QTableView? I'm thinking using this code instead of a complex delegate I did to get mouse hover row effect in a QtableView, you guys could implements this easily with a QRubberband :) and add a QTableView row mouseOver effect with stylesheet?

    I will create a custom QRubberBand with a QLabel at the middle right position, and write the number I want there.

    Here is the code:

    @WorkoutCreator::WorkoutCreator(QWidget *parent) : QWidget(parent), ui(new Ui::WorkoutCreator)
    {
    ui->setupUi(this);

    rubberBand = new QRubberBand(QRubberBand::Rectangle, ui->tableView->viewport());
    totalWidthColumms = 0;
    for (int i=0; i<ui->tableView->colorCount(); i++) {
        totalWidthColumms+= ui->tableView->columnWidth(i);
    }
    

    }

    void WorkoutCreator::on_pushButton_repeat_clicked()
    {
    QModelIndexList lstIndex = ui->tableView->selectionModel()->selectedRows();

    if (lstIndex.size() < 1) {
        return;
    }
    
    QRect recFirstSelection(ui->tableView->visualRect(lstIndex.at(0)));
    QRect recLastSelection(ui->tableView->visualRect(lstIndex.at(lstIndex.size()-1)));
    QRect rectCompleteSelection(recFirstSelection.topLeft(), recLastSelection.bottomRight() );
    rectCompleteSelection.setWidth(totalWidthColumms);
    
    rubberBand->move(rectCompleteSelection.topLeft());
    rubberBand->resize(rectCompleteSelection.size());
    

    // rubberBand->setGeometry(rectCompleteSelection);
    rubberBand->show();
    }
    @



  • I'm struggling to add text in the QRubberBand paintEvent, I can add a frame around the selection, but for some reason the drawText is not displaying., investigating..

    @void myRubberBand::paintEvent(QPaintEvent *event)
    {
    QRect rec1(event->rect());

    qDebug() << "-----------------\nRECT ORIGINAL" << rec1;
    
    //    QStylePainter painter(this);
    //    QStyleOptionFocusRect option;
    //    option.initFrom(this);
    QStylePainter painter(this);
    QStyleOptionRubberBand option;
    initStyleOption(&option);
    
    
    QPen pen(Qt::red, 2);
    pen.setStyle(Qt::SolidLine);
    //    painter.setBrush(Qt::transparent);
    painter.setPen(pen);
    
    
    /// Find the middle right area
    //    QPoint bottomRight(rec1.bottomRight());
    //    bottomRight.setX(bottomRight.x() + 70);
    //    QRect rectText(rec1.topRight(), bottomRight);
    
    //    qDebug() << "RECT NEW TEXT" <<  rectText;
    
    
    painter.drawText(rec1.topRight(), "abc");
    painter.drawRect(rec1);
    
    
    //    painter.end();
    

    }@



  • May be related to this warning: I get a bunch when I launch my program, haven't found the cause :

    @QPaintDevice::metrics: Device has no metric information@

    [Edit: this error what due to bad code here :
    for (int i=0; i<ui->tableView->colorCount(); i++)
    I simply misread colorCount for columnCount


  • Lifetime Qt Champion

    Aren't you drawing the rect above the text ?

    The metrics error is indeed surprising, what OS are you running on ?



  • Hi,

    I am using Windows 8.1 x64 Enterprise

    I want to draw 2 object ;
    1- Rectangle highlighting with my row(s) selections
    2- Text showing a number at the right side of this rectangle (this one is not showing even if I comment the first object

    You can see my current interface here :
    https://www.dropbox.com/s/puzz9jwx09fwyp9/interface111.png
    What is missing is the black box where I need to draw text

    I updated my current QRubberBand paintEvent up here


  • Lifetime Qt Champion

    Since you are using the event rect which probably is the current QRubberBand geometry, painting on the top right corner makes you paint outside your widget



  • You are right, the text is not painting because it is outside of the rectangle, I tried with this code and I can paint part of the text. Now is there a possibility to draw outside of the event.rect ? If not, I'm thinking maybe add a dummy column at the far right that will serve as place to draw the text. not really clean but could work...

    Current result with code below :
    https://www.dropbox.com/s/kumzp59opq9vuhg/currentREsult.png

    @void myRubberBand::paintEvent(QPaintEvent *event)
    {
    QRect rec1(event->rect());

    // qDebug() << "-----------------\nRECT ORIGINAL" << rec1;
    QStylePainter painter(this);
    QStyleOptionRubberBand option;
    initStyleOption(&option);

    QPen pen(Qt::red, 2);
    pen.setStyle(Qt::SolidLine);
    //    painter.setBrush(Qt::transparent);
    painter.setPen(pen);
    
    /// Find the middle right area of rectangle
    QPointF pointMiddleRight(rec1.center());
    pointMiddleRight.setX(pointMiddleRight.x() + 280);
    painter.drawText(pointMiddleRight, "abc");
    painter.drawRect(rec1);
    

    }@


  • Lifetime Qt Champion

    No there's not.

    Another way to do it, is to add a transparent (mouse event and color transparent) widget over your table view and do all the painting there.

    Or a second rubber band for the text part that you keep in sync with the original

    Or make the rubber band larger to also have the space to write the text.



  • All good solutions, working perfectly!, Qt is very flexible
    Thanks again SGaist



  • Hey it's me again, I know I dragged this post a lot maybe I should start a new :)

    Just wondering if something like this would be possible :
    https://www.dropbox.com/s/xhczf5cg7pu8fxw/editableQRubberBand.png
    (Editable QComboBox inside the QRubberBand)

    I know how to draw the text, but not sure the editable QcomboBox is possible? Painting possible, but finding where the signals of the QComboBox is coming from?, maybe giving a unique name to the QComboBox, just tell me if you foresee this as possible before I waste too much time, thanks!


  • Lifetime Qt Champion

    Contextual menu ?



  • Contextual menu? This is a right click menu right? IMO this doesn't really feel natural for this function, maybe for removing, copying rows I will use this but for what I need I would like a separate widget that is always displaying inside the QTableView, one widget per Repeat box, like shown in the pic. Not sure it is possible, I can draw it inside the QRubberBand, but it would be static and not usable afterwards. Still trying to use my imagination but I have limited Qt experience :/


  • Lifetime Qt Champion

    Yes and you have a point. Then you could have a QComboBox as member of your QRubberBand and handle its position yourself



  • Hi SGaist,
    Finally I used the solution of a custom QWidget over the QTableView, I needed a layout and it's easier to manage:

    Here is the result interface :
    https://www.dropbox.com/s/40osxyrdgxkuaf6/finalUi.png

    I post the code in the next post if someone wants to do something similar
    <Thread Solved for real now!>

    Thank you



  • RepeatWidget :
    @RepeatWidget::RepeatWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::RepeatWidget)
    {
    ui->setupUi(this);

    id = 0;
    firstRow = 0;
    lastRow = 0;
    numberRepeat = 0;
    

    }

    void RepeatWidget::on_pushButton_delete_clicked()
    {
    emit deleteSignal(id);
    }

    void RepeatWidget::on_comboBox_repeat_currentIndexChanged(const QString &arg1)
    {
    this->numberRepeat = arg1.toInt();
    emit updateSignal(id);
    }@

    ParentUiClass with QListView:

    @WorkoutCreator::~WorkoutCreator()
    {
    delete ui;
    qDeleteAll(lstRepeatWidget);
    lstRepeatWidget.clear();
    }

    WorkoutCreator::WorkoutCreator(QWidget *parent) : QWidget(parent), ui(new Ui::WorkoutCreator)
    {
    ui->setupUi(this);

    [...]

    connect(ui->tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
            this, SLOT(tableViewSelectionChanged(QItemSelection,QItemSelection)));
    
    totalWidthColumms = 0;
    for (int i=0; i<ui->tableView->colorCount(); i++) {
        totalWidthColumms += ui->tableView->columnWidth(i);
    }
    
    ui->pushButton_delete->setEnabled(false);
    ui->pushButton_copy->setEnabled(false);
    ui->pushButton_repeat->setEnabled(false);
    

    }

    void WorkoutCreator::tableViewSelectionChanged(QItemSelection selected, QItemSelection deselected) {

    /// default behavior = disabled
    ui->pushButton_delete->setEnabled(false);
    ui->pushButton_copy->setEnabled(false);
    ui->pushButton_repeat->setEnabled(false);
    

    // QModelIndexList lstIndex = selected.indexes();
    QModelIndexList lstIndex = ui->tableView->selectionModel()->selectedRows();
    qDebug() << lstIndex.size();

    if (lstIndex.size() < 1) {
        return;
    }
    
    ui->pushButton_delete->setEnabled(true);
    ui->pushButton_copy->setEnabled(true);
    
    QModelIndex firstIndex = lstIndex.at(0);
    QModelIndex lastIndex = lstIndex.at(lstIndex.size()-1);
    
    
    /// --- Check All overlappign possibilities (see if we can enable repeat button)
    foreach (RepeatWidget *wid, lstRepeatWidget) {
    
        if (firstIndex.row() >= wid->firstRow && firstIndex.row() <= wid->lastRow )  {
            qDebug() << "1 REFUSE";
            return;
        }
    
        if (lastIndex.row() <= wid->lastRow && lastIndex.row() >= wid->firstRow)  {
            qDebug() << "2 REFUSE";
            return;
        }
        if (firstIndex.row() < wid->firstRow && lastIndex.row() > wid->lastRow)  {
            qDebug() << "2 REFUSE";
            return;
        }
    }
    
    ui->pushButton_repeat->setEnabled(true);
    

    }

    void WorkoutCreator::on_pushButton_repeat_clicked()
    {

    QModelIndexList lstIndex = ui->tableView->selectionModel()->selectedRows();
    
    if (lstIndex.size() < 1) {
        return;
    }
    
    QModelIndex firstIndex = lstIndex.at(0);
    QModelIndex lastIndex = lstIndex.at(lstIndex.size()-1);
    
    QRect recFirstSelection(ui->tableView->visualRect(firstIndex));
    QRect recLastSelection(ui->tableView->visualRect(lastIndex));
    QRect rectCompleteSelection(recFirstSelection.topLeft(), recLastSelection.bottomRight() );
    rectCompleteSelection.setWidth(totalWidthColumms);
    
    
    RepeatWidget *repeatWidget = new RepeatWidget(ui->tableView->viewport());
    repeatWidget->id = idRepeatWidget;
    repeatWidget->firstRow = firstIndex.row();
    repeatWidget->lastRow = lastIndex.row();
    repeatWidget->move(rectCompleteSelection.topLeft());
    repeatWidget->resize(rectCompleteSelection.size().width() + 190, rectCompleteSelection.size().height());
    repeatWidget->show();
    
    
    connect(repeatWidget, SIGNAL(deleteSignal(int)), this, SLOT(deletedRepeatWidget(int)) );
    connect(repeatWidget, SIGNAL(updateSignal(int)), this, SLOT(updatedRepeatWidget(int)) );
    
    
    /// Add widget to QList
    idRepeatWidget ++;
    lstRepeatWidget.append(repeatWidget);
    

    }

    void WorkoutCreator::on_pushButton_add_clicked()
    {
    intervalModel->insertRow(intervalModel->rowCount());
    }

    void WorkoutCreator::on_pushButton_copy_clicked()
    {

    QModelIndexList lstIndex = ui->tableView->selectionModel()->selectedRows();
    
    foreach (QModelIndex index,  lstIndex) {
        qDebug() << "going to copy row:" << index.row();
        intervalModel->copyRows(index.row());
    }
    

    }

    void WorkoutCreator::on_pushButton_delete_clicked()
    {

    QModelIndexList lstIndex = ui->tableView->selectionModel()->selectedRows();
    
    if (lstIndex.size() < 1) {
        return;
    }
    
    int startRow = lstIndex.at(0).row();
    intervalModel->removeRows(startRow, lstIndex.size(), QModelIndex());
    

    }

    void WorkoutCreator::deletedRepeatWidget(int id) {

    qDebug() << "deletedRepeatWidget" << id;
    
    RepeatWidget *widgetToDelete;
    /// Better way to delete without iterating over QList? Array worth it? small data set..
    foreach (RepeatWidget *wid, lstRepeatWidget) {
        if (wid->id == id) {
            wid->setVisible(false);
            widgetToDelete = wid;
            break;
    
        }
    }
    lstRepeatWidget.removeOne(widgetToDelete);
    

    }

    void WorkoutCreator::updatedRepeatWidget(int id) {

    qDebug() << "updatedRepeatWidget" << id;
    /// TODO: update graph with new values
    

    }
    @


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.