728x90

Qt에서 멀티 쓰레딩을 사용하기 위해서 QThread를 상속받아서 처리하는 예제를 소개한 적이 있습니다.

 

Qt QThread 사용하기(화면 실시간 갱신하기)

센서값을 수집해서 실시간으로 실행창에서 최신화하고 갱신하는 방법에 대해 소개해 드리겠습니다. 먼저 QThread를 상속받는 클래스를 하나 만들어 줍니다. 생성된 qTh.h 파일에 소스코드를 아래

1d1cblog.tistory.com

QtConcurrent를 사용하면 간단하게 비동기 멀티 쓰레딩을 할 수 있습니다. 이를 Qt에서 제공하는 Example을 같이 살펴보며 필요한 부분에 대해 같이 공부해보려 합니다.

 

Example은 Qt Creator > Examples에서 concurrent로 검색하여 첫 번째 예제인 Image Scaling을 사용하려 합니다.

전체 함수를 다 보는 것보다 필요한 부분만 순서대로 보겠습니다. 주석에 나와있는 번호를 따라가면 UI 생성 및 Connect 관계에 대해서는 쉽게 파악이 가능합니다.

 

그리고 중간에 비동기 처리를 확인하기 위해 Debug Message를 출력하는 함수를 추가로 사용하였습니다.

void DebugMsg(const QString& sMsg)
{
    qDebug() << QTime::currentTime().toString("[mm:ss:zzz]") + sMsg;
}

 

[3] 부분인 DownloadDialog를 실행 후 확인을 눌렀을 때 진행되는 부분부터 보겠습니다. DownloadDialog에서는 이미지의 URL들을 관리하고 확인을 눌렀을 때 getUrls를 통해 Url 정보를 가져옵니다.

//! [3]
void Images::process()
{
    // Clean previous state
    replies.clear();
    addUrlsButton->setEnabled(false);

    if (downloadDialog->exec() == QDialog::Accepted) {

        const auto urls = downloadDialog->getUrls();
        if (urls.empty())
            return;

        cancelButton->setEnabled(true);

        initLayout(urls.size());

        downloadFuture = download(urls);
        statusBar->showMessage(tr("Downloading..."));
//! [3]

        //! [4]
        downloadFuture
                .then([this](auto) {
                    cancelButton->setEnabled(false);
                    updateStatus(tr("Scaling..."));
                    //! [16]
                    scalingWatcher.setFuture(QtConcurrent::run(Images::scaled,
                                                               downloadFuture.results()));
                    //! [16]
                })
        //! [4]
        //! [5]
                .onCanceled([this] {
                    updateStatus(tr("Download has been canceled."));
                })
                .onFailed([this](QNetworkReply::NetworkError error) {
                    updateStatus(tr("Download finished with error: %1").arg(error));
                    // Abort all pending requests
                    abortDownload();
                })
                .onFailed([this](const std::exception &ex) {
                    updateStatus(tr(ex.what()));
                })
        //! [5]
                .then([this]() {
                    cancelButton->setEnabled(false);
                    addUrlsButton->setEnabled(true);
                });
    }
}

 

가져온 Url을 Donwload 함수로 넘기게 됩니다.

