본문 바로가기

IT/QT

[QT] 3. 상단 탭 추가하기 (QTapWidget)

반응형

☞ 메인보드 : Jetson Nano Developer Kit

☞ 운영 체제 : Ubuntu 18.04 - JetPack 4.4.3

☞ IDE :  QtCreator, Visual Studio Code

☞ 언어 : C++

 

 

 

 


목차

1. QTabWidget(탭 만들기)

2. 코드 분석

3.이런 것도 있더라 -QTableWidget(엑셀 표)

 

 

 


 

 

정말 오랜만에 글을 작성해보는 것 같다. 조회수는 늘 비슷하게 유지되는 걸 보니 꾸준히 읽어주시는 분이 있는 듯하다. 가끔은 글을 써야 한다는 이상한 중압감에 사로잡혀 억지로 글을 쓰곤 했는데 그런 저퀄리티의 글도 읽어주시는 분들께 정말 감사하다는 인사를 하고 시작한다.

 

 

19년도 로봇 대회 참여로 동아리 발전을 위해 글을 작성하게 되면서 ROS나 OPENCV 이외에도 내가 공부하고 싶었던 분야와 일상에 대해서 올리게 되었다. 다양한 소프트웨어에 접하게 되면서 QT를 발견했을 때는 언젠가 재미있는 소프트웨어를 직접 만들어보자는 가벼운 생각이 있었다. 올해 로봇대회에 참여할 생각을 하고 있어 가벼운 생각이 아닌 본격적으로 소프트웨어를 만들어보려고 QT를 다시 공부하려고 한다. 별 것 아닌 내용일 테지만 남긴 기록이 누군가에게 도움이 되었으면 한다.

 

 

 

 

 

① QTabWidget(탭 만들기)

 

왜 갑자기 탭을 만들자 했냐면, 대회장에 갔을 때 여러 팀들이 독자적인 소프트웨어를 개발하여 왔던 것이 멋있어 보였기 때문이다. ROS kinetic 버전을 사용하면 QT와 연동이되는 확장 플러그인이 있는데, 우리 팀은 melodic 버전을 사용하고 있어서 아무리 찾아봐도 플러그인을 연동할 수 없었다. 벌써 ROS2도 개발된지 많은 시간이 흘렀지만, amd64에 맞는 플러그인은 언제 개발될런지 모르겠다. 

 

일단은 Visual Stduio Code 에서 터미널로 작업을 하고 있는데 나중에 ROS가 삽입이 안되면 매우매우 안타까울 것 같다. 사실 이걸 먼저 확인해야하는데 그냥 QT가 하고 싶었다고 치고 넘어가겠다.

 

 

 

 

간단히 계획을 세워볼까?

 

다양한 위젯을 사용하여 여러가지 기능을 갖고 있는 소프트웨어를 개발하고 싶어 한다면 처음에 손으로 위젯의 배치나 상위, 하위 레이아웃 등을 직접 작성해보는 게 좋을 것 같다. 작은 프로젝트에는 상관이 없겠지만, 점점 살을 붙여가 큰 프로젝트가 될 것을 대비하여 미리미리 상-하위 관계를 기록해놓자.

 

 

 

 

초점이 안맞아서 잘 안 보이는데 보통은 이런 방법으로 혼자 어떻게 만들어볼지 계획을 세우고 코드를 작성했다. 나는 코드 작성 중에 상위, 하위 레이아웃 또는 위젯을 생각하면서 했었다가 중간중간 시간을 많이 빼앗겼다. (빡대가리는 미리 계획을 세우고 하는 게 좋다ㅜㅜ) 물론 이번 포스트처럼 간단한 것은 그냥 해도 된다. 

 

계획을 세우려면 위젯이나 레이아웃이 어디까지 자유도 있게 동작하고 직접 설정할 수 있는지를 알아야한다. 처음 사용해보는 사람은 일단 하나하나 예제로 만들어서 사용해보는 것을 추천한다.

 

 

 

 


(전체 코드를 보려면 아래의 '더보기' 클릭)

더보기

 1. main.cpp

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.resize(300,300);
    w.show();
    
    return a.exec();
}

 

2. widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTabWidget>

#include <QHBoxLayout>
#include <QVBoxLayout>

#include <QPushButton>
#include <QString>
#include <QLabel>
#include <QImage>
#include <QPixmap>

#include <opencv4/opencv2/core/core.hpp>
#include <opencv4/opencv2/highgui.hpp>
#include <opencv4/opencv2/opencv.hpp>


