Qt5 - QGraphicsItem 을 사용한 나만의 Graphic Item 만들기

본 글에서는 Qt5에서 사용할 수 있는 QGraphicsItem 관련 클래스들을 알아보고 이 들을 활용하여 자신만의 Graphic Item을 만들어 본다.

1. QGraphicsItem 클래스 알아보기

Qt5의 모든 GraphicsItem의 부모 클래스로서 아래와 같은 Graphic Item들이 상속을 받는다.

  • QGraphicsEllipseItem : 원 및 타원을 그린다.
  • QGraphicsLineItem : 선을 그린다.
  • QGraphicsPathItem : 임의의 그래픽 path 만든다.
  • QGraphicsPixmapItem : pixmap을 그린다. 이미지을 처리
  • QGraphicsPolygonItem : polygon을 그린다.
  • QGraphicsRectItem : 사각형을 그린다.
  • QGraphicsSimpleTextItem : Text를 그린다.
  • QGraphicsTextItem : Text를 그린다. QGraphicsSimpleTextItem에 비해 자원이 많이 소요되므로 일반적으로 QGraphicsSimpleTextItem을 사용한다.
위에서 열거한 그래픽 관련 클래스를 상속받아 나만의 그래픽 item을 만들 수 있다.
2. 나만의 Graphic Item 만들기

2-1. New project 시작하기

(1) 아래와 같이 선택한 후 Choose를 클릭한다.
(2) Name에 "MyOwnItem"을 입력. 아무거나 입력해도 됨.
(3) 컴파일러를 선택하는 Kit로 MingW 32bit을 선택한다. Qt 5.9 에는 기본적으로 설치가 된다.
(4) 아래는 아무 수정없이 기본값을 유지. Next
(5) 기본값 유지 Finish
(6) 아래와 같은 기본 프로젝트가 완성되었다.
(7) 아래에서 파란색 부분을 클릭하면 컴파일 작업이 진행되고 마지막으로 프로그램이 실행된다.

이제 기본골격이 갖추어졌으니 본격적으로 자신만의 그래픽 item을 만들어 보자.

2-2. 나만의 item 클래스 추가하기

(1) 아래 파랑색 부분에서 마우스 우클릭을 한 후 "Add New" 를 선택한다.
(2) 아래 창과 같이 C++ Class를 추가한다. Next
(3) Class name 에 "MyGraphicItem"을 입력하고, Base class에 QGraphicsRectItem을 입력한 후 Next. 여기서는 MyGraphicItem은 QGraphicsRectItem을 상속 받는다는 것을 의미한다.
(4) 여기서는 기본값을 그대로 두고 Finish
(5) 아래 창과 같이 Qt5 프로젝트에 MyGraphicItem 클랙스가 추가된 것을 확인할 수 있다.

2-3. 나만의 item 클래스(mygraphicitem) 살펴보기

(1) mygraphicitem.h 살펴보기

mygraphicitem.h의 내용을 보면 아래와 같다. 여기서 나의 클래스 MyGraphicItem이 QGraphicsRectItem클래스를 상속받고 있음을 알 수 있다. 아래에서 보면 QGraphicsRectItem의 글자색이 검은색이다. 이는 관련 헤더 파일이 include 되지 않았음을 의미한다. 마우스커서를 QGraphicsRectItem의 Q 앞에 위치시킨 후 alt+Enter를 클릭하여 include 파일을 추가한다.

#ifndef MYGRAPHICITEM_H
#define MYGRAPHICITEM_H


class MyGraphicItem : public QGraphicsRectItem
{
public:
    MyGraphicItem();
};

#endif // MYGRAPHICITEM_H

(2) mygraphicitem.cpp 살펴보기

mygraphicitem.cpp 내용은 아래와 같으며, 기본적으로 클래스 생성자가 선언되어 있음을 알 수 있다. 

#include "mygraphicitem.h"

MyGraphicItem::MyGraphicItem()
{

}

이제 위 두 파일에 나만의 그래픽 item을 만들면 된다.

2-4. 프로젝트에서 불필요한 파일 지우기

현재 프로젝트에 보면 mainwindow.h와 mainwindow.cpp 파일이 있다. 여기서는 사용하지 않을 예정이므로 프로젝트에서 삭제해준다. 각 파일이름 위에 마우스커서를 위치 시키고 우클릭하여 Remove file을 선택하거나 키보드의 Del 키를 누른다. 아래의 창에서 Delete file permanently에 체크하고 OK 한다.


그러면 아래와 같은 파일만 남는데 Forms도 동일한 방법으로 삭제해준다.

또한 main.cpp 파일에서 mainwindow와 관련된 부분을 삭제한다. 그러면 아래 내용가 같이 된다.

#include <QApplication>

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


    return a.exec();
}

2-5. 나만의 그래픽 item 코딩하기

(1) mygraphicitem.h 에 메서드 및 속성 선언하기

mygraphicitem.h파일을 더블클릭하여 에디트 창에 오픈하고 아래와 같이 코딩한다. 생성자함수에 qreal width, qreal height, qreal radius 인자를 추가하고 QGraphicsRectItem *parent; 를 추가한다. 그리고 private 필드에  qreal width, qreal height, qreal radius 변수를 추가한다.

#ifndef MYGRAPHICITEM_H
#define MYGRAPHICITEM_H

#include <QGraphicsRectItem>

class MyGraphicItem : public QGraphicsRectItem
{
public:
    MyGraphicItem(qreal width, qreal height, qreal radius);
    QGraphicsRectItem *parent;
private:
    qreal width;
    qreal height;
    qreal radius;
};

#endif // MYGRAPHICITEM_H