//! [8]
QFuture<QByteArray> Images::download(const QList<QUrl> &urls)
{
//! [8]
//! [9]
    DebugMsg("DonwloadStart");
    QSharedPointer<QPromise<QByteArray>> promise(new QPromise<QByteArray>());
    promise->start();
//! [9]

    //! [10]
    for (const auto &url : urls) {
        DebugMsg("Donwload " + url.toString());
        QSharedPointer<QNetworkReply> reply(qnam.get(QNetworkRequest(url)));
        replies.push_back(reply);
    //! [10]

    //! [11]
        QtFuture::connect(reply.get(), &QNetworkReply::finished).then([=] {
            if (promise->isCanceled()) {
                if (!promise->future().isFinished())
                    promise->finish();
                return;
            }

            if (reply->error() != QNetworkReply::NoError) {
                if (!promise->future().isFinished())
                    throw reply->error();
            }
        //! [12]
            promise->addResult(reply->readAll());
            DebugMsg("Get Data");

            // Report finished on the last download
            if (promise->future().resultCount() == urls.size()) {
                DebugMsg("Finish promis");
                promise->finish();
            }
        //! [12]
        }).onFailed([promise] (QNetworkReply::NetworkError error) {
            promise->setException(std::make_exception_ptr(error));
            promise->finish();
        }).onFailed([promise] {
            const auto ex = std::make_exception_ptr(
                        std::runtime_error("Unknown error occurred while downloading."));
            promise->setException(ex);
            promise->finish();
        });
    }
    //! [11]

//! [13]
    DebugMsg("Return");
    return promise->future();
}

여기서 QPromise와 QFuture을 사용하게 됩니다. C++에서 Promise와 Furture은 같이 사용하게 되는데 Furture(미래)에 데이터를 돌려주겠다는 Promise(약속)이라고 생각하면 됩니다. QPromise의 비동기 처리를 QFurture에 전달하는 방식입니다. QFurtuer의 Connect를 통해 Signal 처리를 하게 되고 혹은 QFurture::waitforFinish를 통해 비동기 처리가 완료될 때까지 기다릴 수도 있습니다.

 

for문으로 전달받은 Url들을 요청하게 되는데 이에 대한 결과는 QNetworkReply::Finish Signal을 QtFurture::connect로 묶어 처리가 됩니다. 아래 로그를 확인하면 Download 후 바로 Promise가 가지고 있는 QFurture을 Return하게 됩니다. 그 후 요청한 Url에 대한 결과가 도착하고 Url 개수만큼의 완료가 진행 됐다면 QPromise는 종료합니다.

"[23:11:039]DonwloadStart"
"[23:11:039]Donwload https://img1.daumcdn.net/thumb/..."
"[23:11:504]Donwload https://img1.daumcdn.net/thumb/..."
"[23:11:505]Return"
"[23:11:505]Downloading"
"[23:11:724]Get Data"
"[23:11:724]Get Data"
"[23:11:724]Finish promis"

그리고 다시 Process 함수로 돌아와서 QPromise가 finish 됨에 따라 QFurtuer의 then의 lambda 함수가 실행됩니다.

//! [3]
void Images::process()
{
    // Clean previous state
    replies.clear();
    addUrlsButton->setEnabled(false);

    if (downloadDialog->exec() == QDialog::Accepted) {

        const auto urls = downloadDialog->getUrls();
        if (urls.empty())
            return;

        cancelButton->setEnabled(true);

        initLayout(urls.size());

        downloadFuture = download(urls);
        statusBar->showMessage(tr("Downloading..."));
//! [3]

        //! [4]
        downloadFuture
                .then([this](auto) {
                    cancelButton->setEnabled(false);
                    updateStatus(tr("Scaling..."));
                    //! [16]
                    scalingWatcher.setFuture(QtConcurrent::run(Images::scaled,
                                                               downloadFuture.results()));
                    //! [16]
                })
        //! [4]
        //! [5]
                .onCanceled([this] {
                    updateStatus(tr("Download has been canceled."));
                })
                .onFailed([this](QNetworkReply::NetworkError error) {
                    updateStatus(tr("Download finished with error: %1").arg(error));
                    // Abort all pending requests
                    abortDownload();
                })
                .onFailed([this](const std::exception &ex) {
                    updateStatus(tr(ex.what()));
                })
        //! [5]
                .then([this]() {
                    cancelButton->setEnabled(false);
                    addUrlsButton->setEnabled(true);
                });
    }
}

여기서 QFurtherWatcher라는 객체에 QtConcurrent::run의 Return을 넘겨주게 됩니다. QFurtherWatcher는 QFurture에 대한 상태를 체크하고 QFurture가 Singal 상태가 됐을 때 실행할 Slot을 connect 시켜주었습니다.

