I am working on a small .ui
project and I am was trying to understand how to properly make a ruler on a QGraphicsView
.
So when the use sees th
Create a new class by subclassing QWidget
to draw your ruler. Then, set a viewport margin based on the size of your ruler.
The main difficulty is to handle the units of your ruler: The painting process in Qt uses only pixels. So, you have to convert all the distance to the right unit.
An example of ruler (in millimeters) for any QAbstractScrollArea
(including QGraphicsView
):
class Ruler: public QWidget
{
Q_OBJECT
public:
Ruler(QAbstractScrollArea* parent=nullptr): QWidget(parent),
offset(0)
{
setFixedSize(40, parent->height());
move(0, 40);
connect(parent->verticalScrollBar(), &QScrollBar::valueChanged, this, &Ruler::setOffset);
}
virtual void paintEvent(QPaintEvent* event)
{
QPainter painter(this);
painter.translate(0, -offset);
int const heightMM = height() * toMM();
painter.setFont(font());
QFontMetrics fm(font());
for (int position = 0; position < heightMM; ++position)
{
int const positionInPix = int(position / toMM());
if (position % 10 == 0)
{
if (position != 0)
{
QString const txt = QString::number(position);
QRect txtRect = fm.boundingRect(txt).translated(0, positionInPix);
txtRect.translate(0, txtRect.height()/2);
painter.drawText(txtRect, txt);
}
painter.drawLine(width() - 15, positionInPix, width(), positionInPix);
}
else {
painter.drawLine(width() - 10, positionInPix, width(), positionInPix);
}
}
}
virtual void resizeEvent(QResizeEvent* event)
{
int const maximumMM = event->size().height() * toMM();
QFontMetrics fm(font());
int w = fm.width(QString::number(maximumMM)) + 20;
if (w != event->size().width())
{
QSize const newSize(w, event->size().height());
sizeChanged(newSize);
return setFixedSize(newSize);
}
return QWidget::resizeEvent(event);
}
void setOffset(int value)
{
offset = value;
update();
}
signals:
void sizeChanged(QSize const&);
private:
int offset;
static qreal toMM()
{
return 25.4 / qApp->desktop()->logicalDpiY();
}
};
The paintEvent()
and resizeEvent()
functions could be simplified if you want to draw the values at the vertical instead of horizontal (you will not have to resize the ruler to be able to display all digits).
How to use it:
class GraphicsView: public QGraphicsView
{
public:
GraphicsView(QWidget* parent=nullptr): QGraphicsView(parent),
ruler(new Ruler(this))
{
connect(ruler, &Ruler::sizeChanged, [this](QSize const& size) { setViewportMargins(size.width(), size.width(), 0, 0); });
}
void setScene(QGraphicsScene* scene)
{
QGraphicsView::setScene(scene);
if (scene)
ruler->setFixedHeight(scene->height());
}
private:
Ruler* ruler;
};
The conversion pixel -> millimeters is not really accurate and you should find another way to make it.
I didn't handle the resize of the scene, either.
On creation, scene->height() is zero by default. If ruler->setFixedHeight() unwittingly set the height of the ruler to 0, paintEvent for the ruler will not be called.
void setScene(QGraphicsScene* scene)
{
QGraphicsView::setScene(scene);
if (scene)
ruler->setFixedHeight(scene->height());
}