Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > Кроссплатформенный C/C++: cl/gcc/Qt/Gtk+/WxWidgets > QTableWidget: как перейти от выделения ячейки к выделению строки?


Автор: vlad2 02.05.24, 15:24
Есть таблица QTableWidget, в которой изначально поставлено выделение строк: QAbstractItemView::SelectRows.
Есть кнопка по которой, по которой редактирую содержимое ячеек текущей строки, устанавливая QAbstractItemView::SelectItems.
После редакции перехожу на другую строку таблицы и хочу чтобы при этом выделение сразу вернулось к SelectRows. Но сразу этого не получается, нужно дополнительно кликнуть по таблице ещё раз.
Как сделать, чтобы сразу?
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    void MainWindow::on_btnEdit_clicked()
    {
        ui->tabW->clearSelection();
        ui->table->editItem(ui->table->item(ui->table->currentRow(), 1));
    //    ui->table->setSelectionMode(QAbstractItemView::SingleSelection);   // ничего не даёт
        ui->table->setSelectionBehavior(QAbstractItemView::SelectItems);
    }
     
    ...
     
    void MainWindow::on_table_currentCellChanged(int curRow, int curCol, int prevRow, int prevCol)
    {
     
        if (curRow != prevRow)
        {
    //        ui->table->clearSelection();
            ui->table->setEditTriggers( QAbstractItemView::NoEditTriggers );
    //        ui->table->setSelectionMode(QAbstractItemView::MultiSelection);
            ui->table->setSelectionBehavior(QAbstractItemView::SelectRows);
    //        ui->table->setRangeSelected(QTableWidgetSelectionRange(curRow, 0, curRow, ui->table->columnCount()-1), true);  // ничего не даёт
    }

Автор: Majestio 03.05.24, 03:03
Привет, бро!

Твоим вопросом пока не занимаюсь, но хочу дать тебе парочку советов. Которые как армейский Устав, "писаны кровью" (читай - временем на разборки):

  1. Прямо сейчас забудь про компоненты Qt, которые заканчиваются на *Widget. Вместо них пользуй *View. В твоем случае это QTableView - они более конфигурабельны. Виджеты - они для студентов, домохозяек и homeless людей.
  2. Если беда с кликами или клавой - она не беда, просто реализуй соответствующие обработчики, и смотри уже в них что и когда происходит

Автор: vlad2 03.05.24, 06:47
Цитата Majestio @
Вместо них пользуй *View.
Т.е. не пользоваться дизайнером? Но это упрощает работу, тем более, если использовать свой класс на базе QTableWidget.

Автор: Majestio 03.05.24, 09:11
Цитата vlad2 @
Т.е. не пользоваться дизайнером?

Забудь про дизайнер!!! Это - зло. Все контролы и их размещение нужно делать руками, вот это - по фэншую!

Автор: Majestio 04.05.24, 09:03
Цитата vlad2 @
Но это упрощает работу

Оно упрощает работу ... на первый взгляд, когда ты создаешь "тупые и статические" формы. Но, как-только ты решишь разнообразить свой интерфейс - ты получишь кучу гемора от декларативных объявлений. Я тебе не советую "с потолка", я сам к этому пришел путем проб и ошибок. Программно создавать интерфейс на первый взгляд сложно. Но потом, когда это уже будет твоей практикой, я тебя уверяю - ты программно напишешь интерфейс кодом гораздо быстрее гвно-кликов в UI-дизайнере. Но у тебя есть варик - "пройди мой путь", если просто не доверяешь. Как говорят: "нет преграды патриотам" :lol:

Автор: vlad2 06.05.24, 08:17
Majestio, спасибо. Если бы начинал программировать, то, наверное, воспользовался советом. Но мой стаж уже не 10 лет и даже не 20).
Сейчас же меня интересует ответ на вопрос, поставленный в первом посте.

Автор: Majestio 06.05.24, 10:56
Цитата vlad2 @
Сейчас же меня интересует ответ на вопрос, поставленный в первом посте.

Без синтетического примера сложно дать на 100% правильный ответ. Проверь вот такую связку в on_table_currentCellChanged:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    ui->table->clearSelection();
    ui->table->selectRow(curRow);

И еще, я думаю, что проверка if (curRow != prevRow) лишняя. Представь, ты редактируешь ячейку в строке, потом просто кликаешь на соседнюю ячейку этой же строки. По идее должно отработать ровно так же если бы ты кликал на ячейку другой строки?