Images::Images(QWidget *parent) : QWidget(parent), downloadDialog(new DownloadDialog(this))
{
    resize(800, 600);

    ...

//! [6]
    connect(&scalingWatcher, &QFutureWatcher<QList<QImage>>::finished, this, &Images::scaleFinished);
//! [6]
}

//! [15]
void Images::scaleFinished()
{
    DebugMsg("Sacle Finished");
    const OptionalImages result = scalingWatcher.result();
    if (result.has_value()) {
        const auto scaled = result.value();
        showImages(scaled);
        updateStatus(tr("Finished"));
    } else {
        updateStatus(tr("Failed to extract image data."));
    }
    addUrlsButton->setEnabled(true);
}

최종적으로 실행된 로그를 보면 아래와 같습니다.

"[23:11:039]DonwloadStart"
"[23:11:039]Donwload https://img1.daumcdn.net/thumb/..."
"[23:11:504]Donwload https://img1.daumcdn.net/thumb/..."
"[23:11:505]Return"
"[23:11:505]Downloading"
"[23:11:724]Get Data"
"[23:11:724]Get Data"
"[23:11:724]Finish promis"
"[23:11:724]Scaling"
"[23:11:724]Scale Image"
"[23:11:752]Sacle Finished"

정리해보자면 QThread를 사용하지 않으면서 비동기 처리를 위해선 QFurture, QPromise 등을 사용하면서 종료를 대기 혹은 신호를 받거나 혹은 QFurtureWatcher에 QFurture를 등록 후 사용도 가능합니다.

 

QFurture를 간단히 사용하기 위해서는 Qt::Concorrent::run을 통해 함수 포인터와 인자를 넘겨줌으로써 그 처리의 QFurture를 반환받아 위처럼 사용도 가능합니다.

.

728x90
728x90

QProcess를 이용해서 cmd 명령어를 실행 후 결과를 얻어오는 코드입니다.

QProcess *m_process = new QProcess(this);

QString program = "ipconfig";
QStringList arguments;

arguments << "/all";

m_process->start(program, arguments);
m_process->waitForFinished(3000);

QByteArray result = m_process->readAll();
auto converter = QStringDecoder(QStringDecoder::System);
QString string = converter(result);
qDebug() << string;

m_process->close();
delete m_process;
728x90
728x90

100mb가 넘는 파일을 push 할 때 remote: GitLab: You are attempting to check in one or more blobs which exceed the 100.0MiB limit: 오류가 뜨는 경우가 있습니다. 이럴 경우에는 git lfs를 설정해주어야 합니다.

 

먼저 https://git-lfs.com/에서 Download 해줍니다.

 

Git Large File Storage

Git Large File Storage (LFS) replaces large files such as audio samples, videos, datasets, and graphics with text pointers inside Git, while storing the file contents on a remote server like GitHub.com or GitHub Enterprise.

git-lfs.com

다 설치가 되었으면 해당 파일 경로로 이동해 아래 명령어를 입력합니다.

git lfs track "file.txt"

그러면 .gitatrribute라는 파일이 생기고 이제 push하면 제대로 업로드가 됩니다.

728x90

'Programming > Git' 카테고리의 다른 글

git revert  (0) 2020.06.04
git reset  (0) 2020.06.04
git checkout  (0) 2020.05.24
git diff  (0) 2020.05.24
git add, git commit, git status, git log  (0) 2020.05.24
728x90

Combobox에 따라 Layout에 있는 Label을 바꾸려고 합니다. Combobox에 index가 바뀌면 replaceWidget 함수를 호출하도록 작성했습니다.

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QLabel* pLabel = new QLabel("test1");
    QLabel* pLabel2 = new QLabel("test2");

    labelList.append(pLabel);
    labelList.append(pLabel2);

    ui->comboBox->addItem(pLabel->text(), 0);
    ui->comboBox->addItem(pLabel2->text(),1);

    ui->verticalLayout->addWidget(pLabel);
    pCurrent = pLabel;
    connect(ui->comboBox, &QComboBox::currentIndexChanged, [&] (int index) {
        auto label = labelList.value(index);
        ui->verticalLayout->replaceWidget(pCurrent, label);
        pCurrent = label;
    });
}

