По поводу выравнивания чекбоксов есть вариант решения в
факе, но он кривоватый (попробуйте - увидите сами), поэтому хочу предложить свой, модифицированный.
Итак, нам необходимо определить свой класс делегата:
class ItemDelegate : public QItemDelegate
ОтрисовкаЧтобы нарисовать чекбокс по центру ячейки достаточно переопределить метод
drawCheck. В него передаётся параметр
rect, мы его модифицируем, сместив в центр и вызовем метод базового класса:
void ItemDelegate::drawCheck(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, Qt::CheckState state) const {
QRect rect_copy(rect);
if(!rect_copy.isNull()) {
rect_copy.moveCenter(option.rect.center());
}
QItemDelegate::drawCheck(painter, option, rect_copy, state);
}
Если сейчас сделать и запустить тестовый проект с таким кодом, то мы увидим, что чекбокс рисуется по центру, как мы и хотели... до тех пор, пока мы не выделим ячейку. А при выделении ячейки цвет выделения полностью закрашивает наш чекбокс. Происходит это потому, что переместив чекбокс в центр ячейки, мы теперь его рисуем в области, где Qt рисует текст. Рисование текста происходит это в функции
drawDisplay, которая вызывается после
drawCheck. В ней Qt заливает область цветом выделения, затем рисует текст. Текст у нас пустой (иначе зачем бы мы стали выравнивать чекбокс по центру), однако заливка цветом выделения всё равно происходит. Зачем Qt так делает, я не понял, ведь при отрисовке ячейки первым делом вызывается функция
drawBackground, которая заливает
всю ячейку нужным цветом, поэтому в
drawDisplay происходит повторная заливка части уже залитой области.
Проще всего избавиться от этого поведения - переопределить функцию
drawDisplay просто оставив её пустой:
void ItemDelegate::drawDisplay(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QString& text) const {
}
Теперь можно видеть, что чекбоксы у нас рисуются по центру как в выделенных, так и в не выделенных ячейках. Если вы устанавливаете делегат только на столбцы с чекбоксами, то можно сразу переходить к части
Редактирование. Однако я, как правило, устанавливаю один делегат на всю таблицу и предоставляю ему самому разбираться, где чекбоксы, где нет и правильно отрисовывать любые ячейки. Для этого придётся ещё немного поработать.
Главное что нам нужно знать - рисуем ли мы сейчас ячейку с чекбоксом или ячейку другого типа, а точнее, нам достаточно знать, присутствует ли в отрисовываемой ячейке текст или нет. Ячейки с текстом, даже чекбоксы, нам надо рисовать как обычно. К сожалению, в методе
drawCheck узнать это, похоже, нельзя (или я не знаю как), поэтому необходимо переопределить функцию
paint:
void ItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
_draw_display = !index.data(Qt::DisplayRole).value<QString>().isEmpty();
QItemDelegate::paint(painter, option, index);
}
Последовательность вызовов здесть такая: при отрисовке ячейки сначала вызывается функция
paint, в которой мы устанавливаем флажок наличия в ячейке текста. Потом мы вызываем функцию базового класса из которой вызовутся наши переопределённые
drawCheck и
drawDisplay.
Переменная
_draw_display должна иметь тип
mutable bool, потому что нам приходится модифицировать её в константной функции
paint.
Теперь
drawCheck будет выглядить таким образом:
void ItemDelegate::drawCheck(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, Qt::CheckState state) const {
QRect rect_copy(rect);
if(!_draw_display) {
if(!rect_copy.isNull()) {
rect_copy.moveCenter(option.rect.center());
}
}
QItemDelegate::drawCheck(painter, option, rect_copy, state);
}
А
drawDisplay таким:
void ItemDelegate::drawDisplay(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QString& text) const {
if(_draw_display) {
QItemDelegate::drawDisplay(painter, option, rect, text);
}
}
Т.е. логика простейшая: если текст в ячейке есть, то мы ничего не трогаем, всю работу делают базовые функции (чекбоксы с текстом мы не выравниваем, я думаю, это логично). Если текста нет, вмешиваемся в процесс отрисовки, пытаясь выровнять чекбокс по центру. Причём здесь даже не важно, является ли отрисовываемая ячейка чекбоксом, всё будет работать правильно в любом случае.
Ну вот с отрисовкой и всё, букв много, но на самом деле всё просто, всю работу выполняют три последних фрагмента кода (переопределённые
paint,
drawCheck и
drawDisplay).
РедактированиеЕсли мы оставим всё как есть, то состояние чекбокса будет меняться при клике не в то место, где мы его отрисовали теперь (по центру), а в то, где Qt думает, что он до сих пор находится (слева). Обработку пользовательского ввода Qt осуществляет в функции
editorEvent, которую можно (и нужно) переопределить. К сожалению, область, где находится чекбокс, в эту функцию не передаётся, поэтому нет возможности модифицировать входные параметры и вызвать базовую функцию. Нам ничего не остаётся, кроме как скопипастить код библиотеки, закомментировать строки, где определяется положение чекбокса и вставить туда свои. Получается так:
bool ItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) {
const bool draw_display = !index.data(Qt::DisplayRole).value<QString>().isEmpty();
if(draw_display) {
return QItemDelegate::editorEvent(event, model, option, index);
}
else {
Q_ASSERT(event);
Q_ASSERT(model);
// make sure that the item is checkable
Qt::ItemFlags flags = model->flags(index);
if (!(flags & Qt::ItemIsUserCheckable) || !(option.state & QStyle::State_Enabled)
|| !(flags & Qt::ItemIsEnabled))
return false;
// make sure that we have a check state
QVariant value = index.data(Qt::CheckStateRole);
if (!value.isValid())
return false;
// make sure that we have the right event type
if ((event->type() == QEvent::MouseButtonRelease)
|| (event->type() == QEvent::MouseButtonDblClick)) {
//QRect checkRect = check(option, option.rect, Qt::Checked);
//QRect emptyRect;
//doLayout(option, &checkRect, &emptyRect, &emptyRect, false);
QStyleOptionButton styleOptionButton;
QRect checkRect = QApplication::style()->subElementRect(QStyle::SE_ViewItemCheckIndicator, &styleOptionButton);
checkRect.moveCenter(option.rect.center());
QMouseEvent *me = static_cast<QMouseEvent*>(event);
if (me->button() != Qt::LeftButton || !checkRect.contains(me->pos()))
return false;
// eat the double click events inside the check rect
if (event->type() == QEvent::MouseButtonDblClick)
return true;
} else if (event->type() == QEvent::KeyPress) {
if (static_cast<QKeyEvent*>(event)->key() != Qt::Key_Space
&& static_cast<QKeyEvent*>(event)->key() != Qt::Key_Select)
return false;
} else {
return false;
}
Qt::CheckState state;
if ( flags & Qt::ItemIsTristate ) {
state = static_cast<Qt::CheckState>( (value.toInt() + 1) % 3 );
} else {
state = (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked
? Qt::Unchecked : Qt::Checked);
}
return model->setData(index, state, Qt::CheckStateRole);
}
}
Теперь мы можем безопасно устанавливать такой делегат на всю таблицу и наслаждаться выровненными по центру чекбоксами. Ну, по крайней мере у меня всё работает как часики