Автор: vlad2 06.05.24, 11:25
Цитата Majestio @
Проверь вот такую связку
Проверял - до того, как написать и сейчас - не работает.
Т.е. мне надо, чтобы при редакции строки выделялась лишь текущая ячейка, а не вся строка, поэтому перед редакцией устанавливаю опцию QAbstractItemView::SelectItems. Но как только перехожу на другую строку, возможность редактирования пропадает (это работает) и сразу выделяется строка - устанавливаю опцию QAbstractItemView::SelectRows (для этого мне и нужна проверка if (curRow != prevRow)
Получается же, что при переходе на другую строку, остаётся выделенной только ячейка, куда кликнул, а чтобы выделилась строка, нужно дополнительно кликнуть ещё раз - куда угодно. Ну некрасиво).

Автор: Majestio 06.05.24, 15:28
Все-таки без какого-то, хотя бы минимального синтетического примера, трудно что-то говорить. Завершение редактирования обычно отлавливают двумя сигналами:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    // Пример использования сигнала itemChanged
    connect(tableWidget, &QTableWidget::itemChanged, [=](QTableWidgetItem* item) {
        // Обработка события завершения редактирования ячейки
    });
     
    // Пример использования сигнала cellChanged
    connect(tableWidget, &QTableWidget::cellChanged, [=](int row, int column) {
        // Обработка события завершения редактирования ячейки
    });

Но не факт, что тебе это сможет помочь, т.к. ты манипулируешь двумя разными видами выделения. В этом случае, мне почему-то кажется, что нужно "спуститься вниз по иерархии" сигналов. А именно обрабатывать "сырой" клик на таблицу. И в нем уже обрабатывать начало редактирования, завершение редактирования, очередное выделение. Т.е. всё, что автоматом реализовано в QTableWidget в плане редактирования ячеек - тебе нужно будет переписать под себя руками. А на встроенные механизмы не надеяться.

Я бы попробовал убедиться в этом следующим образом - код, который вызывается в on_table_currentCellChanged запускать не напрямую, а через лямбду в QTimer::singleShot, с задержкой, допустим в 3 сек. С выводом в qDebug(). И обратить внимание, как отработало, что именно происходило, не было ли такого, что код отработал, а потом остальная логика QTableWidget вернула предыдущие настройки выделения.

Автор: vlad2 06.05.24, 15:54
Majestio, окончание редактирования я делаю сам:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
        void MainWindow::on_table_currentCellChanged(int curRow, int curCol, int prevRow, int prevCol)
        {
        
            if (curRow != prevRow)
            {
                ui->table->closePersistentEditor(ui->table->item(curRow, curCol));
                ui->table->clearSelection();
                ui->table->setEditTriggers( QAbstractItemView::NoEditTriggers );
                ui->table->setSelectionBehavior(QAbstractItemView::SelectRows);
            }
        }
И здесь SelectRows восстанавливается, потому что при повторном клике, например, по той же ячейке, где нахожусь, строка сразу же принимает вид выделенной. Такое ощущение, что после setSelectionBehavior(QAbstractItemView::SelectRows) нужно перерисовать строку или таблицу. Но repaint() не помогает.

Автор: Majestio 06.05.24, 16:57
Хорошо, а если добавить в самом конце, в блоке if:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    ui->table->setSelectionMode(QAbstractItemView::SingleSelection);
    ui->table->selectRow(curRow);

Что-то изменится?

Автор: vlad2 07.05.24, 07:16
Цитата Majestio @
Что-то изменится?
Нет. Я и раньше проделывал все эти танцы с бубном, и сейчас попробовал.

Автор: Majestio 07.05.24, 09:41
Цитата vlad2 @
Нет. Я и раньше проделывал все эти танцы с бубном, и сейчас попробовал.

Можешь скинуть свой код, ну или его чаcть, где это работает (вернее - не работает)?

Автор: vlad2 07.05.24, 09:56
Цитата Majestio @
Можешь скинуть свой код
Позже, может, к вечеру.