그런데 실제로 Replace 해보면 이전의 Widget이 남아 있습니다.

이럴 경우에는 2가지 해결 방법이 있습니다. 이전의 widget을 delete 시키는 방법과 기존 parent를 없애는 방법입니다.

하지만 delete를 하는 방식의 큰 문제는 위와 같이 교체된 widget도 나중에 사용해야 하는 경우에는 맞지 않습니다. 이럴 때는 교체된 widget의 Parent를 0으로 넘겨주면 됩니다.

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QLabel* pLabel = new QLabel("test1");
    QLabel* pLabel2 = new QLabel("test2");

    labelList.append(pLabel);
    labelList.append(pLabel2);

    ui->comboBox->addItem(pLabel->text(), 0);
    ui->comboBox->addItem(pLabel2->text(),1);

    ui->verticalLayout->addWidget(pLabel);
    pCurrent = pLabel;
    connect(ui->comboBox, &QComboBox::currentIndexChanged, [&] (int index) {
        auto label = labelList.value(index);
        ui->verticalLayout->replaceWidget(pCurrent, label);
        pCurrent->setParent(0);
        pCurrent = label;
    });
}

728x90
728x90

Qt를 설치할 때 볼 수 있던 아래와 같은 구조가지는 UI Component를 TreeWidget이라고 합니다. Qt에서 TreeWidget을 사용하는 법에 대해 간단히 소개하려고 합니다.

먼저 UI는 아래와 같이 구성했습니다. 상단에 QTreeWidget 그리고 하단에는 선택한 Item의 Text를 확인할 수 있는 Label과 선택된 Item에 자식 Item을 추가할 수 있는 Insert와 해당 Item을 지울 Delete 버튼입니다.

먼저 간단하게 Header와 몇가지 Item을 추가해보겠습니다.

#include "mainwindow.h"
#include "./ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QTreeWidgetItem* headerItem = new QTreeWidgetItem;
    headerItem->setText(0, "Name");
    headerItem->setText(1, "Price");
    ui->treeWidget->setHeaderItem(headerItem);  // header

    QTreeWidgetItem* Parent = new QTreeWidgetItem(ui->treeWidget);
    Parent->setText(0, "Nintendo");

    QTreeWidgetItem* Child = new QTreeWidgetItem(Parent);
    Child->setText(0, "Zelda");
    Child->setText(1, "70000");
}

MainWindow::~MainWindow()
{
    delete ui;
}

Nintendo라는 부모 Item 아래 Zelda 라는 자식 Item을 추가했습니다.

여기에 선택된 Item의 Name을 label에 보여주는 Connect를 추가해보겠습니다.

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QTreeWidgetItem* headerItem = new QTreeWidgetItem;
    headerItem->setText(0, "Name");
    headerItem->setText(1, "Price");
    ui->treeWidget->setHeaderItem(headerItem);  // header

    QTreeWidgetItem* Parent = new QTreeWidgetItem(ui->treeWidget);
    Parent->setText(0, "Nintendo");

    QTreeWidgetItem* Child = new QTreeWidgetItem(Parent);
    Child->setText(0, "Zelda");
    Child->setText(1, "70000");

    connect(ui->treeWidget, &QTreeWidget::itemClicked, [&] (QTreeWidgetItem *item, int column) {
        QString sName = item->text(0);
        ui->lbTreeItemTitle->setText(sName);
    });
}

