《C++ GUI Qt 4 編程》 筆記(二)

博客搬家自 http://zhouyuanchao.com/wordpress/archives/66


第二部分 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項
&amp; &lt; & &gt;

// 轉換文檔爲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()信號和自定義槽的連接即可。


發佈了34 篇原創文章 · 獲贊 21 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章