现在的位置: 首页 > 综合 > 正文

(转)双缓冲技术1

2014年03月23日 ⁄ 综合 ⁄ 共 12705字 ⁄ 字号 评论关闭
Rubber band(橡皮筋线,或者橡皮线), pixmap(图像,双缓冲中用到的图像,有时也直呼pixmap),off-screen pixmap(离线图像)
Plot(plot,这一节实现的就是一个绘制曲线的控件Plotter,有时原文也叫plot,有点小名的意思,没有翻译,直接呼之)
废话少说,以下是译文:
 

双缓冲技术是GUI编程中常用的技术。所谓的双缓冲就是把把一个需要渲染的控件保存到一个离线图像(off-screen pixmap)中,然后再把图像拷贝到需要绘制的控件上。在Qt的早期版本中,为了用户界面更加清爽,经常用这个技术来消除闪烁。
在Qt4中,QWidget能够自动处理闪烁,因此我们不用再担心这个问题。尽管如此,如果控件渲染复杂且需要经常刷新,双缓冲技术还是很有用的。我们可以把控件永久保存在一个图像中,随时准备下一次绘制事件的到来,一旦接到一个控件的绘制事件,就把图片拷贝到控件上。如果我们要做的只是小范围的修改,这个技术更是尤为有用,如要绘制一条橡皮筋线,就不必一次次刷新整个控件了。
在本章的最后一节,我们实现的是一个叫做Plotter的自定义控件。这个控件使用了双缓冲技术,也涉及到了Qt编程的其他方面:如键盘的事件处理,布局和坐标系统。
Plotter控件用来显示一条或者多条曲线,这些曲线由一组向量坐标表示。用户可以在图像上画一个橡皮筋线(Rubeber band),Plotter控件对橡皮筋线包围的区域进行放大。用户用鼠标左键在控件上选择一个点,然后拖动鼠标走到另一点,然后释放鼠标,就在控件上绘制一条橡皮筋线。
Figure 5.7 Zooming in on the Plotter Widget

  

用户可以多次用橡皮筋线进行放大,也可以用ZoomOut按钮缩小,然后用ZoomIn按钮再放大。ZoomOut和ZoomIn按钮只是在控件第一次放大或者缩小操作后变得可见,如果用户不缩放图形,则这两个按钮会一直不可见,这样可以使绘图区域不那么混乱。
Plotter控件可以存储任何数量的曲线的数据。同时它还维护一个PlotSettings对象的栈区域,每一个PlotSettings对象都是对应一个特定的缩放值。
首先看一下头文件的代码(对头文件的解析在代码中用注释的形式给出):
#ifndef PLOTTER_H
#define PLOTTER_H 
#include 
<QMap>//包含的Qt的头文件
#include <QPixmap>
#include 
<QVector>
#include 
<QWidget> 
class QToolButton; //两个前向声明
class PlotSettings;  
class Plotter : public QWidget
{
    Q_OBJECT
public:
    Plotter(QWidget 
*parent = 0);
    
void setPlotSettings(const PlotSettings &settings);
    
void setCurveData(int id, const QVector<QPointF> &data);//QPointF表明是浮点类型的QPoint
    
void clearCurve(int id);
    QSize minimumSizeHint() 
const//重写QWidget::minimumSizeHint()
    QSize sizeHint() const;        //重写QWidget::sizeHint()
public slots:
    
void zoomIn();   //放大曲线
    void zoomOut();   //缩小显示曲线
protected:  //重新实现的QWidget的事件处理函数
    void paintEvent(QPaintEvent *event);
    void resizeEvent(QResizeEvent *event);
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    void keyPressEvent(QKeyEvent *event);
    void wheelEvent(QWheelEvent *event);
private:
    
void updateRubberBandRegion();
    
void refreshPixmap();
    
void drawGrid(QPainter *painter);
    
void drawCurves(QPainter *painter);
    
enum { Margin = 50 };//表明图象周围的空间
    QToolButton 
*zoomInButton;
    QToolButton 
*zoomOutButton;
    QMap
<int, QVector<QPointF> > curveMap;  //曲线数据
    QVector<PlotSettings> zoomStack;   //PlotSettings栈区域
    int curZoom;
    
bool rubberBandIsShown;
    QRect rubberBandRect;
    QPixmap pixmap; 
//显示在屏幕的控件的一个拷贝,任何绘制总是先绘制在离线pixmap上,然后拷贝到控件上
};
//PlotSettings确定x,y轴的范围,和刻度的个数
class PlotSettings
{
public:
    PlotSettings();