다음으로 Add Child Item, Delete Item 코드입니다.

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QTreeWidgetItem* headerItem = new QTreeWidgetItem;
    headerItem->setText(0, "Name");
    headerItem->setText(1, "Price");
    ui->treeWidget->setHeaderItem(headerItem);  // header

    QTreeWidgetItem* Parent = new QTreeWidgetItem(ui->treeWidget);
    Parent->setText(0, "Nintendo");

    QTreeWidgetItem* Child = new QTreeWidgetItem(Parent);
    Child->setText(0, "Zelda");
    Child->setText(1, "70000");

    connect(ui->treeWidget, &QTreeWidget::itemClicked, [&] (QTreeWidgetItem *item, int column) {
        QString sName = item->text(0);
        ui->lbTreeItemTitle->setText(sName);
    }); // Display Current Selected Item Name(Column = 0)

    connect(ui->pb_AddChild, &QPushButton::clicked, [&] {
        QTreeWidgetItem* pCurrentSelectedItem = ui->treeWidget->currentItem();
        if ( pCurrentSelectedItem == nullptr ) { return; }

        QTreeWidgetItem* pNewChild = new QTreeWidgetItem;
        pNewChild->setText(0, ui->le_ChildTitle->text());
        pCurrentSelectedItem->addChild(pNewChild);
    }); // Add Child Item

    connect(ui->pb_DeleteItem, &QPushButton::clicked, [&] {
        QTreeWidgetItem* pCurrentSelectedItem = ui->treeWidget->currentItem();
        if ( pCurrentSelectedItem == nullptr ) { return; }

        delete pCurrentSelectedItem;
    }); // Delete Current Item
}

다음은 Item을 접고 펼칠때마다 Column의 크기를 재 조정할 수 있는 기능입니다. 

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QTreeWidgetItem* headerItem = new QTreeWidgetItem;
    headerItem->setText(0, "Name");
    headerItem->setText(1, "Price");
    ui->treeWidget->setHeaderItem(headerItem);  // header

    QTreeWidgetItem* Parent = new QTreeWidgetItem(ui->treeWidget);
    Parent->setText(0, "Nintendo");

    QTreeWidgetItem* Child = new QTreeWidgetItem(Parent);
    Child->setText(0, "Zelda");
    Child->setText(1, "70000");

    connect(ui->treeWidget, &QTreeWidget::itemClicked, [&] (QTreeWidgetItem *item, int column) {
        QString sName = item->text(0);
        ui->lbTreeItemTitle->setText(sName);
    }); // Display Current Selected Item Name(Column = 0)

    connect(ui->pb_AddChild, &QPushButton::clicked, [&] {
        QTreeWidgetItem* pCurrentSelectedItem = ui->treeWidget->currentItem();
        if ( pCurrentSelectedItem == nullptr ) { return; }

        QTreeWidgetItem* pNewChild = new QTreeWidgetItem;
        pNewChild->setText(0, ui->le_ChildTitle->text());
        pCurrentSelectedItem->addChild(pNewChild);
    }); // Add Child Item

    connect(ui->pb_DeleteItem, &QPushButton::clicked, [&] {
        QTreeWidgetItem* pCurrentSelectedItem = ui->treeWidget->currentItem();
        if ( pCurrentSelectedItem == nullptr ) { return; }

        delete pCurrentSelectedItem;
    }); // Delete Current Item

    connect(ui->treeWidget, &QTreeWidget::itemExpanded, [&] (QTreeWidgetItem *item) {
        ui->treeWidget->resizeColumnToContents(0);
        ui->treeWidget->resizeColumnToContents(1);
    }); // Resize Columns

    connect(ui->treeWidget, &QTreeWidget::itemCollapsed, [&] (QTreeWidgetItem *item) {
        ui->treeWidget->resizeColumnToContents(0);
        ui->treeWidget->resizeColumnToContents(1);
    }); // Resize Columns
}

