(1)塗鴉板
上面一節我們深入分析了一下Qt的座標系統,這一節我們在前面程序的基礎上稍加改動,設計一個塗鴉板程序。
簡單的塗鴉板:
1.我們再在程序中添加函數。
我們在dialog.h裏的public中再添加鼠標移動事件和鼠標釋放事件的函數聲明:
void mouseMoveEvent(QMouseEvent *);
void mouseReleaseEvent(QMouseEvent *);
在private中添加變量聲明:
QPixmap pix;
QPoint lastPoint;
QPoint endPoint;
因爲在函數裏聲明的QPixmap類對象是臨時變量,不能存儲以前的值,所以爲了實現保留上次的繪畫結果,我們需要將其設爲全局變量。
後兩個QPoint變量存儲鼠標指針的兩個座標值,我們需要用這兩個座標值完成繪圖。
2.在dialog.cpp中進行修改。
在構造函數裏進行變量初始化。
resize(600,500); //窗口大小設置爲600*500
pix = QPixmap(200,200);
pix.fill(Qt::white);
然後進行其他幾個函數的定義:
void Dialog::paintEvent(QPaintEvent *)
{
QPainter pp(&pix);
pp.drawLine(lastPoint,endPoint); //根據鼠標指針前後兩個位置就行繪製直線
lastPoint = endPoint; //讓前一個座標值等於後一個座標值,這樣就能實現畫出連續的線
QPainter painter(this);
painter.drawPixmap(0,0,pix);
}
void Dialog::mousePressEvent(QMouseEvent *event)
{
if(event->button()==Qt::LeftButton) //鼠標左鍵按下
lastPoint = event->pos();
}
void Dialog::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons()&Qt::LeftButton) //鼠標左鍵按下的同時移動鼠標
{
endPoint = event->pos();
update();
}
}
void Dialog::mouseReleaseEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton) //鼠標左鍵釋放
{
endPoint = event->pos();
update();
}
}
這裏的update()函數,是進行界面重繪,執行該函數時就會執行那個重繪事件函數。
3.這時運行程序,效果如下。(點擊圖片可將其放大)
這樣簡單的塗鴉板程序就完成了。下面我們進行放大後的塗鴉。
放大後再進行塗鴉:
1.添加放大按鈕。
在dialog.h中添加頭文件聲明: #include <QPushButton>
在private中添加變量聲明:
int scale;
QPushButton *pushBtn;
然後再在下面寫上按鈕的槽函數聲明:
private slots:
void zoomIn();
2.在dialog.cpp中進行更改。
在構造函數裏添加如下代碼:
scale =1; //設置初始放大倍數爲1,即不放大
pushBtn = new QPushButton(this); //新建按鈕對象
pushBtn->setText(tr("zoomIn")); //設置按鈕顯示文本
pushBtn->move(500,450); //設置按鈕放置位置
connect(pushBtn,SIGNAL(clicked()),this,SLOT(zoomIn())); //對按鈕的單擊事件和其槽函數進行關聯
這裏我們利用代碼添加了一個按鈕對象,用它來實現放大操作。
再在構造函數以外進行zoomIn()函數的定義:
void Dialog::zoomIn() //按鈕單擊事件的槽函數
{
scale *=2;
update();
}
3.通過上一節的學習,我們應該已經知道想讓畫布的內容放大有兩個辦法,一個是直接放大畫布的座標,一個是放大窗口的座標。
我們主要講解放大窗口座標。
void Dialog::paintEvent(QPaintEvent *)
{
QPainter pp(&pix);
pp.drawLine(lastPoint,endPoint); //根據鼠標指針前後兩個位置就行繪製直線
lastPoint = endPoint; //讓前一個座標值等於後一個座標值,這樣就能實現畫出連續的線
QPainter painter(this);
painter.scale(scale,scale); //進行放大操作
painter.drawPixmap(0,0,pix);
}
這時運行程序。
先隨意畫一個圖形,如下圖。
再按下“zoomIn”按鈕,進行放大兩倍。可以看到圖片放大了,效果如下。
這時我們再進行繪圖,繪製出的線條已經不能和鼠標指針的軌跡重合了。效果如下圖。
有了前面一節的知識,我們就不難理解出現這個問題的原因了。窗口的座標擴大了,但是畫布的座標並沒有擴大,而我們畫圖用的座標值是鼠標指針的,鼠標指針又是獲取的窗口的座標值。現在窗口和畫布的同一點的座標並不相等,所以就出現了這樣的問題。
其實解決辦法很簡單,窗口放大了多少倍,就將獲得的鼠標指針的座標值縮小多少倍就行了。
void Dialog::paintEvent(QPaintEvent *)
{
QPainter pp(&pix);
pp.drawLine(lastPoint/scale,endPoint/scale);
lastPoint = endPoint;
QPainter painter(this);
painter.scale(scale,scale); //進行放大操作
painter.drawPixmap(0,0,pix);
}
運行程序,效果如下:
這種用改變窗口座標大小來改變畫布面積的方法,實際上是有損圖片質量的。就像將一張位圖放大一樣,越放大越不清晰。原因就是,它的像素的個數沒有變,如果將可視面積放大,那麼單位面積裏的像素個數就變少了,所以畫質就差了。
下面我們簡單說說另一種方法。
放大畫布座標。
void Dialog::paintEvent(QPaintEvent *)
{
QPainter pp(&pix);
pp.scale(scale,scale);
pp.drawLine(lastPoint/scale,endPoint/scale);
lastPoint = endPoint;
QPainter painter(this);
painter.drawPixmap(0,0,pix);
}
效果如下:
此時,畫布中的內容並沒有放大,而且畫布也沒有變大,不是我們想要的,所以我們再更改一下函數。
void Dialog::paintEvent(QPaintEvent *)
{
if(scale!=1) //如果進行放大操作
{
QPixmap copyPix(pix.size()*scale); //臨時畫布,大小變化了scale倍
QPainter pter(©Pix);
pter.scale(scale,scale);
pter.drawPixmap(0,0,pix); //將以前畫布上的內容複製到現在的畫布上
pix = copyPix; //將放大後的內容再複製回原來的畫布上,這樣只傳遞內容,不傳遞座標系
scale =1; //讓scale重新置1
}
QPainter pp(&pix);
pp.scale(scale,scale);
pp.drawLine(lastPoint/scale,endPoint/scale);
lastPoint = endPoint;
QPainter painter(this);
painter.drawPixmap(0,0,pix);
}
此時運行效果如下:
這樣就好了。可以看到,這樣放大後再進行繪製,出來的效果是不同的。
我們就講到這裏,如果你有興趣,可以接着研究!
怎麼應用上面講到的內容,你可以查看繪圖軟件的教程(鏈接過去)。
(2)雙緩衝繪圖簡介
上面一節我們實現了塗鴉板的功能,但是如果我們想在塗鴉板上繪製矩形,並且可以動態地繪製這個矩形,也就是說我們可以用鼠標畫出隨意大小的矩形,那該怎麼辦呢?
我們先進行下面的三步,最後引出所謂的雙緩衝繪圖的概念。
第一步:
我們更改上一節的那個程序的重繪函數。
void Dialog::paintEvent(QPaintEvent *)
{
QPainter painter(this);
int x,y,w,h;
x = lastPoint.x();
y = lastPoint.y();
w = endPoint.x() - x;
h = endPoint.y() - y;
painter.drawRect(x,y,w,h);
}
然後運行,效果如下。
這時我們已經可以拖出一個矩形了,但是這樣直接在窗口上繪圖,以前畫的矩形是不能保存住的。所以我們下面加入畫布,在畫布上進行繪圖。
第二步:
我們先在構造函數裏將畫布設置大點:pix = QPixmap(400,400);
然後更改函數,如下:
void Dialog::paintEvent(QPaintEvent *)
{
int x,y,w,h;
x = lastPoint.x();
y = lastPoint.y();
w = endPoint.x() - x;
h = endPoint.y() - y;
QPainter pp(&pix);
pp.drawRect(x,y,w,h);
QPainter painter(this);
painter.drawPixmap(0,0,pix);
}
這時運行程序,效果如下:
現在雖然能畫出矩形,但是卻出現了無數個矩形,這不是我們想要的結果,我們希望能像第一步那樣繪製矩形,所以我們再加入一個臨時畫布。
第三步:
首先,我們在dialog.h中的private裏添加變量聲明:
QPixmap tempPix; //臨時畫布
bool isDrawing; //標誌是否正在繪圖
然後在dialog.cpp中的構造函數裏進行變量初始化:
isDrawing = false;
最後更改函數如下:
void Dialog::paintEvent(QPaintEvent *)
{
int x,y,w,h;
x = lastPoint.x();
y = lastPoint.y();
w = endPoint.x() - x;
h = endPoint.y() - y;
QPainter painter(this);
if(isDrawing) //如果正在繪圖
{
tempPix = pix; //將以前pix中的內容複製到tempPix中,這樣實現了交互繪圖
QPainter pp(&tempPix);
pp.drawRect(x,y,w,h);
painter.drawPixmap(0,0,tempPix);
}
else
{
QPainter pp(&pix);
pp.drawRect(x,y,w,h);
painter.drawPixmap(0,0,pix);
}
}
void Dialog::mousePressEvent(QMouseEvent *event)
{
if(event->button()==Qt::LeftButton) //鼠標左鍵按下
{
lastPoint = event->pos();
isDrawing = true; //正在繪圖
}
}
void Dialog::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons()&Qt::LeftButton) //鼠標左鍵按下的同時移動鼠標
{
endPoint = event->pos();
update();
}
}
void Dialog::mouseReleaseEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton) //鼠標左鍵釋放
{
endPoint = event->pos();
isDrawing = false; //結束繪圖
update();
}
}
我們使用兩個畫布,就解決了繪製矩形等圖形的問題。
其中tempPix = pix;一句代碼很重要,就是它,才實現了消除那些多餘的矩形。
雙緩衝繪圖簡介:
根據我的理解,如果將第一步中不用畫布,直接在窗口上進行繪圖叫做無緩衝繪圖,那麼第二步中用了一個畫布,將所有內容都先畫到畫布上,在整體繪製到窗口上,就該叫做單緩衝繪圖,那個畫布就是一個緩衝區。這樣,第三步,用了兩個畫布,一個進行臨時的繪圖,一個進行最終的繪圖,這樣就叫做雙緩衝繪圖。
我們已經看到,利用雙緩衝繪圖可以實現動態交互繪製。其實,Qt中所有部件進行繪製時,都是使用的雙緩衝繪圖。就算是第一步中我們沒有用畫布,Qt在進行自身繪製時也是使用的雙緩衝繪圖,所以我們剛纔那麼說,只是爲了更好地理解雙緩衝的概念。
----------------------------------------------------------------------------------------------------------
到這裏,我們已經可以進行一些Qt 2D繪圖方面的設計了。我這裏有兩個例子,一個是那個繪圖軟件(鏈接到那裏),它是實踐了我所講的這幾節的內容,可以說是對這幾節內容的一個綜合。還有一個例子,就是俄羅斯方塊程序(鏈接到那裏),那個是對這些知識的應用。如果你有興趣,可以看一下。
(3)圖形視圖框架簡介
我們前面用基本的繪圖類實現了一個繪圖軟件,但是,我們無法做出像Word或者Flash中那樣,繪製出來的圖形可以作爲一個元件進行任意變形。我們要想很容易地做出那樣的效果,就要使用Qt中的圖形視圖框架。
The QGraphics View Framework(圖形視圖框架),在Qt Creator中的幫助裏可以查看它的介紹,當然那是英文的,這裏有一篇中文的翻譯,大家可以看一下:
http://hi.baidu.com/yafeilinux/blog/item/f7040630723a0612eac4af20.html
如果你的程序中要使用大量的2D圖元,並且想要這些圖元都能進行單獨或羣組的控制,你就要使用這個框架了。比方說像Flash一樣的矢量繪圖軟件,各種遊戲軟件。但是因爲這裏涉及的東西太多了,不可能用一兩篇文章就介紹清楚,所以這裏我們只是提及一下,讓一些剛入門的朋友知道有這樣一個可用的框架。
最簡單的使用:
The QGraphics View Framework包含三個大類:QGraphicsItem 項類(或者叫做圖元類),QGraphicsScene 場景類,和QGraphicsView 視圖類。
QGraphicsItem 用來繪製你所要用到的圖形,QGraphicsScene 用來包含並管理所有的圖元,QGraphicsView 用來顯示所有場景。而他們三個都擁有自己各自的座標系統。我們下面就來建立一個工程,完成一個最簡單的例子。
1.新建空的Qt工程:
2.更改工程名和存放路徑。
3.然後新建C++類。
4.更改類名爲MyItem,基類填寫爲QGraphicsItem,如下圖:
5.可以看到新建的類默認已經添加到了工程裏。
6.新建C++ Source File,更改名字爲main.cpp,如下圖:
7.然後更改各文件的內容。
更改完成後,myitem.h文件內容如下:
myitem.cpp文件的內容如下:
main.cpp的內容如下:
運行程序,最終效果如下:
這裏我們只是演示了一下使用這個框架完成最簡單的程序的過程,只起到拋磚引玉的作用。
這個框架很複雜,但是功能也很強大,Qt Creator中自帶了幾個相關的例子(在幫助中查找Graphics View Examples即可),你可以參考一下。因爲篇幅問題,我們就只講這麼多,如果以後有機會,我會推出一個相關的專題來講述這個框架。