• 双缓冲技术(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;
    }

    参考:http://www.cnblogs.com/SkylineSoft/articles/2046262.html

  • 相关阅读:
    Do You See Me? Ethical Considerations of the Homeless
    ELDER HOMELESSNESS WHY IS THIS AN ISSUE?
    Endoflife support is lacking for homeless people
    html内联框架
    html字体
    html块 div span
    html列表
    html表格
    SQL Server管理员专用连接的使用   作为一名DBA,经常会处理一些比较棘手的服务无响应问题,鉴于事态的严重性,多数DBA可能直接用“重启”大法,以便尽快的恢复生产环境的正常运转,但是多数情况
    如何配置最大工作线程数 (SQL Server Management Studio)
  • 原文地址:https://www.cnblogs.com/findumars/p/4733607.html
Copyright © 2020-2023  润新知