(2) mygraphicitem.cpp 에 메서드 구현하기

mygraphicitem.cpp파일을 오픈하여 아래와 같이 생성자 함수에 관련 코딩을 추가한다.

#include "mygraphicitem.h"
#include <QLinearGradient>
#include <QPen>

MyGraphicItem::MyGraphicItem(qreal width, qreal height, qreal radius):
    width(width),
    height(height),
    radius(radius)
{
    QLinearGradient gradient(0, 0, width, height);
    gradient.setColorAt(0.0, Qt::yellow);
    gradient.setColorAt(0.3, Qt::red);
    gradient.setColorAt(1.0, Qt::magenta);
    QBrush parentBrush = gradient;

    QRectF rect(-width*0.5, -height*0.5, width, height);
    rect.translate(0.5, 0.5); // for thin lines
    QPen pen;
    //pen.setStyle(Qt::DashLine);
    pen.setColor(Qt::red);

    parent = new QGraphicsRectItem(rect);
    parent->setFlag(QGraphicsItem::ItemIsSelectable, true);
    parent->setFlag(QGraphicsItem::ItemIsMovable, true);
    parent->setPen(pen);
    parent->setBrush(parentBrush);

    QRectF circleBoundary(-radius, -radius, 2*radius, 2*radius);

    //QGraphicsTextItem *childText = new QGraphicsTextItem("My Rect",parent);  // Try to avoid QGraphicsTextItem. heavy load
    QGraphicsSimpleTextItem *childText = new QGraphicsSimpleTextItem("My Rect",parent);
    QPointF posText(-0.5*childText->boundingRect().width(), -0.5*childText->boundingRect().height());
    //childText->setDefaultTextColor(Qt::blue);
    childText->setPos(posText);
    //childText->setPen(QColor(Qt::green));
    //childText->setBrush(parentBrush);

    for(int i=0; i<4; i++)
    {
        QGraphicsEllipseItem *child = new QGraphicsEllipseItem(circleBoundary, parent);
        child->setBrush(Qt::blue);
        QPointF pos;
        switch (i)
        {
        case 0:
            pos = rect.topLeft();
            break;
        case 1:
            pos = rect.bottomLeft();
            break;
        case 2:
            pos = rect.topRight();
            break;
        case 3:
            pos = rect.bottomRight();
            break;
        }
        child->setPos(pos);
        QGraphicsSimpleTextItem *nodeID = new QGraphicsSimpleTextItem(QString::number(i));
        // text를 직접 ItemIgnoresTransformations하면, view가 변형되었을때 좌표가 깨져서 문자가  중앙에 위치하지 않게 된다.
        // 이를 방지하기 위해 원의 중앙에 위치한 사각형을 먼저 만들어  안에 문자를 위치시킨다.
        QRectF textRect = nodeID->boundingRect();
        textRect.translate(-textRect.center());
        QGraphicsRectItem *rectItem = new QGraphicsRectItem(textRect, child);
        //rectItem->setPen(QPen(Qt::blue));
        rectItem->setPen(Qt::NoPen);
        rectItem->setFlag(QGraphicsItem::ItemIgnoresTransformations);
        nodeID->setParentItem(rectItem);
        nodeID->setBrush(Qt::white);
        nodeID->setPos(textRect.topLeft());
        //nodeID->setFlag(QGraphicsItem::ItemIgnoresTransformations);
    }
}

(3) main.cpp 코딩하기

위에서 구현한 MyGraphicItem은 화면에 표시하기 위해 main.cpp파일에 아래와 같이 코딩한다.

#include "mygraphicitem.h"

#include <QApplication>
#include <QGraphicsRectItem>
#include <QGraphicsScene>
#include <QGraphicsView>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    // 그래픽scene을 생성한다.
    QGraphicsScene scene;

    //Custom item 2개를 생성
    MyGraphicItem *node1 = new MyGraphicItem(100, 60, 8);
    MyGraphicItem *node2 = new MyGraphicItem(100, 60, 8);
    // parent(= QGraphicsRectItem)를 씬에 추가한다. 
    scene.addItem(node1->parent);
    scene.addItem(node2->parent);
    // parent의 표시 위치를 설정한다.
    node2->parent->setPos(200, 0);
    node2->parent->setRotation(20);
    // Graphic 뷰를 씬과 연결한다.
    QGraphicsView view(&scene);
    // Graphic 뷰를 2배 확대한다.
    view.scale(2.0, 2.0);
    // Graphic 뷰 속성을 설정한다.
    view.setRenderHint(QPainter::Antialiasing);
    view.setRenderHint(QPainter::TextAntialiasing);
    view.setDragMode(QGraphicsView::RubberBandDrag);
    view.centerOn(node1);
    // Graphic 뷰 보여준다.
    view.show();
    //
    // Graphic 뷰를 이미지 파일로 저장한다.
    QImage image(view.size(), QImage::Format_ARGB32);
    image.fill(Qt::transparent);
    QPainter painter(&image);
    scene.render(&painter);
    image.save("scene.png");
//
    return a.exec();
}

2-6. 컴파일 및 결과 보기

이제 위에서 코딩한 것을 컴파일 및 실행하여 결과를 보면 아래와 같다. 아래 창에서 각각의 아이템은 선택 후 움직일 수 있다.


3. 정리하기

Qt5를 활용하여 자신만의 그래픽 item을 만들어 보았다. 여기서 제시된 기술들을 잘 응용하면 더 많은 것을 만들 수 있으리라 믿는다. 특히 mygraphicitem.cpp에서 활용한 parent-children 계층구조는 매우 유용한 기술이니 잘 습득해두어야 한다.











































댓글

이 블로그의 인기 게시물

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

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