Автор: vlad2 07.05.24, 13:41
Вот рабочий тестовый пример.
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    //  mainwindow.h
     
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
     
    #include <QDialog>
    #include <QMainWindow>
     
    namespace Ui {
    class MainWindow;
    }
     
    class MainWindow : public QDialog
    {
        Q_OBJECT
     
    public:
        explicit MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
     
    private slots:
        void on_btnEdit_clicked();
     
        void on_table_currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn);
     
    private:
        Ui::MainWindow *ui;
      int Mode;
    };
     
    #endif // MAINWINDOW_H
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    //  mainwindow.cpp
     
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
     
    MainWindow::MainWindow(QWidget *parent) :
        QDialog(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        ui->table->blockSignals(true);
        ui->table->setRowCount(0);
        ui->table->setRowCount(10);
     
        for (int k = 0; k < ui->table->rowCount(); ++k)
        {
          for (int i = 0; i < ui->table->columnCount(); ++i)
          {
            ui->table->setItem(k, i, new QTableWidgetItem(QString::number(((k + i) * (k - i)) / 0.27)));
          }
        }
        ui->table->blockSignals(false);
        Mode = 0;
    }
     
    MainWindow::~MainWindow()
    {
        delete ui;
    }
     
    void MainWindow::on_btnEdit_clicked()
    {
        ui->table->clearSelection();
        ui->table->setEditTriggers( QAbstractItemView::AllEditTriggers);
        ui->table->editItem(ui->table->item(ui->table->currentRow(), 1));
    //    ui->table->setSelectionMode(QAbstractItemView::SingleSelection);
        ui->table->setSelectionBehavior(QAbstractItemView::SelectItems);
        Mode = 1;
    }
    void MainWindow::on_table_currentCellChanged(int curRow, int curCol, int prevRow, int prevCol)
    {
        if (Mode && curRow != prevRow)
        {
            ui->table->blockSignals(true);
            ui->table->closePersistentEditor(ui->table->item(curRow, curCol));
            ui->table->setEditTriggers( QAbstractItemView::NoEditTriggers );
     
    //        ui->table->setSelectionMode(QAbstractItemView::MultiSelection);
            ui->table->setSelectionBehavior(QAbstractItemView::SelectRows);
            ui->table->selectRow(curRow);
     
    //        ui->table->setRangeSelected( QTableWidgetSelectionRange(ui->table->currentRow(), 0, ui->table->currentRow(), ui->table->columnCount()-1), true);
            Mode = 0;
            ui->table->blockSignals(false);
        }
    }

Автор: Majestio 08.05.24, 20:24
Еще бы mainwindow.ui, без него как-то тоскливо ;)

Автор: Majestio 09.05.24, 12:29
Цитата vlad2 @
Majestio, спасибо. Если бы начинал программировать, то, наверное, воспользовался советом. Но мой стаж уже не 10 лет и даже не 20).
Сейчас же меня интересует ответ на вопрос, поставленный в первом посте.

В общем, не дождался я UI-файла, накидал тебе работающий проект. По идее, все как тебе нужно. По твоему коду, невзирая на твой громадный стаж программирования, могу сказать одно - учиться никогда не поздно. Особенно если громадный стаж не касается какого-то нового для тебя инструментария. Если более конкретно - не забывай, что фрэймворк Qt построен по принципу MVC. Не нужно загонять данные в представление, представление его должно само извлекать из модели. Представь, что у тебя таблица со стопицот миллионов записей. Во время отображения, скроллинга, перерисовки QTableWidget/QTableView сам запросит нужную порцию данных. А вот как и откуда получит эти данные модель - это уже её зона ответственности.

В присоединенном примере я это тебе реализовал. Там данные хранятся в переменной типа QVector<QVector<QString>>, при изменении - тудаже и записываются. Кнопочка [Dump] на форме в лог пишет текущее содержимое этой переменной. Ну а по самому интерфейсу все упрощенно:
  • Двойной клик на ячейке включает режим ее редактирования
  • Если во время редактирования нажать ESC, произойдет выход из режима редактирования и возврат исходного содержимого
  • Если во время редактирования нажать Enter или кликнуть на другую ячейку, то произойдет выход из режима редактирования и новые данные сохранятся
  • Про кнопку Dump уже писал выше
Проект собирал под Qt 5.15.13 и Qt 6.7.0 - везде полёт нормальный. В общем, качай и разбирайся, может чего и пригодится.

TestEditWidget.src.7z (, : 17)

P.S. Небольшое дополнение. Присоединенный пример - конечно не эталон. Там упущена одна важная деталь - разорвана "связь" между хранилищем данных (переменной) и моделью. По фэн-шую хранилище данных нужно обернуть классом, сделать ему геттеры и сеттеры, а также связать сигналами & слотами его с моделью. Тогда при любом изменении данных в хранилище - изменения автоматом улетят в модель, а оттуда уже в представление.

