QGraphicsItem, QGraphicsScene 및 QGraphicsView 이해하기

1. 개념 이해


여기서는 QGraphicsItem, QGraphicsScene 및 QGraphicsView 에 대해 알아보자.

Qt에서 제공하는 Graphic관련 중요 요소는 다음 3가지가 있다.

  (1) QGraphicsItem : item

  (2) QGraphicsScene : scene

  (3) QGraphicsView : view

The usual workflow is to first create a couple of items, then add them to a scene, and finally set that scene on a view.

위의 3가지 요소를 활용한 일반적인 그래픽 작업 과정은 다음과 같다.

  (1) QGraphicsItem을 활용하여 필요한 item을 생성한다.

  (2) 생성된 item을 QGraphicsScene에 추가한다.

  (3) 마지막으로 QGraphicsScene을 QGraphicsView 에 셋팅한다.

Graphic 기능을 사용하기 위해 먼저 QGraphicsItem, QGraphicsScene 및 QGraphicsView에 대해 알아보자. 아래 그림은 그래픽의 3가지 요소에 대한 관계를 이해하는데 도움이된다. 여기서 보면 Scene은 전체 그림을 담는 큰 도화지와 같고 item은 도화지 위에 그려진 여러가지 그림이다. 그리고 view는 우리가 그림을 볼때 우리의 눈이 집중해서 보고 있는 도화지의 한 부분이라고 이해할 수 있을 것이다. 따라서 item은 scene에 add하여야 하며 다 그린 scene은 view와 연결을 해주어야 실제 화면에 표시되는 것이다.

따라서, scene은 item을 위치와 item의 변형 등을 정확하게 표시해주는 역할을 수행한다. 또한 어떤 event에 대해 어떤 item들이 영향을 받는지를 알려주는 역할도 수행한다.


2. QGraphicsItem생성하고 Scene 및 View와 연결하기

Scene에 표시되는 모든 item들은 QGraphicsItem클래스를 상속받아야만 한다. QGraphicsItem클래스는 boundingRect() 및 paint() 의 가상함수를 갖는 추상클래스(abstract class)이다.


2-1. QGraphicsItem들 간의 관계 설정

QGraphicsItem클래스의 생성자는 다른 item의 포인터를 parent를 받을 수 있다. 만약 포인터=0이면, parent가 없다는 의미이다. setParentItem()함수를 사용하면 parent를 변경하고 싶을 때 언제든지 변경할 수 있다. 또한 parent를 부터 child item을 제거하길 원하면, child item에 대해 setParentItem(0)를 호출하면 된다. 다음의 코드는 이런 관계를 보여주는 예시이다.

QGraphicsItem *parentItem = new QGraphicsItem();

QGraphicsItem *firstChildItem = new QGraphicsItem(parentItem);

QGraphicsItem *secondChildItem = new QGraphicsItem();

secondChildItem->setParentItem(parentItem);

delete parentItem;

위의 코드는 아래의 상속관계를 형성하게 한다.

참고로 item에 대해 parent가 있는지를 확인하기 위해서는 parentItem()함수를 사용하며, item이 child items를 가지고 있는지를 확인하기 위해서는 childItems()함수를 사용한다. childItems()함수의 경우 item들의 포인터가 저장된 QList를 리턴해준다. 

2-2. item의 화면 표시하기

QGraphicsItem들은 어떻게 화면에 표시할까? 당연히 그려야한다(paint). 모든 item들은 paint() 가상함수를 구현하여 자신만의 모양을 화면에 나타나게 할 수 있다. 물론 QGraphicsItem 을 상속하여 Qt에서 이미 만들어 놓은 표준item이 제공된다. 여기서는 paint()함수를 사용하여 item을 만들어보자. 아래에서는 QGraphicsItem클래스를 상속받는 BlackRectangle 클래스를 만들었다. 생성자에서는 QGraphicsItem을 인자로 받고 있으며 소멸자 ~BlackRectangle()를 선언주어야만 한다. boundingRect()함수에서는 폭75픽셀, 높이 25픽셀 크기로 정의되고 있으며 paint()에서는 이 영역만큼에 대해서만 작업을 할 수 있다. 마지막으로 paint()함수는 boundingRect을 검은색으로 칠해주고 있다. 여기서 Q_UNUSED 매크로는 사용하지 않는 함수인자를 컴파일러에 알려줌으로서 컴파일러가 경고메시지를 출력하지 않도록 해준다.

blackrectangle.h 파일

#ifndef BLACKRECTANGLE_H
#define BLACKRECTANGLE_H

#include <QGraphicsItem>
#include <QPainter>
#include <QStyleOptionGraphicsItem>

class BlackRectangle : public QGraphicsItem
{
public:
    explicit BlackRectangle(QGraphicsItem *parent);

    virtual ~BlackRectangle() {}

    QRectF boundingRect() const
    {
        return QRectF(0, 0, 75, 25);
    }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem
    *option, QWidget *widget)
    {
        Q_UNUSED(option)
        Q_UNUSED(widget)
        painter->fillRect(boundingRect(), Qt::black);
    }
};

#endif // BLACKRECTANGLE_H


blackrectangle.cpp 파일


#include "blackrectangle.h"