    void scroll(int dx, int dy);
    
void adjust();
    
double spanX() const { return maxX - minX; }
    
double spanY() const { return maxY - minY; }

    double minX;
    
double maxX;
    
int numXTicks;
    
double minY;
    
double maxY;
    
int numYTicks;

private:
    
static void adjustAxis(double &min, double &max, int &numTicks);
};
#endif

 

图5-8表示了Plotter控件和PlotSettings的关系。
通常,numXTicks和numYTicks是有一个的误差,如果numXTicks为5,实际上Plotter会在x轴上绘制6个刻度。这样可以简化以后的计算(至于怎么样简化的,就看程序和后文吧)。
Figure 5-8 PlotSettings's member variables
现在来看源文件(代码有些长,先用代码格式给出完整源文件代码):

#include <QtGui>
#include 
<cmath>

#include "plotter.h"

Plotter::Plotter(QWidget *parent)
    : QWidget(parent)
{
    setBackgroundRole(QPalette::Dark);
    setAutoFillBackground(
true);
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    setFocusPolicy(Qt::StrongFocus);
    rubberBandIsShown 
= false;

    zoomInButton = new QToolButton(this);
    zoomInButton
->setIcon(QIcon(":/images/zoomin.png"));
    zoomInButton
->adjustSize();
    connect(zoomInButton, SIGNAL(clicked()), 
this, SLOT(zoomIn()));

    zoomOutButton = new QToolButton(this);
    zoomOutButton
->setIcon(QIcon(":/images/zoomout.png"));
    zoomOutButton
->adjustSize();
    connect(zoomOutButton, SIGNAL(clicked()), 
this, SLOT(zoomOut()));

    setPlotSettings(PlotSettings());
}

void Plotter::setPlotSettings(const PlotSettings &settings)
{
    zoomStack.clear();
    zoomStack.append(settings);
    curZoom 
= 0;
    zoomInButton
->hide();
    zoomOutButton
->hide();
    refreshPixmap();
}

void Plotter::zoomOut()
{
    
if (curZoom > 0) {
        
--curZoom;
        zoomOutButton
->setEnabled(curZoom > 0);
        zoomInButton
->setEnabled(true);
        zoomInButton
->show();
        refreshPixmap();
    }
}

void Plotter::zoomIn()
{
    
if (curZoom < zoomStack.count() - 1) {
        
++curZoom;
        zoomInButton
->setEnabled(curZoom < zoomStack.count() - 1);
        zoomOutButton
->setEnabled(true);
        zoomOutButton
->show();
        refreshPixmap();
    }
}

void Plotter::setCurveData(int id, const QVector<QPointF> &data)
{
    curveMap[id] 
= data;
    refreshPixmap();
}

void Plotter::clearCurve(int id)
{
    curveMap.remove(id);
    refreshPixmap();
}

QSize Plotter::minimumSizeHint() const
{
    
return QSize(6 * Margin, 4 * Margin);
}

QSize Plotter::sizeHint() const
{
    
return QSize(12 * Margin, 8 * Margin);
}

void Plotter::paintEvent(QPaintEvent * /* event */)
{
    QStylePainter painter(
this);
    painter.drawPixmap(
00, pixmap);
    
if (rubberBandIsShown) {
        painter.setPen(palette().light().color());
        painter.drawRect(rubberBandRect.normalized()
                                       .adjusted(
00-1-1));
    }
    
if (hasFocus()) {
        QStyleOptionFocusRect option;
        option.initFrom(
this);
        option.backgroundColor 
= palette().dark().color();
        painter.drawPrimitive(QStyle::PE_FrameFocusRect, option);
    }
}

