I'm currently trying to build a Qt table by subclassing QTableView, QAbstractTableModel and QStyledItemDelegate to maintian some semblance of MVC archiectecture.
I'm using this simple example as a base to build on as I haven't gone near Qt table classes before:
http://qt-project.org/doc/qt-4.8/modelview.html
Anyway the table is mostly text columns but it also needs a single toggle button column and a single checkbox column.
I've noticed that the data method of the model can be used to implement a checkbox but I'm going to need a custom delegate for the button so I was going to use it for the checkbox also.
Anyway I'm unable to find any decent examples on the internet that create tables by using the QTableView object with a mixture of text, checkboxes and buttons. Can any of you good sirs point me in the right direction?
You don't need a custom delegate for having checkbox and toggle button in your tableview. You can simply make your item checkable and set it to your model like:
QStandardItem *item = new QStandardItem( true );
item->setCheckable(true);
item->setCheckState(Qt::Unchecked);
QStandardItemModel * model = new QStandardItemModel( 0, 2 );
model->setRowCount(1);
model->setItem(0, 0, item);
For a toggle button you can do like:
QPushButton * but = new QPushButton(this);
but->setCheckable(true);
but->setText("Toggle");
ui->tableView->setIndexWidget(model->item(0,1)->index(),but);
Based on the information Dmitry provided above I've implemented the following paint method in my delegate for rendering my button and checkbox. Obviously this needs an editorEvent() to do anything I can add this also if it proves useful.
void DDUTableDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
int col = index.column();
if (col == VIEW_COLUMN) {
// Draw our checkbox indicator
bool value = index.data(Qt::EditRole).toBool();
QStyleOptionButton checkbox_indicator;
// Set our button state to enabled
checkbox_indicator.state |= QStyle::State_Enabled;
checkbox_indicator.state |= (value) ? QStyle::State_On : QStyle::State_Off;
// Get our deimensions
checkbox_indicator.rect = QApplication::style()->subElementRect( QStyle::SE_CheckBoxIndicator, &checkbox_indicator, NULL );
// Position our indicator
const int x = option.rect.center().x() - checkbox_indicator.rect.width() / 2;
const int y = option.rect.center().y() - checkbox_indicator.rect.height() / 2;
checkbox_indicator.rect.moveTo( x, y );
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
}
QApplication::style()->drawControl( QStyle::CE_CheckBox, &checkbox_indicator, painter );
}
else if (col == TEST_COLUMN) {
bool value = index.data(Qt::EditRole).toBool();
QStyleOptionButton button;
// Set our button to fill the entire cell contents
button.rect = option.rect;
// Set our button state to enabled
button.state |= QStyle::State_Enabled;
if (value) {
button.state |= QStyle::State_Sunken;
button.text = STOP_TEST;
}
else {
button.text = START_TEST;
}
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
}
QApplication::style()->drawControl(QStyle::CE_PushButton, &button, painter);
}
}
Related
I have a QTableView with my own implemented QAbstractItemModel, in which I can drag and drop multiple items inside. My problem is that when dragging the items and while trying to drop them in a destination cell, it is not so obvious for the user what the result is going to be. For example, I have the following,but I would prefer sth like the default widows displaying, which makes all 3 items like one item:
my QT Table
vs
windows dragging n dropping folders
After eyllanesc's suggestion for QPixmap, I found the correct solution to my problem, so that I can keep the mime data coming from my model. I have re-implemented startDrag(Qt::DropActions supportedActions) in my QTreeView class, so that when multiple objects are moved, one icon will be displayed along with the number of items moved. Now looks like this:
void MyTreeView::startDrag(Qt::DropActions supportedActions)
{
QModelIndexList indexes = selectedIndexes();
if (indexes.size() == 1)
return QAbstractItemView::startDrag(supportedActions);
if (indexes.count() > 0)
{
QMimeData *data = model()->mimeData(indexes);
if (!data)
return;
QRect rect;
rect.adjust(horizontalOffset(), verticalOffset(), 0, 0);
QDrag *drag = new QDrag(this);
ActionTreeItem* pItem = static_cast<ActionTreeItem*>(indexes[0].internalPointer());
if (pItem != NULL)
{
QPixmap pixmap = myIcon.pixmap(myIcon.actualSize(QSize(32, 32)));
QPainter *paint = new QPainter(&pixmap);
paint->setPen(Qt::black);
paint->setBrush(QBrush(Qt::white));
QRect numberRect(18, 18, 13, 13);
paint->drawRect(numberRect);
paint->drawText(numberRect, Qt::AlignHCenter | Qt::AlignVCenter, QString("%1").arg(indexes.count()));
drag->setPixmap(pixmap);
}
drag->setMimeData(data);
Qt::DropAction defaultDropAction = Qt::MoveAction;
drag->exec(supportedActions, defaultDropAction);
}
}
Taking this tutorial as a reference, the mousePressEvent method is overwritten, and a new QPixmap is placed in QDrag:
void mousePressEvent(QMouseEvent *event){
if (event->button() == Qt::LeftButton){
QDrag *drag = new QDrag(this);
drag->setMimeData(new QMimeData());
drag->setPixmap(QPixmap("image.png"));
drag->exec();
}
QTableView::mousePressEvent(event);
}
Output:
In QtCreater I added a table to my project. in my code I am generating some data to output into the table. I want to add a QCheckbox into each row to allow the row to be selected. All the content of the table is aligned left, how do I make only these checkboxes in the first column of every row align to the center?
I'm adding the QCheckbox using:
ui->data_table->setCellWidget(rowCount,0, new QCheckBox);
Two thumbs up for Barry Mavin! You don't even have to subclass.
one line...
pCheckBox->setStyleSheet("margin-left:50%; margin-right:50%;");
done!!
I usually use a layout and a container widget for this. It is an ugly solution, but it works:
QWidget * w = new QWidget();
QHBoxLayout *l = new QHBoxLayout();
l->setAlignment( Qt::AlignCenter );
l->addWidget( <add your checkbox here> );
w->setLayout( l );
ui->data_table->setCellWidget(rowCount,0, w);
So basically you will have:
Table Cell -> Widget -> Layout -> Checkbox
you'll have to consider it if you will need to access the checkbox through the table.
This is an old post but in fact there is a much easier and lighter way of achieving this, just subclass QCheckBox and set the stylesheet to
margin-left:50%;
margin-right:50%;
It works for me, but my checkbox is not completely displayed.
To have a complete view of the widget, remove margins in layout :
l->setContentsMargins(0,0,0,0);
As stated in similar question around Stack Overflow, it's currently an open BUG:
https://bugreports.qt-project.org/browse/QTBUG-5368
can be center like this too using layout if want to add more customization
// Create a widget that will contain a checkbox
QWidget *checkBoxWidget = new QWidget();
QCheckBox *checkBox = new QCheckBox(); // We declare and initialize the checkbox
QHBoxLayout *layoutCheckBox = new QHBoxLayout(checkBoxWidget); // create a layer with reference to the widget
layoutCheckBox->addWidget(checkBox); // Set the checkbox in the layer
layoutCheckBox->setAlignment(Qt::AlignCenter); // Center the checkbox
layoutCheckBox->setContentsMargins(0,0,0,0); // Set the zero padding
ui->my_table_view->setCellWidget(row_number,column_number, checkBoxWidget); // set cell widget
OR simply add left right margins
checkBox->setStyleSheet("margin-left:50%; margin-right:50%;");
#if QT_VERSION < 0x046000
#include <QCommonStyle>
class MyStyle : public QCommonStyle {
public:
QRect subElementRect(SubElement subElement, const QStyleOption *option, const QWidget *widget = 0) const {
switch(subElement) {
case QStyle::SE_CheckBoxIndicator: {
QRect r = QCommonStyle::subElementRect(subElement, option, widget);
r.setRect( (widget->width() - r.width())/2, r.top(), r.width(), r.height());
return QRect(r);
}
default: return QCommonStyle::subElementRect(subElement, option, widget);
}
}
};
#else
#include <QProxyStyle>
#include <QStyleFactory>
class MyStyle: public QProxyStyle {
public:
MyStyle():QProxyStyle(QStyleFactory::create("Fusion")) {}
QRect subElementRect(SubElement subElement, const QStyleOption *option, const QWidget *widget = 0) const {
switch(subElement) {
case QStyle::SE_CheckBoxIndicator: {
QRect r = QProxyStyle::subElementRect(subElement, option, widget);
r.setRect( (widget->width() - r.width())/2, r.top(), r.width(), r.height());
return QRect(r);
}
default: return QProxyStyle::subElementRect(subElement, option, widget);
}
}
};
#endif
QCheckBox *box = new QCheckBox();
box->setStyle(new MyStyle());
I have a paint method in a delegate used in a QTableview where I add a checkbox indicator to a cell. Its very easy to set the mouseover state of the checkbox when I enter the cell by checking the option.state for the QStyle::State_MouseOver flag but what I ideally need to do is only set the mouseover state for the checkbox indicator when the mouse pointer is over the indicator itself and not just hovering around the cell. Unfortunately the paint method is only triggered when moving from one cell to the other at present so I need some pointers on how to do this.
Code is as follows (where mouse_pointer_ is the last stored mouse coordinates):
void
CheckBoxDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
// Draw our checkbox indicator
bool value = index.data(Qt::DisplayRole).toBool();
QStyleOptionButton checkbox_indicator;
// Set our button state to enabled
checkbox_indicator.state |= QStyle::State_Enabled;
checkbox_indicator.state |= (value) ? QStyle::State_On : QStyle::State_Off;
// Get our dimensions
checkbox_indicator.rect = QApplication::style()->subElementRect(QStyle::SE_CheckBoxIndicator, &checkbox_indicator, NULL );
// Position our indicator
const int x = option.rect.center().x() - checkbox_indicator.rect.width() / 2;
const int y = option.rect.center().y() - checkbox_indicator.rect.height() / 2;
checkbox_indicator.rect.moveTo(x, y);
if (checkbox_indicator.rect.contains(mouse_position_)) {
checkbox_indicator.state |= QStyle::State_MouseOver;
}
QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkbox_indicator, painter);
}
Any help would be much appreciated.
After some investigation I found that the issue was that my editorEvent() method needed to return true (indicating that the cell contents have changed) to force the repaint of the cell and thus set the selected state. Code is below:
bool CheckBoxDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
{
bool repaint_cell = false;
QMouseEvent *e = static_cast<QMouseEvent *> (event);
mouse_position_ = e->pos();
// We need to check if the mouse pointer is hovering within
// the checkbox indicator area of the table cell
QStyleOptionButton checkbox_indicator;
checkbox_indicator.rect = QApplication::style()->subElementRect( QStyle::SE_CheckBoxIndicator, &checkbox_indicator, NULL );
const int x = option.rect.center().x() - checkbox_indicator.rect.width() / 2;
const int y = option.rect.center().y() - checkbox_indicator.rect.height() / 2;
checkbox_indicator.rect.moveTo(x, y );
if (checkbox_indicator.rect.contains(mouse_position_)) {
repaint_cell = true;
// Check if the user has clicked in this area
if (e->button() == Qt::LeftButton) {
switch(event->type()) {
case QEvent::MouseButtonRelease:
// Update model data in here
break;
default:
break;
}
}
}
return repaint_cell;
}
more simple
bool hover = option.state & QStyle::State_MouseOver;
if (hover) {
painter->fillRect(r, QColor(0,0,0,10));
}
In QtCreater I added a table to my project. in my code I am generating some data to output into the table. I want to add a QCheckbox into each row to allow the row to be selected. All the content of the table is aligned left, how do I make only these checkboxes in the first column of every row align to the center?
I'm adding the QCheckbox using:
ui->data_table->setCellWidget(rowCount,0, new QCheckBox);
Two thumbs up for Barry Mavin! You don't even have to subclass.
one line...
pCheckBox->setStyleSheet("margin-left:50%; margin-right:50%;");
done!!
I usually use a layout and a container widget for this. It is an ugly solution, but it works:
QWidget * w = new QWidget();
QHBoxLayout *l = new QHBoxLayout();
l->setAlignment( Qt::AlignCenter );
l->addWidget( <add your checkbox here> );
w->setLayout( l );
ui->data_table->setCellWidget(rowCount,0, w);
So basically you will have:
Table Cell -> Widget -> Layout -> Checkbox
you'll have to consider it if you will need to access the checkbox through the table.
This is an old post but in fact there is a much easier and lighter way of achieving this, just subclass QCheckBox and set the stylesheet to
margin-left:50%;
margin-right:50%;
It works for me, but my checkbox is not completely displayed.
To have a complete view of the widget, remove margins in layout :
l->setContentsMargins(0,0,0,0);
As stated in similar question around Stack Overflow, it's currently an open BUG:
https://bugreports.qt-project.org/browse/QTBUG-5368
can be center like this too using layout if want to add more customization
// Create a widget that will contain a checkbox
QWidget *checkBoxWidget = new QWidget();
QCheckBox *checkBox = new QCheckBox(); // We declare and initialize the checkbox
QHBoxLayout *layoutCheckBox = new QHBoxLayout(checkBoxWidget); // create a layer with reference to the widget
layoutCheckBox->addWidget(checkBox); // Set the checkbox in the layer
layoutCheckBox->setAlignment(Qt::AlignCenter); // Center the checkbox
layoutCheckBox->setContentsMargins(0,0,0,0); // Set the zero padding
ui->my_table_view->setCellWidget(row_number,column_number, checkBoxWidget); // set cell widget
OR simply add left right margins
checkBox->setStyleSheet("margin-left:50%; margin-right:50%;");
#if QT_VERSION < 0x046000
#include <QCommonStyle>
class MyStyle : public QCommonStyle {
public:
QRect subElementRect(SubElement subElement, const QStyleOption *option, const QWidget *widget = 0) const {
switch(subElement) {
case QStyle::SE_CheckBoxIndicator: {
QRect r = QCommonStyle::subElementRect(subElement, option, widget);
r.setRect( (widget->width() - r.width())/2, r.top(), r.width(), r.height());
return QRect(r);
}
default: return QCommonStyle::subElementRect(subElement, option, widget);
}
}
};
#else
#include <QProxyStyle>
#include <QStyleFactory>
class MyStyle: public QProxyStyle {
public:
MyStyle():QProxyStyle(QStyleFactory::create("Fusion")) {}
QRect subElementRect(SubElement subElement, const QStyleOption *option, const QWidget *widget = 0) const {
switch(subElement) {
case QStyle::SE_CheckBoxIndicator: {
QRect r = QProxyStyle::subElementRect(subElement, option, widget);
r.setRect( (widget->width() - r.width())/2, r.top(), r.width(), r.height());
return QRect(r);
}
default: return QProxyStyle::subElementRect(subElement, option, widget);
}
}
};
#endif
QCheckBox *box = new QCheckBox();
box->setStyle(new MyStyle());
I have a QTableView which I am setting a custom QStyledItemDelegate on.
In addition to the custom item painting, I want to style the row's background color for the selection/hovered states. The look I am going for is something like this KGet screenshot:
KGet's Row Background http://www.binaryelysium.com/images/kget_background.jpeg
Here is my code:
void MyDelegate::paint( QPainter* painter, const QStyleOptionViewItem& opt, const QModelIndex& index ) const
{
QBrush backBrush;
QColor foreColor;
bool hover = false;
if ( opt.state & QStyle::State_MouseOver )
{
backBrush = opt.palette.color( QPalette::Highlight ).light( 115 );
foreColor = opt.palette.color( QPalette::HighlightedText );
hover = true;
}
QStyleOptionViewItemV4 option(opt);
initStyleOption(&option, index);
painter->save();
const QStyle *style = option.widget ? option.widget->style() : QApplication::style();
const QWidget* widget = option.widget;
if( hover )
{
option.backgroundBrush = backBrush;
}
painter->save();
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, widget);
painter->restore();
switch( index.column() )
{
case 0: // we want default behavior
style->drawControl(QStyle::CE_ItemViewItem, &option, painter, widget);
break;
case 1:
// some custom drawText
break;
case 2:
// draw a QStyleOptionProgressBar
break;
}
painter->restore();
}
The result is that each individual cell receives the mousedover background only when the mouse is over it, and not the entire row. It is hard to describe so here is a screenshot:
The result of the above code http://www.binaryelysium.com/images/loader_bg.jpeg
In that picture the mouse was over the left most cell, hence the highlighted background.. but I want the background to be drawn over the entire row.
How can I achieve this?
Edit: With some more thought I've realized that the QStyle::State_MouseOver state is only being passed for actual cell which the mouse is over, and when the paint method is called for the other cells in the row QStyle::State_MouseOver is not set.
So the question becomes is there a QStyle::State_MouseOver_Row state (answer: no), so how do I go about achieving that?
You need to be telling the view to update its cells when the mouse is over a row, so I would suggest tracking that in your model. Then in the paint event, you can ask for that data from the model index using a custom data role.
void TrackDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem viewOption(option);
if (viewOption.state & QStyle::State_HasFocus)
viewOption.state = viewOption.state ^ QStyle::State_HasFocus;
QImage image(m_RowBackGroundImagePath);
QPixmap pixmap(m_RowBackGroundImagePath);
qDebug()<<"forward"<<pixmap.width()<<pixmap.height();
pixmap.scaled(option.rect.width(),option.rect.height());
qDebug()<<"back"<<pixmap.width()<<pixmap.height();
qDebug()<<option.rect.width()<<option.rect.height();
QBrush brush(pixmap);
painter->save();
painter->fillRect(option.rect, brush/*QColor(238, 233, 233, 255)*/);
painter->restore();
viewOption.rect = QRect(option.rect.x(), option.rect.y(), option.rect.width(), option.rect.height());
//viewOption.palette.setColor(QPalette::Text, QColor(Qt::red));
//viewOption.palette.setBrush ( QPalette::ButtonText, brush1);
QItemDelegate::paint(painter, viewOption,index);
int progress = index.model()->data(index,Qt::DisplayRole).toInt();
QStyleOptionProgressBar progressBarOption;
progressBarOption.rect = QRect(option.rect.x(), option.rect.y()+(SETHEIGHT - PROGRESSBARHEIGHT)/2, option.rect.width(), /*option.rect.height()*/PROGRESSBARHEIGHT);
//qDebug()<<progressBarOption.rect.x()<<progressBarOption.rect.y()<<progressBarOption.rect.height()<<progressBarOption.rect.width();
//qDebug()<<option.rect.x()<<option.rect.y()<<option.rect.height()<<option.rect.width();
progressBarOption.state |= QStyle::State_Enabled;
progressBarOption.direction = QApplication::layoutDirection();
progressBarOption.fontMetrics = QApplication::fontMetrics();
progressBarOption.minimum = 0;
progressBarOption.maximum = 100;
progressBarOption.textAlignment = Qt::AlignCenter;
progressBarOption.textVisible = true;
progressBarOption.progress = progress < 0 ? 0 : progress;
progressBarOption.text = QString().sprintf("%d%%", progressBarOption.progress);
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter);
break;
}