class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private:
    QHBoxLayout *HLayout;
    QVBoxLayout *VLayout;

    QTabWidget *tabWidget;

    QWidget *tab1;
    QLabel *tab1_screen;
    QLabel *tab1_label;
    QHBoxLayout *tab1_HLayout;

    QWidget *tab2;
    QLabel *tab2_screen;
    QLabel *tab2_label;
    QHBoxLayout *tab2_HLayout;

    QPushButton *button;

    cv::Mat tab1_image;
    cv::Mat tab2_image;
    void updateImage(cv::Mat img, QLabel *label);
};

#endif // WIDGET_H

 

3. widget.cpp

#include "widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent)
{
    tabWidget = new QTabWidget();
    tabWidget->setFixedSize(300, 300);
    

    // TAB 1
    tab1 = new QWidget();
    tabWidget->addTab(tab1, QString("TAB 1"));

    tab1_screen = new QLabel();

    std::string fileName1 = "/home/yasun95/qt_ws/tab1/irene2.jpeg";
    tab1_image = cv::imread(fileName1);
    cv::resize(tab1_image, tab1_image, cv::Size(190,270));
    cv::cvtColor(tab1_image, tab1_image, cv::COLOR_BGR2RGB);
    updateImage(tab1_image, tab1_screen);

    tab1_label = new QLabel("irene NO.1");

    tab1_HLayout = new QHBoxLayout;
    tab1_HLayout->addWidget(tab1_screen);
    tab1_HLayout->addWidget(tab1_label);
    tab1->setLayout(tab1_HLayout);


    // TAB 2
    tab2 = new QWidget();
    tabWidget->addTab(tab2, QString("TAB 2"));

    tab2_screen = new QLabel();
    
    std::string fileName2 = "/home/yasun95/qt_ws/tab1/irene3.jpeg";
    tab2_image = cv::imread(fileName2);
    cv::resize(tab2_image, tab2_image, cv::Size(190,270));
    cv::cvtColor(tab2_image, tab2_image, cv::COLOR_BGR2RGB);
    updateImage(tab2_image, tab2_screen);

    tab2_label = new QLabel("This is irene");

    tab2_HLayout = new QHBoxLayout;
    tab2_HLayout->addWidget(tab2_screen);
    tab2_HLayout->addWidget(tab2_label);
    tab2->setLayout(tab2_HLayout);

    button = new QPushButton("QUIT");
    button->setFixedSize(150,30);
    connect(button, SIGNAL(clicked()), this, SLOT(close()));

    VLayout = new QVBoxLayout(this);
    VLayout->addWidget(tabWidget);

    HLayout = new QHBoxLayout();
    HLayout->setAlignment(Qt::AlignRight);
    HLayout->addWidget(button);
   
    VLayout->addLayout(HLayout);
    setLayout(VLayout);
}

void Widget::updateImage(cv::Mat img, QLabel *label)
{
    if(img.data) {
        QImage src(img.data, img.cols, img.rows, img.step, QImage::Format_RGB888);
        label->setPixmap(QPixmap::fromImage(src));
    }
    else {
        label->setText("NO IMAGE");
    }
}

Widget::~Widget()
{
}

 

※  사용한 사진

 

 

 

irene2.jpeg
irene3.jpeg

 

 

 

 

 


② 코드 분석

 

 

※ 탭 생성

 

 

 

tabWidget = new QTabWidget();
tabWidget->setFixedSize(300,300)

tab1 = new QWidget();
tab2 = new QWidget();

tabWidget->addTab(tab1, QString("TAB 1"));
tabWidget->addTab(tab2, QString("TAB 2"));

 

 

탭 생성을 하기 위해서는 QWidget 클래스인 tab1을 tabWidget에 addTab으로 추가해줘야한다. 이때 addTab의 세 번째 파라미터 QString 부분에 탭 이름을 적어 줘야 한다.(생략된 두 번째 파라미터는 탭에 아이콘을 삽입할 때 사용한다.)

 

 

VLayout = new QVBoxLayout(this);
VLayout->addWidget(tabWidget);
setLayout(VLayout);

 

 

tabWidget 이외의 다른 위젯을 추가하기 위해서 레이아웃을 하나 생성해주어야 한다. 바로 다음에 생성할 QPushButton 자리를 위해 수직 레이아웃을 하나 생성한다.

 

 

 

 

☞ tab을 인스턴스화 해야하는 이유 ( + 예시 )

 

 

 

//**********************************************
//                잘못된 예시
// 
//               복붙하지 마시오
//
//        잘못된 예시라고 분명히 얘기했다
//
//**********************************************

