第二部分 Qt中级
第6章 布局管理
分组布局
QStackedLayout 对一组子窗口部件进行摆放,或对它们进行分页。
QStackedWidget 内置QStackedLayout的QWidget
listWidget = new QListWidget;
listWidget->addItem(tr("Appearance"));
listWidget->addItem(tr("Web Browser"));
stackedLayout = new QStackedLayout;
stackedLayout->addWidget(appearancePage);
stackedLayout->addWidget(webBrowerPage);
connect(listWidget, SIGNAL(currentRowChanged(int)),
stackedLayout, SLOT(setCurrentIndex(int)));
QTabWidget
切分窗口 QSplitter
QTextEdit* editor1 = new QTextEdit;
QTextEdit* editor2 = new QTextEdit;
QTextEdit* editor3 = new QTextEdit;
QSplitter splitter(Qt::Horizontal);
splitter.addWidget(editor1);
splitter.addWidget(editor2);
splitter.addWidget(editor3);
splitter.show();
滚动区域 QScrollArea
如果想给一个窗口部件添加一个滚动条,则可以使用一个QScrollArea类来实现。
IconEditor* iconEditor = new IconEditor; // 一个自定义窗口部件
iconEditor->setIconImage(QImage(":/images/mouse.png"));
QScrollArea scrollArea;
scrollArea.setWidget(iconEditor);
scrollArea.viewport()->setBackgroundRole(QPalette::Dark);
scrollArea.viewport()->setAutoFillBackground(true);
scrollArea.show();
// 使滚动条总是可见
scrollArea.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
停靠窗口
指一些可以停靠在QMainWindow中或者浮动为独立窗口的窗口。
Qt中,各个停靠窗口都是QDockWidget的实例。
如果需要一个浮动工具栏,只需把它放进QDockWidget
如果需要让停靠区重叠部分左上角属于左侧停靠区域:
QMainWindow::setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
QDockWidget* shapesDockWidget = new QDockWidget(tr("Shapes"));
// objectname 用于调试、QMainWindow::saveState()
shapesDockWidget->setObjectName("shapesDockWidget");
shapesDockWidget->setWidget(treeWidget);
shapesDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea |
Qt::RightDockWidgetArea);
addDockWidget(Qt::RightDockWidgetArea, shapesDockWidget);
QMainWindow::saveState();
QMainWindow::saveGeometry();
多文档界面
Qt中,通过把QMdiArea类作为中央窗口部件,并且通过让每一个文档窗口都成为这个QMdiArea的子窗口部件,就可以创建一个多文档界面应用程序了。
第7章 事件处理
// 事件循环一空闲就会触发
QTimer::singleShot(0, this, SLOT(loadFiles));
QMdiSubWindow* subWindow = mdiArea->addSubWindow(editor);
代码编辑组件 QScintilla
www.riverbankcomputing.co.uk/qscintilla
定时器事件可以用来实现光标的闪烁和其他动画的播放,或只是简单地用作显示的刷新。
updateGeometry()通知对窗口部件负责的任意布局管理器,提示该窗口部件的大小发生了变化。
// 每30毫秒Qt都会产生一个定时器事件
myTimerId = startTimer(30); // QObject::startTimer()
killTimer(myTimerId);
void Ticker::timerEvent(QTimerEvent& event)
{
if (event->timerId() == myTimerId)
// ...
}
QWidget::scroll(-1, 0)把窗口部件的内容向左滚动一个像素
定时器事件是一种低级事件,如果需要多个定时器,保持并分鬃所有ID会很麻烦
更为简单的方式是,为每一个定时器分别创建一个QTimer对象。QTimer会在每个时间间隔发射timeout()信号。
Qt调用QApplication::notify()来发送一个事件。
对于耗时多的事件为了避免界面无法响应:
1. 多线程
2. 频繁调用QApplication::processEvents()
为避免在处理任务时关闭窗口或执行新任务:
qApp->processEvents(QEventLoop::ExcludeUserInputEvents); // 忽略键盘鼠标事件
QProcessDialog
// 没有调用show(),因为进度对话框会自动调用它
QProcessDialog progress(this);
progress.setLabelText(tr("Saving %1").arg(filename));
progress.setRange(0, rowCount);
progress.setModal(true);
progress.setValue(row);
qApp->processEvents();
if (progress.wasCanceled())
// ...
0毫秒的定时器,只要没有其他尚待处理的事件,就可以触发这个定时器。
qApp->hasPendingEvents()
第8章 二维图形 QPainter
重写QWidget::paintEvent()可用于定制窗口部件,并且随心所欲地控制它们的外观。
void MyWidget::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
// ...
}
QPen 画笔: 颜色 宽度 线型 拐点风格 连接风格
QBrush 画刷: 颜色 风格 纹理 渐变
QFont 字体: 字体族 磅值
drawPoint()
drawLine()
drawPolyline()
drawPoints()
drawLines()
drawPolygon()
drawRect()
drawRoundRect()
drawEllipse()
drawArc()
drawChord()
drawPie()
drawText()
drawPixmap()
drawPatch()
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(QPen(Qt::black, 12, Qt::DashDotLine, Qt::RoundCap));
painter.setBrush(QBrush(Qt::green, Qt::SolidPattern);
painter.drawEllipse(80, 80, 400, 240);
save() 将设备的当前状态存入一个内存堆栈
restore() 恢复设备状态
坐标系统
假设一个320x200的窗口部件
// 逻辑坐标 (-50, -50) 对应物理坐标 (0, 0)
// 逻辑坐标 (100, 100) 对应物理坐标 (0, 0)
painter.setWindow(-50, -50, 100, 100);
世界变换
QTransform transform;
transform.rotate(45);
painter.setWorldTransform(transform);
painter.translate()
painter.rotate()
QDateTime::currentDateTime()
qBound(min, val, max)
QPainter实例 微波炉定时开关
void OvenTimer::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
int side = qMin(width(), height());
painter.setViewPort((width() - side) / 2, (height() - side) / 2,
side, side);
painter.setWindow(-50, -50, 100, 100);
draw(&painter);
}
void OvenTimer::draw(QPainter* painter)
{
static const int triangle[3][2] = {
{-2, -49}, {2, -49}, {0, -47}};
QPen thickPen(palette().foreground(), 1.5);
QPen thinPen(palette().foreground(), 0.5);
QColor niceBlue(150, 150, 200);
// 上边的小三角形
painter->setPen(thinPen);
painter->setBrush(palette().foreground());
painter->drawPolygon(QPolygon(3, &triangle[0][0]));
// 刻度盘背景
QConicalGradient coneGradient(0, 0, -90); // 圆锥形渐变
coneGradient.setColorAt(0.0, Qt::darkGray);
coneGradient.setColorAt(0.2, niceBlue);
coneGradient.setColorAt(0.5, Qt::white);
coneGradient.setColorAt(1.0, Qt::darkGray);
painter->setBrush(coneGradient);
painter->drawEllipse(-46, -46, 92, 92);
// 内部小圆,旋钮背景
QRadialGradient haloGradient(0, 0, 20, 0, 0)
haloGradient.setColorAt(0.0, Qt::lightGray);
haloGradient.setColorAt(0.8, Qt::darkGray();
haloGradient.setColorAt(0.9, Qt::white);
haloGradient.setColorAt(1.0, Qt::black);
painter->setPen(Qt::NoPen);
painter->setBrush(haloGradient);
painter->drawEllipse(-20, -20, 40, 40);
// 旋钮把手
QLinearGradient knobGradient(-7, -25, 7, -25);
knobGradient.setColorAt(0.0, Qt::black);
knobGradient.setColorAt(0.2, niceBlue);
knobGradient.setColorAt(0.3, Qt::lightGray);
knobGradient.setColorAt(0.8, Qt::white);
knobGradient.setColorAt(1.0, Qt::black);
painter->rotate(duration() * DegreesPerSecond);
painter->setBrush(knobGradient);
painter->setPen(thinPen);
painter->drawRoundRect(-7, -25, 14, 50, 99, 49);
// 刻度线和刻度文字
for (int i = 0; i <= MaxMinutes; ++i)
{
if (i % 5 == 0)
{
painter->setPen(thickPen);
painter->drawLine(0, -41, 0, -44);
painter->drawText(-15, -41, 30, 30,
Qt::AlignHCenter | Qt::AlignTop, QString::number(i));
}
else
{
painter->setPen(thinPen);
painter->drawLine(0, -42, 0, -44);
}
painter->rotate(-DegreesPerMinute);
}
}
QImage
在QWidget或QPixmap上绘图,需要依赖于平台自带的绘图引擎
画到一个QImage上,然后把结果复制到屏幕上。这样可以总是使用Qt自己内置的绘图引擎,在所有平台上得到同样的结果。
void MyWidget::paintEvent(QPaintEvent* event)
{
// RGB通道预乘以alpha值
QImage image(size(), QImage::Format_ARGB32_Premultiplied);
QPainter imagePainter(&image);
imagePainter.initFrom(this);
imagePainter.setRenderHint(QPainter::Antialiasing, true);
imagePainter.eraseRect(rect());
draw(&imagePainter);
imagePainter.end();
QPainter widgetPainter(this);
widgetPainter.drawImage(0, 0, image);
}
// 默认的复合模式为 QImage::CompositionMode_SourceOver
painter.setCompositionMode(...);
QGraphicsScene QGraphicsItem QGraphicsView
预定义的QGraphicsItem子类:
QGraphicsLineItem
QGraphicsRectItem
QGraphicsEllipseItem
QGraphicsPolygonItem
QGraphicsPathItem
QGraphicsPixmapItem
QGraphicsSvgItem
QGraphicsSimpleTextItem
QGraphicsTextItem
QGraphicsScene 有三层:
background layer
item layer
foreground layer
背景层和前景层由QBrush指定或重新实现drawBackground() drawForeground()
QGraphicsView 是一个窗口部件,这个窗口部件可以显示场景,在需要的情况下提供滚动条
class Node 中
Q_DECLARE_TR_FUNCTIONS(Node)
这种方法可以直接使用tr(),而不是静态的QObject::tr()或者QCoreApplication::translate()
当创建QGraphicsItem的子类时,要想自己实现绘图,一般是重新实现boundingRect()和paint()
如果不重新实现shape()基类的实现将会退而使用boundingRect()
视图体系用外接矩形来决定一个项是否需要被绘制
shape用来决定一个点是否在项内,或者是否两个项是重合的
shape用于精确的碰撞检查
update()安排一次重绘
计算包围文字的矩形
QRectF Node::outlineRect() const
{
const int padding = 8;
QFontMetricsF metrics = qApp->font();
QRectF rect = metrics.boundingRect(myText);
rect.adjust(-padding, -padding, padding, padding);
rect.translate(-rect.center()); // 使原点位于中心
return rect;
}
scene = new QGraphicsScene(0, 0, 600, 500);
view = new QGraphicsView;
view->setScene(scene);
// 可以通过圈选选中
view->setDragMode(QGrahicsView::RubberBandDrag);
scene->addItem(...);
scene->clearSelection();
node->setSelected(true);
node->setZValue(...);
剪贴板
QApplication::clipboard()->setText(str);
QStyleOptionGraphicsItem::levelOfDetail
缩放因子
1.0 原始大小显示场景
0.5 以原始大小的一半显示场景
2.5 显示的大小是原始尺寸的2.5倍
// 不随场景缩放
setFlag(ItemIgnoresTransformations, true);
缩放因子
QGraphicsView::scale()
在希望显示动画的项上使用QGraphicsItemAnimations
使用QTimeLine播放动画
也可以通过继承QObject(应用多继承)创建图形项的子类,重新实现QObject::timeEvent()显示动画
打印
1. 创建一个当作绘制设备的QPrinter
2. 弹出一个QPrintDialog对话框,以允许用户选择打印机并且设置一些选项
3. 创建一个在QPrinter上操作的QPainter
4. 调用QPainter::newPage()来进行下一页的绘制
5. 重复步骤4和步骤5,直到所有页都被打印为止
生成PDF文件
QPrinter::setOutputFormat(QPrinter::PdfFormat)
void PrintWindow::printImage(const QImage& image)
{
QPrintDialog printDialog(&printer, this);
if (printDialog.exec())
{
QPainter painter(&printer);
QRect rect = painter.viewport();
QSize size = image.size();
size.scale(rect.size(), Qt::KeepAspectRatio);
painter.setViewPort(rect.x(), rect.y(), size.width(), size.height());
painter.setWindow(image.rect());
painter.drawImage(0, 0, image);
}
}
打印场景
QGraphicsScene::render()
QGraphicsView::render()
Qt::escape()把特殊字符 & < > 替换为相应的HTML项
& < & >
// 转换文档为HTML并使用QTextDocument打印它,是目前为止打印报告和其他一些复杂文档的最方便的选择
// 利用HTML自动处理分页
QTextDocument textDocument;
textDocument.setHtml(html);
textDocument.print(&printer);
QRect titleRect = painter->boundingRect(0, 0, textWidth, maxHeight,
Qt::TextWordWrap, title);
第9章 拖放
QWidget::dragEnterEvent()
QWidget::dropEvent()
QWidget::setAcceptDrops(true);
void MyWidget::dragEnterEvent(QDragEnterEvent* event)
{
if (event->mimeData()->hasFormat("text/uri-list"))
{
// 允许拖放,Qt会自动改变光标
event->acceptProposedAction();
}
}
void MyWidget::dropEvent(QDropEvent* event)
{
QList<QUrl> urls = event->mimeData()->urls();
if (urls.isEmpty())
return;
QString fileName = urls.first().toLocalFile();
if (fileName.isEmpty())
return;
}
// 推荐的拖动距离起始距离(通常是4个像素)
QApplication::startDragDistance()
开始一个拖动
QMimeData* mimeData = new QMimeData;
mimeData->setText(item->text());
QDrag* drag = new QDrag(this);
drag->setMimeData(mimeData);
// 在拖放发生时使图标随光标移动
drag->setPixmap(QPixmap(":/images/person.png"));
// 如果没有执行任何动作返回Qt::IgnoreAction
if (drag->exec(Qt::MoveAction) == Qt::MoveAction)
// ...
响应dragEnter和dragMove代码相同
void MyWidget::dragEnterEvent(QDragEnterEvent* event)
{
MyWidget* source = qobject_cast<MyWidget*>(event->source());
if (source && source != this)
{
event->setDropAction(Qt::MoveAction);
event->accept();
}
}
响应dragDrop
void MyWidget::dropEvent(QDropEvent* event)
{
MyWidget* source = qobject_cast<MyWidget*>(event->source());
if (source && source != this)
{
// event->mimeData()->text()
event->setDropAction(Qt::MoveAction);
event->accept();
}
}
QMimeData成员函数支持纯文本、超文本、图像、URL、颜色
自定义拖动类型
方式1:
mimeData->setData("text/csv", toCsv(plainText).toUtf8());
Qt::escape() 转义HTML中的特殊字符
if (event->mimeData()->hasFormat("text/csv"))
{
QByteArray cvsData = event->mimeData()->data("text/csv");
QString csvText = QString::fromUtf8(csvData);
event->acceptProposedAction();
}
方式2:
继承自QMimeData
构造函数中:
// QStringList myFormats
myFormats << "text/csv" << "text/html";
QStringList MyMimeData::formats() const
{
return myFormats;
}
QVariant MyMimeData::retrieveData(const QString& format,
QVariant::Type preferredType) const
{
if (format == "text/csv")
return toCsv(...);
else if (format == "text/html")
return toHtml(...);
QMimeData::retrieveData(format, preferredType);
}
响应dragDrop时,强制转换为MyMimeData,如果成功,说明是同一个程序,获得MyMimeData中保存的Widget,直接读取数据。而不必经过QMimeData的应用程序接口。
MIME类型指定编码方式
text/plain;charset=UTF-8
剪贴板处理技术
QApplication::clipboard()返回QClipboard对象的指针
将数据放到剪贴板中
setText() setImage() setPixmap()
获取数据
text() image() pixmap()
自定义类型
setMimeData() mimeData()
如果想在剪贴板中的内容发生改变时就立即得到通报:
建立QClipboard::dataChanged()信号和自定义槽的连接即可。