void Plotter::resizeEvent(QResizeEvent * /* event */)
{
    
int x = width() - (zoomInButton->width()
                       
+ zoomOutButton->width() + 10);
    zoomInButton
->move(x, 5);
    zoomOutButton
->move(x + zoomInButton->width() + 55);
    refreshPixmap();
}
void Plotter::resizeEvent(QResizeEvent * /* event */)
{
    
int x = width() - (zoomInButton->width()
                       
+ zoomOutButton->width() + 10);
    zoomInButton
->move(x, 5);
    zoomOutButton
->move(x + zoomInButton->width() + 55);
    refreshPixmap();
}
void Plotter::resizeEvent(QResizeEvent * /* event */)
{
    
int x = width() - (zoomInButton->width()
                       
+ zoomOutButton->width() + 10);
    zoomInButton
->move(x, 5);
    zoomOutButton
->move(x + zoomInButton->width() + 55);
    refreshPixmap();
}

void Plotter::mousePressEvent(QMouseEvent *event)
{
    QRect rect(Margin, Margin,
               width() 
- 2 * Margin, height() - 2 * Margin);
    
if (event->button() == Qt::LeftButton) {
        
if (rect.contains(event->pos())) {
            rubberBandIsShown 
= true;
            rubberBandRect.setTopLeft(
event->pos());
            rubberBandRect.setBottomRight(
event->pos());
            updateRubberBandRegion();
            setCursor(Qt::CrossCursor);
        }
    }
}
void Plotter::mouseMoveEvent(QMouseEvent *event)
{
    
if (rubberBandIsShown) {
        updateRubberBandRegion();
        rubberBandRect.setBottomRight(
event->pos());
        updateRubberBandRegion();
    }
}
void Plotter::mouseReleaseEvent(QMouseEvent *event)
{
    
if ((event->button() == Qt::LeftButton) && rubberBandIsShown) {
        rubberBandIsShown 
= false;
        updateRubberBandRegion();
        unsetCursor();
        QRect rect 
= rubberBandRect.normalized();
        
if (rect.width() < 4 || rect.height() < 4)
            
return;
        rect.translate(
-Margin, -Margin);
        PlotSettings prevSettings 
= zoomStack[curZoom];
        PlotSettings settings;
        
double dx = prevSettings.spanX() / (width() - 2 * Margin);
        
double dy = prevSettings.spanY() / (height() - 2 * Margin);
        settings.minX 
= prevSettings.minX + dx * rect.left();
        settings.maxX 
= prevSettings.minX + dx * rect.right();
        settings.minY 
= prevSettings.maxY - dy * rect.bottom();
        settings.maxY 
= prevSettings.maxY - dy * rect.top();
        settings.adjust();
        zoomStack.resize(curZoom 
+ 1);
        zoomStack.append(settings);
        zoomIn();
    }
}

void Plotter::keyPressEvent(QKeyEvent *event)
{
    
switch (event->key()) {
    
case Qt::Key_Plus:
        zoomIn();
        
break;
    
case Qt::Key_Minus:
        zoomOut();
        
break;
    
case Qt::Key_Left:
        zoomStack[curZoom].scroll(
-10);
        refreshPixmap();
        
break;
    
case Qt::Key_Right:
        zoomStack[curZoom].scroll(
+10);
        refreshPixmap();
        
break;
    
case Qt::Key_Down:
        zoomStack[curZoom].scroll(
0-1);
        refreshPixmap();
        
break;
    
case Qt::Key_Up:
        zoomStack[curZoom].scroll(
0+1);
        refreshPixmap();
        
break;
    
default:
        QWidget::keyPressEvent(
event);
    }
}

void Plotter::wheelEvent(QWheelEvent *event)
{
    
int numDegrees = event->delta() / 8;
    
int numTicks = numDegrees / 15;
    
if (event->orientation() == Qt::Horizontal) {
        zoomStack[curZoom].scroll(numTicks, 
0);
    } 
else {
        zoomStack[curZoom].scroll(
0, numTicks);
    }
    refreshPixmap();
}
void Plotter::updateRubberBandRegion()
{
    QRect rect 
= rubberBandRect.normalized();
    update(rect.left(), rect.top(), rect.width(), 
1);
    update(rect.left(), rect.top(), 
1, rect.height());
    update(rect.left(), rect.bottom(), rect.width(), 
1);
    update(rect.right(), rect.top(), 
1, rect.height());
}
void Plotter::refreshPixmap()
{
    pixmap 
= QPixmap(size());
    pixmap.fill(
this00);
    QPainter painter(
&pixmap);
    painter.initFrom(
this);
    drawGrid(
&painter);
    drawCurves(
&painter);
    update();
}

