Pixelator也是个自定义委托的例子,这是个挺有意思的例子,好好看看!
这个例子展示了怎么用自定义委托去修改标准视图外观。为了完成这个任务,我们需要实现下面的组件。
1. 一个在图片做为数据项的情况下表示每个像素的模型,在这模型里,每个项都包含一个亮度值一致的像素。
2. 一个自定义委托,它使用被模型提供的信息去表示每个作为白底黑圆的像素,这些圆的半径和它像素点的黑度一致。
意思就是1,我们要实现一个自定义的模型,模型里的项是亮度一致的像素。2,自定义一个委托用使用自定义的模型提供的像素项,让黑色的半径来表示像素点的灰度值。程序截图如下:
程序包含三个主要类,MainWindow主窗口,ImageModel,PixelDelegate。
int main(int argc, char *argv[]) { Q_INIT_RESOURCE(images); QApplication app(argc, argv); MainWindow window; #if defined(Q_OS_SYMBIAN) window.showMaximized(); #else window.show(); #endif window.openImage(":/images/qt.png"); // 初始化图 return app.exec(); }
class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(); void openImage(const QString &fileName); public slots: void chooseImage(); // 选择图片槽 void printImage(); // 打印图片槽 void showAboutBox(); // 显示aboutBox槽 void updateView(); // 更新视图槽 private: ImageModel *model; // 模型指针 QAction *printAction; // 打印动作指针 QString currentPath; // 打开选择图片时用的路径 QTableView *view; // 主窗口的表视图指针 };
//! [0] MainWindow::MainWindow() { //! [0] currentPath = QDir::homePath(); // home路径,就是用户文件夹位置 model = new ImageModel(this); // 创建ImageModel自定义模型 QWidget *centralWidget = new QWidget; // MainWindow中心的主部件,将其他部件都放在它里面 //! [1] view = new QTableView; // 创建表视图 view->setShowGrid(false); // 不显示表的格子 view->horizontalHeader()->hide(); // 隐藏横向表头 view->verticalHeader()->hide(); // 隐藏纵向表头 view->horizontalHeader()->setMinimumSectionSize(1); // 横向表头最小值 view->verticalHeader()->setMinimumSectionSize(1); // 纵向表头最小值 view->setModel(model); // 为表视图设置模型:ImageModel的实例 //! [1] //! [2] PixelDelegate *delegate = new PixelDelegate(this); // 实例化自定义的委托 view->setItemDelegate(delegate); // 为表视图设置委托 //! [2] //! [3] // 表视图下方的label和spinBox QLabel *pixelSizeLabel = new QLabel(tr("Pixel size:")); QSpinBox *pixelSizeSpinBox = new QSpinBox; pixelSizeSpinBox->setMinimum(4); // 最小最大和初始值 pixelSizeSpinBox->setMaximum(32); pixelSizeSpinBox->setValue(12); //! [3] // 创建菜单了 // 文件菜单和打开文件 QMenu *fileMenu = new QMenu(tr("&File"), this); QAction *openAction = fileMenu->addAction(tr("&Open...")); openAction->setShortcuts(QKeySequence::Open); // 打印 printAction = fileMenu->addAction(tr("&Print...")); printAction->setEnabled(false); printAction->setShortcut(QKeySequence::Print); // 退出 QAction *quitAction = fileMenu->addAction(tr("E&xit")); quitAction->setShortcuts(QKeySequence::Quit); // help菜单和about QMenu *helpMenu = new QMenu(tr("&Help"), this); QAction *aboutAction = helpMenu->addAction(tr("&About")); menuBar()->addMenu(fileMenu); menuBar()->addSeparator(); menuBar()->addMenu(helpMenu); // 连接菜单的各项信号槽 connect(openAction, SIGNAL(triggered()), this, SLOT(chooseImage())); connect(printAction, SIGNAL(triggered()), this, SLOT(printImage())); connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); connect(aboutAction, SIGNAL(triggered()), this, SLOT(showAboutBox())); //! [4] // spinBox的值改变必须和委托里setPixlSize、窗体显示的图里保持同步 connect(pixelSizeSpinBox, SIGNAL(valueChanged(int)), delegate, SLOT(setPixelSize(int))); connect(pixelSizeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateView())); //! [4] // 可以开始布局显示了 QHBoxLayout *controlsLayout = new QHBoxLayout; controlsLayout->addWidget(pixelSizeLabel); controlsLayout->addWidget(pixelSizeSpinBox); controlsLayout->addStretch(1); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(view); mainLayout->addLayout(controlsLayout); centralWidget->setLayout(mainLayout); setCentralWidget(centralWidget); // 将这个中心部件设置到MainWindow的中心 setWindowTitle(tr("Pixelator")); resize(640, 480); //! [5] } //! [5] // 菜单打开时触发这个槽:选择图片 void MainWindow::chooseImage() { // 用OPEN对话框获取图片 QString fileName = QFileDialog::getOpenFileName(this, tr("Choose an image"), currentPath, "*"); if (!fileName.isEmpty()) openImage(fileName); // 将图片打开 } // 将图片打开 void MainWindow::openImage(const QString &fileName) { QImage image; if (image.load(fileName)) { model->setImage(image); // 为模型载入图 if (!fileName.startsWith(":/")) { // 修改标题 currentPath = fileName; setWindowTitle(tr("%1 - Pixelator").arg(currentPath)); } printAction->setEnabled(true); // 打印设置可用 updateView(); // 更新视图 } } void MainWindow::printImage() { #ifndef QT_NO_PRINTER // 可打印情况 if (model->rowCount(QModelIndex())*model->columnCount(QModelIndex()) // 大图片 > 90000) { QMessageBox::StandardButton answer; answer = QMessageBox::question(this, tr("Large Image Size"), tr("The printed image may be very large. Are you sure that " "you want to print it?"), QMessageBox::Yes | QMessageBox::No); if (answer == QMessageBox::No) // 放弃打印,返回函数 return; } QPrinter printer(QPrinter::HighResolution); QPrintDialog *dlg = new QPrintDialog(&printer, this); // 打印对话框 dlg->setWindowTitle(tr("Print Image")); if (dlg->exec() != QDialog::Accepted) // 放弃打印,返回函数 return; QPainter painter; painter.begin(&printer); // 获取打印 int rows = model->rowCount(QModelIndex()); int columns = model->columnCount(QModelIndex()); int sourceWidth = (columns+1) * ItemSize; int sourceHeight = (rows+1) * ItemSize; painter.save(); // 保存状态 double xscale = printer.pageRect().width()/double(sourceWidth); double yscale = printer.pageRect().height()/double(sourceHeight); double scale = qMin(xscale, yscale); // 调整painter位置适应打印页面 painter.translate(printer.paperRect().x() + printer.pageRect().width()/2, printer.paperRect().y() + printer.pageRect().height()/2); painter.scale(scale, scale); // 适应页面的缩放 painter.translate(-sourceWidth/2, -sourceHeight/2); QStyleOptionViewItem option; QModelIndex parent = QModelIndex(); QProgressDialog progress(tr("Printing..."), tr("Cancel"), 0, rows, this); // 展示进度 progress.setWindowModality(Qt::ApplicationModal); // 设置成模态对话框 float y = ItemSize/2; for (int row = 0; row < rows; ++row) { // 按行数来标记进度 progress.setValue(row); qApp->processEvents(); // 应用程序响应进程事件 if (progress.wasCanceled()) break; float x = ItemSize/2; for (int column = 0; column < columns; ++column) { option.rect = QRect(int(x), int(y), ItemSize, ItemSize); view->itemDelegate()->paint(&painter, option, // 打印绘制 model->index(row, column, parent)); x = x + ItemSize; } y = y + ItemSize; } progress.setValue(rows); painter.restore(); // 恢复先前保存状态 painter.end(); if (progress.wasCanceled()) { // 进度被取消 QMessageBox::information(this, tr("Printing canceled"), tr("The printing process was canceled."), QMessageBox::Cancel); } #else // 不可打印情况:弹出对话框完事 QMessageBox::information(this, tr("Printing canceled"), tr("Printing is not supported on this Qt build"), QMessageBox::Cancel); #endif } // 显示about对话框 void MainWindow::showAboutBox() { QMessageBox::about(this, tr("About the Pixelator example"), tr("This example demonstrates how a standard view and a custom\n" "delegate can be used to produce a specialized representation\n" "of data in a simple custom model.")); } //! [6] // 更新视图 void MainWindow::updateView() { view->resizeColumnsToContents(); // 为内容调整列和行的size view->resizeRowsToContents(); } //! [6]
//! [0] // ImageModel继承QAbstractTableModel // 因为图片矩阵和Table最接近 // rowCount,columnCount,data,headerData都需要被使用到,需要重实现 class ImageModel : public QAbstractTableModel { Q_OBJECT public: ImageModel(QObject *parent = 0); void setImage(const QImage &image); // 设置图片,将图片载入 int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; private: QImage modelImage; }; //! [0]
//! [0] ImageModel::ImageModel(QObject *parent) : QAbstractTableModel(parent) { } //! [0] //! [1] void ImageModel::setImage(const QImage &image) { modelImage = image; reset(); // 模型改变,需要重新设置模型 } //! [1] //! [2] int ImageModel::rowCount(const QModelIndex & /* parent */) const { return modelImage.height(); // 返回模型图片的高 } int ImageModel::columnCount(const QModelIndex & /* parent */) const //! [2] //! [3] { return modelImage.width(); // 返回模型图片的宽 } //! [3] //! [4] // 返回数据:索引下的像素灰度值,这个灰度用qGray函数 QVariant ImageModel::data(const QModelIndex &index, int role) const { // 这里的角色是DisplayRole if (!index.isValid() || role != Qt::DisplayRole) return QVariant(); return qGray(modelImage.pixel(index.column(), index.row())); } //! [4] //! [5] // 委托会要获取该值 QVariant ImageModel::headerData(int /* section */, Qt::Orientation /* orientation */, int role) const { if (role == Qt::SizeHintRole) return QSize(1, 1); return QVariant(); } //! [5]
static const int ItemSize = 256; //! [0] // PixelDelegate继承QAbstractItemDelegate // 需要支持绘制项,就要重新实现paint // 需要重新实现sizeHint class PixelDelegate : public QAbstractItemDelegate { Q_OBJECT public: PixelDelegate(QObject *parent = 0); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index ) const; public slots: void setPixelSize(int size); // 设置显示点的大小 private: int pixelSize; }; //! [0]
//! [0] PixelDelegate::PixelDelegate(QObject *parent) : QAbstractItemDelegate(parent) { pixelSize = 12; // 保持和spinBox中的值初始化相同 } //! [0] //! [1] // paint的任务就是要画pixelSize大小的黑圆,作为项 void PixelDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { //! [2] if (option.state & QStyle::State_Selected) // 项被选中 painter->fillRect(option.rect, option.palette.highlight()); //! [1] //! [3] int size = qMin(option.rect.width(), option.rect.height()); // 取更新中高和宽的较小者作为半径 //! [3] //! [4] int brightness = index.model()->data(index, Qt::DisplayRole).toInt(); // 取模型的值 double radius = (size/2.0) - (brightness/255.0 * size/2.0); // 灰度比值对应的半径大小 if (radius == 0.0) return; //! [4] //! [5] painter->save(); // 保存当前painter的状态便于恢复 //! [5] //! [6] painter->setRenderHint(QPainter::Antialiasing, true); // 反走样 //! [6] //! [7] painter->setPen(Qt::NoPen); // 设置笔 //! [7] //! [8] if (option.state & QStyle::State_Selected) // 如果被选中 //! [8] //! [9] painter->setBrush(option.palette.highlightedText()); // 设置绘制高亮画刷 else //! [2] painter->setBrush(option.palette.text()); // 否则设置绘制黑点画刷 //! [9] //! [10] // 绘制 painter->drawEllipse(QRectF(option.rect.x() + option.rect.width()/2 - radius, option.rect.y() + option.rect.height()/2 - radius, 2*radius, 2*radius)); painter->restore(); // painter恢复先前状态 } //! [10] //! [11] // 返回项的大小 QSize PixelDelegate::sizeHint(const QStyleOptionViewItem & /* option */, const QModelIndex & /* index */) const { return QSize(pixelSize, pixelSize); } //! [11] //! [12] // 设置项的大小,作为spinBox的changevalue响应槽 void PixelDelegate::setPixelSize(int size) { pixelSize = size; } //! [12]