마지막으로 TreeWidgetItem에 체크박스를 추가하고 체크 상태에 따라 부모와 자식의 상태도 자동으로 변하는 기능입니다.

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QTreeWidgetItem* headerItem = new QTreeWidgetItem;
    headerItem->setText(0, "Name");
    headerItem->setText(1, "Price");
    ui->treeWidget->setHeaderItem(headerItem);  // header

    QTreeWidgetItem* Parent = new QTreeWidgetItem(ui->treeWidget);
    Parent->setCheckState(0, Qt::Unchecked);
    Parent->setFlags(Parent->flags() | Qt::ItemIsAutoTristate | Qt::ItemIsUserCheckable );
    Parent->setText(0, "Nintendo");

    QTreeWidgetItem* Child = new QTreeWidgetItem(Parent);
    Child->setFlags(Child->flags() | Qt::ItemIsUserCheckable );
    Child->setCheckState(0, Qt::Unchecked);
    Child->setText(0, "Zelda");
    Child->setText(1, "70000");

    Child = new QTreeWidgetItem(Parent);
    Child->setFlags(Child->flags() | Qt::ItemIsUserCheckable );
    Child->setCheckState(0, Qt::Unchecked);
    Child->setText(0, "Mario");
    Child->setText(1, "75000");

    Child = new QTreeWidgetItem(Parent);
    Child->setFlags(Child->flags() | Qt::ItemIsUserCheckable );
    Child->setCheckState(0, Qt::Unchecked);
    Child->setText(0, "Pockemon");
    Child->setText(1, "60000");

    connect(ui->treeWidget, &QTreeWidget::itemClicked, [&] (QTreeWidgetItem *item, int column) {
        QString sName = item->text(0);
        ui->lbTreeItemTitle->setText(sName);
    }); // Display Current Selected Item Name(Column = 0)

    connect(ui->pb_AddChild, &QPushButton::clicked, [&] {
        QTreeWidgetItem* pCurrentSelectedItem = ui->treeWidget->currentItem();
        if ( pCurrentSelectedItem == nullptr ) { return; }

        QTreeWidgetItem* pNewChild = new QTreeWidgetItem;
        pNewChild->setText(0, ui->le_ChildTitle->text());
        pCurrentSelectedItem->addChild(pNewChild);
    }); // Add Child Item

    connect(ui->pb_DeleteItem, &QPushButton::clicked, [&] {
        QTreeWidgetItem* pCurrentSelectedItem = ui->treeWidget->currentItem();
        if ( pCurrentSelectedItem == nullptr ) { return; }

        delete pCurrentSelectedItem;
    }); // Delete Current Item

    connect(ui->treeWidget, &QTreeWidget::itemExpanded, [&] (QTreeWidgetItem *item) {
        ui->treeWidget->resizeColumnToContents(0);
        ui->treeWidget->resizeColumnToContents(1);
    }); // Resize Columns

    connect(ui->treeWidget, &QTreeWidget::itemCollapsed, [&] (QTreeWidgetItem *item) {
        ui->treeWidget->resizeColumnToContents(0);
        ui->treeWidget->resizeColumnToContents(1);
    }); // Resize Columns
}

자식 Item에 Check 시에는 자식이 다수일 경우에는 Qt::PartiallyChecked이고 다 선택되면 Qt::Checked 상태 그리고 부모에서 Check 시에는 자식 Item이 모두 Qt::Checked 상태로 변하게 됩니다.

만약에 자식 item에는 checkbox를 두지 않고 싶으면 부모에 Qt::ItemIsAutoTristate는 제거하면 됩니다. 

728x90
728x90

먼저 Ui에 tablewidget을 하나 추가합니다.

 

Ui에서 더블클릭하여 Column을 추가하거나 cpp 코드에서 아래와 같이 컬럼을 추가합니다.

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    AddColumns();
}

void MainWindow::AddColumns()
{
    for(int i = 0 ; i < 16 ; i++) {
        ui->tableWidget->insertColumn(i);
    }
    
    // ui->tableWidget->setColumnCount(16);

    QStringList lHeader;
    lHeader << "one" << "two";

    ui->tableWidget->setHorizontalHeaderLabels(lHeader);
}

setHorizontalHeaderLabels에 QStringList를 넘겨 컬럼의 이름을 변경할 수 있습니다.

