5-4 双缓冲技术(Double Buffering)(1、简介和源代码部分)

 
这一节实在是有些长,翻译完后统计了一下,快到2w字了。考虑到阅读的方便和网络的速度,打算把这节分为5个部分,第一部分为双缓冲技术的一个简介和所有的代码,如果能够看懂代码,不用看译文也就可以了。第二部分为Plotter控件的公有函数的实现,第三部分为Plotter的事件处理函数的实现,第四部分为Plotter控件的私有函数实现,第五部分为辅助类PlotSettings的实现。
这里给出一些常用的中英文对照(不一定准确,我这样用的):
Rubber band(橡皮筋线,或者橡皮线), pixmap(图像,双缓冲中用到的图像,有时也直呼pixmap),off-screen pixmap(离线图像)
Plot(plot,这一节实现的就是一个绘制曲线的控件Plotter,有时原文也叫plot,有点小名的意思,没有翻译,直接呼之)
废话少说,以下是译文:
 
双缓冲技术是GUI编程中常用的技术。所谓的双缓冲就是把需要绘制的控件保存到一个图像中,然后在把图像拷贝到需要绘制的控件上。在Qt的早期版本中,为了用户界面更加清爽,经常用这个技术来消除闪烁。
在Qt4中,QWidget能够自动处理闪烁,因此我们不用再担心这个问题。尽管如此,如果控件绘制复杂且需要经常刷新,双缓冲技术还是很有用的。我们可以把控件永久保存在一个图像中,随时准备下一次绘制事件的到来,一旦接到一个控件的绘制事件,就把图片拷贝到控件上。如果我们要做的只是小范围的修改,这个技术更是尤为有用,如要绘制一条橡皮筋线,就不必刷新整个控件了。
在本章的最后一节,我们实现的是一个叫做Plotter的自定义控件。这个控件使用了双缓冲技术,也涉及到了Qt编程的其他方面:如键盘的事件处理,布局和坐标系统。
Plotter控件用来显示一条或者多条曲线,这些曲线由一组向量坐标表示。用户可以在显示的曲线上画一个橡皮筋线,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);
    
void  clearCurve( int  id);
    QSize minimumSizeHint() 
const // 重写QWidget::minimumSizeHint()
    QSize sizeHint()  const ;         // 重写QWidget::sizeHint()
public  slots:
    
void  zoomIn();    // 放大曲线
void  zoomOut();    // 缩小显示曲线
protected :   // 重写的事件处理函数
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(
0 0 , pixmap);
    
if  (rubberBandIsShown) {
        painter.setPen(palette().light().color());
        painter.drawRect(rubberBandRect.normalized()
                                       .adjusted(
0 0 - 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()  +   5 5 );
    refreshPixmap();
}
void  Plotter::resizeEvent(QResizeEvent  *   /*  event  */ )
{
    
int  x  =  width()  -  (zoomInButton -> width()
                       
+  zoomOutButton -> width()  +   10 );
    zoomInButton
-> move(x,  5 );
    zoomOutButton
-> move(x  +  zoomInButton -> width()  +   5 5 );
    refreshPixmap();
}
void  Plotter::resizeEvent(QResizeEvent  *   /*  event  */ )
{
    
int  x  =  width()  -  (zoomInButton -> width()
                       
+  zoomOutButton -> width()  +   10 );
    zoomInButton
-> move(x,  5 );
    zoomOutButton
-> move(x  +  zoomInButton -> width()  +   5 5 );
    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(
- 1 0 );
        refreshPixmap();
        
break ;
    
case  Qt::Key_Right:
        zoomStack[curZoom].scroll(
+ 1 0 );
        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(
this 0 0 );
    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()  +   5 100 15 ,
                          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  -   5 20 ,
                          Qt::AlignRight 
|  Qt::AlignVCenter,
                          QString::number(label));
    }
    painter
-> drawRect(rect.adjusted( 0 0 - 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   if  ( 2   *  step  <  grossStep) {
        step 
*=   2 ;
    }
    numTicks 
=   int (ceil(max  /  step)  -  floor(min  /  step));
    
if  (numTicks  <  MinTicks)
        numTicks 
=  MinTicks;
    min 
=  floor(min  /  step)  *  step;
    max 
=  ceil(max  /  step)  *  step;
}
在MFC(Microsoft Foundation Classes)环境中编写双缓冲Double Buffering)的小球绘图算法涉及到一些图形处理的基本原理和MFC的视图类(如CView)。由于这里无法提供完整的源代码,我会给出一个概述并提供关键部分的伪代码和步骤: 首先,你需要在CView派生类中设置双缓冲。这通常通过`OnPrepareDC()`和`OnDraw()`方法实现。在`OnPrepareDC()`中,创建一个新的设备上下文(CDC)用于画布备份,而在`OnDraw()`中,先在备份上绘制,然后切换到主设备上下文进行显示。 ```cpp class MyView : public CView { public: void OnPrepareDC(CDC* pDC, const CPrintInfo* pInfo = NULL) override { CDC backupDC; DoPrepareDC(&backupDC); m_pBackBuffer = new CDC(pDC->GetSafeHdc()); // 其他必要的准备工作... } void OnDraw(CDC* pDC) override { if (m_pBackBuffer) { // 切换到备份DC进行绘制 pDC->BitBlt(0, 0, GetWidth(), GetHeight(), m_pBackBuffer, 0, 0, SRCCOPY); // 清理备份 delete m_pBackBuffer; m_pBackBuffer = nullptr; // 再次准备DC以更新屏幕 DoPrepareDC(pDX); } else { // 如果还没创建备份DC,则直接在这里绘制 // ... } } private: CDC* m_pBackBuffer; // 双缓冲设备上下文指针 // 其他小球绘图函数... }; ``` 对于小球的移动和绘图,你可以设计一个包含位置、大小等属性的类,比如Ball类,并在每次需要更新时(例如每帧或鼠标移动),调用小球的绘图方法,并将备份DC作为参数,以便在后台绘制。 ```cpp void Ball::Draw(CDC& dc) { dc.Ellipse(m_position.x - m_radius, m_position.y - m_radius, m_position.x + m_radius, m_position.y + m_radius); } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值