Автор: vlad2 13.05.24, 09:11
Цитата Majestio @
Еще бы mainwindow.ui
Добавил.
Цитата Majestio @
В общем, не дождался я UI-файла, накидал тебе работающий проект
Извини, был в отъезде. Спасибо за проект, будет полезен для изучения. В твоём примере нет перехода от SelectItems к SelectRows, о чём , собственно и был мой первый вопрос: как сделать, чтобы строка выделялась сразу после того, как в ходе выполнения кода встречается setSelectionBehavior(QAbstractItemView::SelectRows) или что-то надо ещё добавить. Если не менять выделение строки, то редактируемая строка выглядит примерно так, как в твоём примере, только остаётся выделенной, а не серой, как в примере (см. картинку). Видимо, в твоём примере фокус переходит на editor, а у меня - нет.
Что касается данных для таблицы, то в реальных таблицах они разных типов и берутся из двоичных файлов, поэтому в качестве хранения использую контейнеры структур типа QList<...>. Твой пример пригодится для организации редактирования с откатами и подтверждениями. Ну и для понимания структуры программ в Qt.
mainwindow.ui (, : 16)
tab01.jpg (, : 87)

Автор: Majestio 13.05.24, 10:39
Цитата vlad2 @
В твоём примере нет перехода от SelectItems к SelectRows, о чём , собственно и был мой первый вопрос: как сделать, чтобы строка выделялась сразу после того, как в ходе выполнения кода встречается setSelectionBehavior(QAbstractItemView::SelectRows) или что-то надо ещё добавить. Если не менять выделение строки, то редактируемая строка выглядит примерно так, как в твоём примере, только остаётся выделенной, а не серой, как в примере (см. картинку). Видимо, в твоём примере фокус переходит на editor, а у меня - нет.

Да, когда я делал свой пример, у меня была одна цель - продемонстрировать правильный вход и выход из режима редактирования. Чтобы не было необходимости дополнительного клика на таблице, как ты писал в ранних сообщениях. Однако, хочу заметить, ты хочешь несколько видоизменить функционал интерфейса. Но по стандартам проектирования GUI фокусом может обладать только один элемент! А ты пытаешься сделать фокусом и строку, и редактируемую ячейку. Твой подход в данном случае будет неправильным. Предлагаю не ломать стандартную логику фокусного выделения, а просто перекрасить то, что не устраивает - в моем случае "серое" выделение. Решение этого несложное, добавим немножко кода.

В файле ItemEditDelegate.h добавим перекрытие метода initStyleOption, теперь серый цвет будет заменен синим:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    #ifndef ITEMEDITDELEGATE_H
    #define ITEMEDITDELEGATE_H
     
    #include <QStyledItemDelegate>
     
    class ItemEditDelegate : public QStyledItemDelegate {
            Q_OBJECT
     
        public:
            explicit ItemEditDelegate(QObject *parent = nullptr);
            QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
            void initStyleOption(QStyleOptionViewItem *option, const QModelIndex& index) const override {
                QStyledItemDelegate::initStyleOption(option, index);
                if (option->state & QStyle::State_Selected) {
                    option->palette.setColor(QPalette::HighlightedText, QColor(Qt::white));
                    option->palette.setColor(QPalette::Highlight, QColor(Qt::blue));
                }
            }
     
        signals:
            void cellEditingStarted(const QModelIndex& index) const;
            void cellEditingFinished(const QModelIndex& index) const;
    };
     
    #endif // ITEMEDITDELEGATE_H

Одно важное замечание - в моем дополнении "перекраска" проводится строго в синий цвет. Но народ достаточно часто меняет под себя темы оформления своего рабочего стола. И не у всех цвет выделения синий. Поэтому будет более правильно с перекрываемом методе сперва находить цвет фона и текста по системным метрикам. Конечно это платформо-зависимые шляпы, для Windows своё WinAPI, для Линукc/FreeBSD - API X11, для MacOSX - API XQuartz. В общем, если тебя этот вопрос волнует - это тебе домашнее задание :lol:

Автор: vlad2 13.05.24, 11:36
Цитата Majestio @
А ты пытаешься сделать фокусом и строку, и редактируемую ячейку.

Вовсе нет). На приведенной выше картинке показано, как не надо. Видимо, плохо объяснил. На приложенной к этому посту картинке показал текущую ситуацию:
A - допустим, редактирую 5-ю строку (здесь SelectItems);
B - после клика по 2-й строке, неважно, в какой ячейке, редакция заканчивается и устанавливается SelectRows. Но мы видим, что, несмотря на это, строка не выделена. Чтобы строка выделилась, нужно ещё раз кликнуть по этой строке;
C - такую картинку хочу получить сразу после клика по 2-й строке.

Автор: Majestio 14.05.24, 00:03
Ну ок, спорить нет желания :lol: См. мою последнюю правку - по идее то, что ты хотел бы видеть.

Автор: vlad2 14.05.24, 06:51
Цитата Majestio @
См. мою последнюю правку - по идее то, что ты хотел бы видеть.
Хотел я А + С из предыдущего поста. Если это похоже на твой пример с правкой, то ок, тоже не люблю спорить).
В любом случае - спасибо за помощь.

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)