BlackRectangle::BlackRectangle(QGraphicsItem *parent = 0)
    : QGraphicsItem(parent)
{

}

main.cpp 파일

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include "blackrectangle.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    QGraphicsView view;

    BlackRectangle *blackRect = new BlackRectangle(0);
    scene.addItem(blackRect);

    view.setScene(&scene);
    view.show();

    return a.exec();

}

위 코드의 실행결과는 다음과 같다.


paint()함수에 대해 좀 더 이해해보자. paint 함수의 인자 QStyleOptionGraphicsItem *option은 매우 유용한 기능을 제공한다. 예를 들어 사각형을 마우스가 선택할 경우 빨강색으로 변하고 아닐 경우 검은색으로 변하게 만들기 위해 paint()함수를 아래와 같이 수정하자.

void paint(QPainter *painter, const QStyleOptionGraphicsItem
    *option, QWidget *widget)
{
        Q_UNUSED(widget)

        if (option->state.testFlag(QStyle::State_Selected))
            painter->fillRect(boundingRect(), Qt::red);
        else
            painter->fillRect(boundingRect(), Qt::black);

}

또한 item을 마우스로 선택할 수 있도록 BlackRectangle클래스의 생성자에 다음과 같이 수정을 해주어야 한다.

#include "blackrectangle.h"

BlackRectangle::BlackRectangle(QGraphicsItem *parent = 0)
    : QGraphicsItem(parent)
{
    setFlag(QGraphicsItem::ItemIsSelectable);

}

이를 컴파일하여 실행한 결과 화면은 다음과 같다.


paint() 함수에서 사용한 QStyle에는 다음과 같은 인자들이 있다.

State_Enabled : Indicates that the item is enabled. If the item is disabled, you may want to draw it as grayed out.

State_HasFocus : Indicates that the item has the input focus. To receive this state, the item needs to have the ItemIsFocusable flag set.

State_MouseOver : Indicates that the cursor is currently hovering over the item. To receive this state the item needs to have the acceptHoverEvents variable set to true.

State_Selected : Indicates that the item is selected. To receive this state, the item needs to have the ItemIsSelectable flag set. The normal behavior would be to draw a dashed line around the item as a selection marker.

마지막으로 사각형의 크기를 사용자가 지정할 수 있도록 코드를 수정해보자. 이를 위해 QRectF m_rect 변수를 private 변수로 추가하자. 그리고 boundingRect()함수를 아래와 같이 수정한다.

QRectF boundingRect() const
{
   return m_rect;

}

그리고 아래와 같은 rect()함수와 setRect(const QRectF& rect)함수를 추가하자. 아래에서 prepareGeometryChange()함수는 scene에게 item을 다시 그리도록 알려준다.

    QRectF rect() const 
    {
        return m_rect;
    }
    
    void setRect(const QRectF& rect) 
    {
        if (rect == m_rect)
            return;
    
        prepareGeometryChange();
    
        m_rect = rect;

    }

그러면 최종 blackrectangle.h 파일은 다음과 같다.

#ifndef BLACKRECTANGLE_H

#define BLACKRECTANGLE_H
#include <QGraphicsItem>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
class BlackRectangle : public QGraphicsItem
{
public:
    explicit BlackRectangle(QGraphicsItem *parent);
    virtual ~BlackRectangle() {}
    QRectF boundingRect() const
    {
        return m_rect;
    }
    void paint(QPainter *painter, const QStyleOptionGraphicsItem
    *option, QWidget *widget)
    {
        Q_UNUSED(widget)
        if (option->state.testFlag(QStyle::State_Selected))
            painter->fillRect(boundingRect(), Qt::red);
        else
            painter->fillRect(boundingRect(), Qt::black);
    }
    QRectF rect() const
    {
        return m_rect;
    }
    void setRect(const QRectF& rect)
    {
        if (rect == m_rect)
            return;
        prepareGeometryChange();
        m_rect = rect;
    }
private:
    QRectF m_rect;
};
#endif // BLACKRECTANGLE_H

그리고 생성자함수에서 m_rect를 아래와 같이 초기화해준다.

#include "blackrectangle.h"

BlackRectangle::BlackRectangle(QGraphicsItem *parent = 0)
    : QGraphicsItem(parent),
      m_rect(0, 0, 25, 75)
{
    setFlag(QGraphicsItem::ItemIsSelectable);
}

이제 사용자가 사각형의 크기 조절 기능을 사용해보기 위하여 main.cpp파일을 다음과 같이 수정한다.

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    QGraphicsView view;

    BlackRectangle *blackRect = new BlackRectangle(0);

    blackRect->setRect(QRectF(0,0,100,100));

    scene.addItem(blackRect);

    view.setScene(&scene);
    view.show();

    return a.exec();

}

최종 실행 화면은 다음과 같다.


3. 정리하기

여기서는 QGraphicsItem, QGraphicsScene 및 QGraphicsView 의 관계에 대해 알아보고 이를 응용하여 간단한 예제를 만들어 보았다. 다음 글에서는 QGraphics의 좌표체계에 대해서 이해해보자.

댓글

이 블로그의 인기 게시물

SPACE 코드 제어 계통도를 그림 파일로 변환하는 예시

Scintilla 라이브러리를 활용한 SPACE 코드 전용 에디터 AESPA 개발 [2일차]