tabWidget->addTab(new QWidget, QString("TAB1"));
tabWidget->addTab(new QWidget, QString("TAB2"));

button = new QPushButton("Push Button");
button->setFixedSize(150,30);

VLayout = new QVBoxLayout(this);
VLayout->addWidget(button);

tabWidget->setLayout(VLayout);

 

 

 

만약에 addTab의 첫 번째 파라미터에 new QWidget을 삽입하고 tabWidget 자리에 버튼 위젯을 추가해버리면 아래의 결과처럼 TAB1과 TAB2 자리에 모두 버튼이 생겨버린다. 탭을 개별적으로 제어하기 위해서는 tab1, tab2와 같이 인스턴스를 만들어 사용해야 한다.

 

 

tab1 = new QWidget();
tab2 = new QWidget();

 

 

 

 

 

※ 이미지 불러오기

 

 

이미지를 불러오는 것은 OPENCV의 cv::imread 를 이용해서 불러왔다. 라이브러리 링크와 cv 클래스에 대한 설명은 아래 포스트에 있으니 참고바란다.

 

 

 

[QT] 2. QT + OPENCV

☞  메인보드 : Jetson Nano Developer Kit ☞ 운영 체제 : Ubuntu18.04 - JetPack 4.4.1 ☞  IDE : QtCreator ☞  언어 : C++ 19년도 6월인가 그때 처음 OPENCV를 공부했는데 블로그를 시작하고 했던 것을..

95mkr.tistory.com

 

 

 

tab1_screen = new QLabel();
tab2_screen = new QLabel();

 

 

tab1과 tab2를 각각 제어해주기 위해 하나씩 인스턴스를 만들어준다. 

 

 

 

std::string fileName1 = "/home/yasun95/qt_ws/tab1/irene2.jpeg";
tab1_image = cv::imread(fileName1);
cv::resize(tab1_image, tab1_image, cv::Size(190,270));
cv::cvtColor(tab1_image, tab1_image, cv::COLOR_BGR2RGB);
updateImage(tab1_image, tab1_screen);

 

 

파일 이름을 잘못 입력하거나 파일 경로를 제대로 입력하지 않으면 src.empty 라는 에러 메시지가 출력된다. 파일 경로를 모른다면 이미지 파일의 오른쪽 버튼을 눌러 속성을 보면 파일 경로를 확인할 수 있다. 상위 폴더에 적힌 경로와 이미지 이름을 적어주면 된다. size 또한 파일 속성의 그립 탭으로 들어가면 가로 세로 픽셀을 알 수 있다.

 

main.cpp에서 위젯의 크기를 300, 300 으로 설정했기 때문에 그와 비슷한 픽셀의 이미지를 구해왔다.

 

 

void Widget::updateImage(cv::Mat img, QLabel *label)
{
    if(img.data) {
        QImage src(img.data, img.cols, img.rows, img.step, QImage::Format_RGB888);
        label->setPixmap(QPixmap::fromImage(src));
    }
    else {
        label->setText("NO IMAGE");
    }
}

 

 

QLable의 setPixmap은 문자열 대신에 이미지를 입력 가능하게 한다. updataImage 함수는 cv::imread로 부터 불러온 이미지를 QLabel 자리에 출력할 수 있게 해준다. 이미지를 정상적으로 불러오지 못한다면 "NO IMAGE" 라는 글을 출력하도록 추가해놓았다.

 

 

    button = new QPushButton("QUIT");
    button->setFixedSize(150,30);
    connect(button, SIGNAL(clicked()), this, SLOT(close()));

 

 

버튼을 클릭했다는 신호를 받으면 위젯이 종료되는 SIGNAL-SLOT 관계를 만들어준다. 탭과 함께 포함된 수직 레이아웃 아래 쪽에 위치하게 된다. 

 

 

 

 

    VLayout = new QVBoxLayout(this);
    VLayout->addWidget(tabWidget);

    HLayout = new QHBoxLayout();
    HLayout->setAlignment(Qt::AlignRight);
    HLayout->addWidget(button);

 

 

마지막으로 버튼과 함께 레이아웃에 정리해주면 완성이다.

 

 

 

 

 

 

 

 

 


이런 것도 있더라 - QTableWidget(엑셀 표)

 