void Plotter::drawGrid(QPainter *painter)
{
    QRect rect(Margin, Margin,
               width() 
- 2 * Margin, height() - 2 * Margin);
    
if (!rect.isValid())
        
return;
    PlotSettings settings 
= zoomStack[curZoom];
    QPen quiteDark 
= palette().dark().color().light();
    QPen light 
= palette().light().color();
    
for (int i = 0; i <= settings.numXTicks; ++i) {
        
int x = rect.left() + (i * (rect.width() - 1)
                                 
/ settings.numXTicks);
        
double label = settings.minX + (i * settings.spanX()
                                          
/ settings.numXTicks);
        painter
->setPen(quiteDark);
        painter
->drawLine(x, rect.top(), x, rect.bottom());
        painter
->setPen(light);
        painter
->drawLine(x, rect.bottom(), x, rect.bottom() + 5);
        painter
->drawText(x - 50, rect.bottom() + 510015,
                          Qt::AlignHCenter 
| Qt::AlignTop,
                          QString::number(label));
    }
    
for (int j = 0; j <= settings.numYTicks; ++j) {
        
int y = rect.bottom() - (j * (rect.height() - 1)
                                   
/ settings.numYTicks);
        
double label = settings.minY + (j * settings.spanY()
                                          
/ settings.numYTicks);
        painter
->setPen(quiteDark);
        painter
->drawLine(rect.left(), y, rect.right(), y);
        painter
->setPen(light);
        painter
->drawLine(rect.left() - 5, y, rect.left(), y);
        painter
->drawText(rect.left() - Margin, y - 10, Margin - 520,
                          Qt::AlignRight 
| Qt::AlignVCenter,
                          QString::number(label));
    }
    painter
->drawRect(rect.adjusted(00-1-1));
}

void Plotter::drawCurves(QPainter *painter)
{
    
static const QColor colorForIds[6= {
        Qt::red, Qt::green, Qt::blue, Qt::cyan, Qt::magenta, Qt::yellow
    };
    PlotSettings settings 
= zoomStack[curZoom];
    QRect rect(Margin, Margin,
               width() 
- 2 * Margin, height() - 2 * Margin);
    
if (!rect.isValid())
        
return;
    painter
->setClipRect(rect.adjusted(+1+1-1-1));
    QMapIterator
<int, QVector<QPointF> > i(curveMap);
    
while (i.hasNext()) {
        i.next();
        
int id = i.key();
        
const QVector<QPointF> &data = i.value();
        QPolygonF polyline(data.count());
        
for (int j = 0; j < data.count(); ++j) {
            
double dx = data[j].x() - settings.minX;
            
double dy = data[j].y() - settings.minY;
            
double x = rect.left() + (dx * (rect.width() - 1)
                                         
/ settings.spanX());
            
double y = rect.bottom() - (dy * (rect.height() - 1)
                                           
/ settings.spanY());
            polyline[j] 
= QPointF(x, y);
        }
        painter
->setPen(colorForIds[uint(id) % 6]);
        painter
->drawPolyline(polyline);
    }
}

////////////////////////////////////////////////////////////
PlotSettings::PlotSettings()
{
    minX 
= 0.0;
    maxX 
= 10.0;
    numXTicks 
= 5;
    minY 
= 0.0;
    maxY 
= 10.0;
    numYTicks 
= 5;
}

void PlotSettings::scroll(int dx, int dy)
{
    
double stepX = spanX() / numXTicks;
    minX 
+= dx * stepX;
    maxX 
+= dx * stepX;
    
double stepY = spanY() / numYTicks;
    minY 
+= dy * stepY;
    maxY 
+= dy * stepY;
}

void PlotSettings::adjust()
{
    adjustAxis(minX, maxX, numXTicks);
    adjustAxis(minY, maxY, numYTicks);
}

void PlotSettings::adjustAxis(double &min, double &max,
                              
int &numTicks)
{
    
const int MinTicks = 4;
    
double grossStep = (max - min) / MinTicks;
    
double step = pow(10.0, floor(log10(grossStep)));
    
if (5 * step < grossStep) {
        step 
*= 5;
    } 
else

抱歉!评论已关闭.