다음으로 row아이템들을 추가합니다.

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    AddColumns();		// column 추가
    AddItem();			// row와 아이템 추가
}

void MainWindow::AddItem()
{
    QTableWidget* table = ui->tableWidget;
    QTableWidgetItem* item = new QTableWidgetItem;
    QString tmp;
    int     nRow = 0;

    table->insertRow(nRow);

    item = new QTableWidgetItem;
    tmp.sprintf("row %d", nRow+1);
    item->setText(tmp);
    table->setVerticalHeaderItem(nRow,item);        // row의 이름 설정

    item = new QTableWidgetItem;
    item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
    item->setBackground(QBrush(QColor(0,0,255)));
    table->setItem(0,8,item);
    item->setText("item" + QString::number(nRow));
    nRow++;
}

column과 비슷하게 setVerticalHeaderItem으로 row의 이름을 설정할 수 있습니다.

setVerticalHeaderItem을 하지 않으면 index 번호대로 설정이 됩니다.

setItem으로 원하는 row, coulmn위치에 아이템을 배치할 수 있습니다.

Item에 background나 alignment도 부여할 수 있습니다.

 

셀을 병합해서 사용하고 싶을때는 setSpan 함수를 사용합니다.

row, column의 인덱스를 전달하고 그리고 몇개의 셀을 병합할지를 전달합니다.

void QTableView::setSpan(int row, int column, int rowSpanCount, int columnSpanCount)
void MainWindow::AddItem()
{
    QTableWidget* table = ui->tableWidget;
    QTableWidgetItem* item = new QTableWidgetItem;
    QString tmp;
    int     nRow = 0;

    table->insertRow(nRow);

    item = new QTableWidgetItem;
    tmp.sprintf("row %d", nRow+1);
    item->setText(tmp);
    table->setVerticalHeaderItem(nRow,item);        // row의 이름 설정

    table->setSpan(nRow,8,1,8);
    item = new QTableWidgetItem;
    item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
    item->setBackground(QBrush(QColor(0,0,255)));
    table->setItem(0,8,item);
    item->setText("item" + QString::number(nRow));
    nRow++;
}

다음으로 column과 row를 표의 크기에 맞게 세팅하는 방법입니다.

 

row와 coulmn을 추가하다보면 아래와 같이 한번에 모든 셀을 보는것이 힘들어집니다.

그럴땐 아래와 같은 옵션을 설정하면 모든 row와 coulmn의 셀을 볼수있게 맞춰서 출력이 됩니다.

ui->tableWidget->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
ui->tableWidget->verticalHeader()->setResizeMode(QHeaderView::Stretch);

 

// 23.11.21 추가

최신 버전 QHeaderView에는 setResizeMode 함수가 없으니 setSectionResizeMode(ResizeMode mode) 혹은 특정 Column/Row에만 처리하고 싶다면 setSectionResizeMode(int logicalIndex, ResizeMode mode)를 사용하면 됩니다.

 

아무 설정없이 기본적으론 tablewidget의 아이템을 더블클릭하면 아이템을 수정할 수 있는데 이걸 방지하기 위해 아래 코드를 추가합니다.

ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);

그리고 vertical header(row1 ~ 16)에 라인이 안보이는데 아래와 같이 StyleSheet을 적용하면 verital header에도 라인이 그려지게 됩니다.

ui->tableWidget->verticalHeader()->setStyleSheet("QHeaderView::section{"
                                   "border-top:0px solid #E5E5E5;"
                                   "border-left:0px solid #E5E5E5;"
                                   "border-right:1px solid #E5E5E5;"
                                   "border-bottom: 1px solid #D8D8D8;"
                                   "background-color:white;"
                                   "padding:4px}");

table안에 아이템의 입력 값을 제한하기 위해서는 Item에 Validator를 적용한 QLineEdit를 Set해주면 됩니다.

QLineEdit* pLineEdit = new QLineEdit;
pLineEdit->installEventFilter(this);
pLineEdit->setFrame(false);