처음에 Tab에 대한 내용을 찾아보면서 QTableWidget 이란 위젯을 찾아냈다. 결과적으로는 전혀 관계없는 녀석이었지만 이런 것도 있다고 간단히 소개해본다. 생김새를 보아하니 그냥 엑셀의 서식과 같다. 무언가를 정리할 때 사용하면 간편하긴 하겠지만 내가 개발하려고 하는 소프트웨어와는 관계가 없어 나중으로 미뤄버렸다.

 

 

 

 

TableWidget = new QTableWidget(this);
TableWidget->setRowCount(100);
TableWidget->setColumnCount(5);

QStringList TableHeader;
TableHeader<<"TAB1"<<"TAB2"<<"TAB3"<<"TAB4"<<"TAB5";

 

 

혹시 사용할 사람이 있나해서 하나만 얘기해보자면 setRowCount는 왼쪽에 있는 테이블의 행의 수를, setColumnCount는 상단에 있는 테이블의 열의 수를 의미한다. QStringList인 TableHeader와 함께 레이블을 만들어 직접 이름을 붙여줄 수 있으며 이 경우에는 TableHeader에 입력된 문자열과 함께 5 X 100 의 테이블이 생성된다.

 

 

TableWidget->setHorizontalHeaderLabels(TableHeader);
TableWidget->verticalHeader()->setVisible(false);
TableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
TableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
TableWidget->setShowGrid(false);
TableWidget->setStyleSheet("QTableView {selection-background-color: red;}");

 

 

☞ QTableWidget로 코드에 사용한 메서드

 

◎ setHorizontalHeaderLabels() : 레이블을 사용하여 수평 테이블 탭의 이름을 설정한다.

 

◎ verticalHeader()->setVisible(false) : 수직 테이블를 보이지 않게 한다.

 

◎ setEditTriggers(QAbstractItemView::NoEditTriggers) : 사용자가 편집을 가능하는 액션을 지정할 수 있는데 NoEditTriggers의 경우에는 특정 액션 없이 바로 편집을 할 수 있고, DoubleClicked으로 지정하면 칸을 두 번 클릭했을 때 편집이 가능하다. 또한 AllEditTriggers으로 지정하면 모든 액션에 대한 편집이 가능하게 된다.

 

◎ setSelectionMode(QAbstractItemView::SingleSelection) : 테이블의 칸을 선택할 때의 액션을 설정할 수 있다. SingleSelection은 선택할 때 마다 한 개의 칸만 선택이 된다.(새로운 칸을 클릭했을 때 이전에 클릭한 칸은 선택이 취소된다.)

 

◎ setShowGrid(false) : 테이블 칸을 구분하는 경계선을 보이지 않게 한다.

 

◎ setStyleSheet("QTableView {selection-background-color: red;}") : 테이블의 칸을 클릭했을 때 선택된 칸의 배경 색을 지정한다.

 

 

 

 

코드에서 사용되진 않았지만 만약 테이블을 클릭했을 때 전체 행 또는 전체 열을 클릭하고 싶다면

setSelectionBehavior(QAbstractItemView::SelectRows) 또는 setSelectionBehavior(QAbstractItemView::SelectColumns) 로 클릭 결과를 바꿀 수 있으니 참고하기 바란다.

 

 

 


(전체 코드를 보려면 아래의 '더보기' 클릭)

더보기

1. main.cpp

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.resize(600,600);
    w.show();

    return a.exec();
}

 

2. widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QApplication>
#include <QTableWidget>

#include <QHBoxLayout>

class Widget : public QWidget
{
    Q_OBJECT
        
public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private:
    QTableWidget *TableWidget;
    QHBoxLayout *layout;
};

#endif // WIDGET_H

 

3. widget.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent)
{
    TableWidget = new QTableWidget(this);
    TableWidget->setRowCount(100);
    TableWidget->setColumnCount(5);

    QStringList TableHeader;
    TableHeader<<"TAB1"<<"TAB2"<<"TAB3"<<"TAB4"<<"TAB5";
    TableWidget->setHorizontalHeaderLabels(TableHeader);
    TableWidget->verticalHeader()->setVisible(false);
    TableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
    TableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
    TableWidget->setShowGrid(false);
    TableWidget->setStyleSheet("QTableView {selection-background-color: red;}");

    layout = new QHBoxLayout;
    layout->addWidget(TableWidget);
    this->setLayout(layout);
}

Widget::~Widget()
{
}

 

 

 

 

 

 

 

 

 

 

반응형

'IT > QT' 카테고리의 다른 글

[QT] 2. QT + OPENCV  (0) 2020.12.12
[QT] 1. 다양한 위젯 사용하기 (QPushButton, QSlider, QLabel, QSpinBox)  (0) 2020.12.06