QIntValidator* pValidator = new QIntValidator(0,100);
pLineEdit->setValidator(pValidator);

ui->tablewidget->setCellWidget(nRow,nColumn,pLineEdit);

QLineEdit에 적용할 수 있는 Validator에 대한 내용은 아래 링크를 참고 부탁드립니다.

 

Qt LineEdit 입력 값 제한하기

QLineEdit을 사용할 때 입력 값을 제한하고 싶을 때가 있습니다. 이럴 때 사용할 수 있는게 QValidator 입니다. QValidator Class | Qt GUI 5.15.3 QValidator Class The QValidator class provides validation of..

1d1cblog.tistory.com

 

728x90
728x90

하나의 Signal을 처리할 때 여러 Slot 함수를 만들지 않고 싶다면 sender() 함수를 사용하여 처리할 수 있습니다. 이전 포스팅에서 사용했던 QSplitter로 예를 들어보겠습니다.

 

Horizontal, Vertical QSplitter가 있습니다. QSplitter의 이동이 끝나면 splitterMoved라는 Signal이 발생하고 이 Signal이 발생됐을 때 하나의 Slot 함수로 처리하고 싶습니다.

connect(horizonSplitter, SIGNAL(splitterMoved(int, int)), this, SLOT(slot_setSpliterState(int, int)));
connect(verticalSplitter, SIGNAL(splitterMoved(int, int)), this, SLOT(slot_setSpliterState(int, int)));

이럴 때 slot에서는 어떤 QObject에서 Signal이 발생한 것인지 알아야 그에 맞는 처리를 할 수 있습니다. 이럴 때 사용하는 것이 sender 함수 입니다.

void MainWindow::slot_setSpliterState(int pos, int index)
{
    const QString sKeyHorizonSplitter = "Spliiter/horizon";
    const QString sKeyVerticalSplitter = "Spliiter/vertical";
    
    QObject* obj = sender();
    if ( obj == nullptr ) { return; }

    if ( obj == horizonSplitter ) {
        qsetting->setValue(sKeyHorizonSplitter, horizonSplitter->saveState());
    } else if ( obj == verticalSplitter ) {
        qsetting->setValue(sKeyVerticalSplitter, verticalSplitter->saveState());
    }
}

위 코드는 sender를 통해 splitter를 비교 후 해당 splitter에 맞는 key-value로 세팅하는 코드입니다.

728x90
728x90

QSplitter를 사용하면 화면에 배치된 UI Component의 크기를 마우스 드래그로 동적으로 조절할 수 있습니다.


위와 같은 ui가 있을 때 TabWidget 안에 Group Box 간, 그리고 TabWidget과 하단의 Text Browser 간에 사이즈를 조절하고 싶을 때 아래와 같이 코드를 작성합니다.

#include <QSplitter>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QSplitter* sp = new QSplitter();
    sp->addWidget(ui->groupBox);
    sp->addWidget(ui->groupBox_2);
    ui->horizontalLayout->addWidget(sp);

    QSplitter* sp2 = new QSplitter();
    sp2->addWidget(ui->tabWidget);
    sp2->addWidget(ui->textBrowser);
    sp2->setOrientation(Qt::Vertical);
    ui->gridLayout->addWidget(sp2);
}

QSplitter를 생성하고 조절하고자 하는 위젯을 AddWidget을 통해 추가 후 Layout에 추가해주면 됩니다.
 
다음으로 추가할 수 있는 속성들에 대한 함수입니다. 

  • setOrientation : Splitter의 방향을 지정할 수 있습니다.
  • setChildrenCollapsible : Widget의 크기를 최소한 이상으로 줄였을 때 숨길 것인지 설정할 수 있습니다.
  • setOpaqueResize : false로 설정하면 드래그가 종료 될 때 사이즈를 조절하게 됩니다.
  • saveState / restoreState : splitter의 size를 QSettings를 통해 저장할 수 있습니다.

 

728x90

+ Recent posts