728x90

QML으로 간단하게 TimeTimer라는 프로그램을 제작하면서 다른 기능도 사용해 보겠습니다. 

TimeTimer는 위와 같이 생긴 시계인데 예전에 크롬 확장 프로그램 만들기에서도 진행했었는데 예제로 사용하기 괜찮아서 이번에는 Qt/QML로 한번 만들어보면서 익숙해져보려 합니다.

 

크롬 확장프로그램 Time Timer 개발하기 - 4

이제 만들어진 시계 타이머 둘레에 숫자를 입력해보겠습니다. 현재 만든 시계의 좌표는 아래와 같습니다. 이 원의 둘레 주변에 1 ~ 12의 숫자를 넣어주려고 합니다. 우선 간단하게 12시에만 숫자

1d1cblog.tistory.com

먼저 완성된 UI부터 보여드리겠습니다. Main 화면 UI입니다. 가장 위부터 설정할 수 있는 window를 띄워줄 Button, 시계 UI, 시간 설정할 수 있는 Spinbox, Start / Pause / Stop Button입니다. 시간 UI에는 남은 시간도 보여줄 수 있는 Label도 있습니다.

UI에 사용된 TimeTimer.qml 파일입니다. 

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15

Window {
    id:windows
    property int windowWidth : 300;
    property int windowHeight : 420;

    property double dOpacity: 1
    property bool bAlwaysOnTop : false
    opacity: dOpacity

    width: windowWidth
    height: windowHeight

    minimumWidth: windowWidth
    maximumWidth: windowWidth

    minimumHeight: windowHeight
    maximumHeight: windowHeight

    visible: true
    signal emit_Start();
    signal emit_Pause();
    signal emit_Stop();

    Item {
        anchors.fill: parent
        ColumnLayout {
            spacing : 10
            anchors.centerIn : parent
            Button {
                Layout.preferredWidth: 40
                Layout.preferredHeight:30
                Layout.alignment: Qt.AlignRight
                icon.name: "setting"
                icon.source: "qrc:/icon/setting.png"
                onClicked: {
                    timerMgr.showDialog()
                }
            }

            Rectangle {
                border.width: 1
                Layout.preferredWidth: 280
                Layout.preferredHeight:280

                Canvas {
                    id:backgroundCanvas
                    anchors.fill: parent
                    property int centerX: parent.width / 2
                    property int centerY: parent.height / 2
                    property int radius : parent.width / 2 - 39
                    property color backColor : "white"

                    onPaint: {
                        var ctx = backgroundCanvas.getContext("2d");
                        ctx.beginPath()
                        ctx.fillStyle = backColor
                        ctx.arc(centerX, centerY, radius, 0, 2*Math.PI)
                        ctx.fill()
                        ctx.closePath()
                    }
                }

                Canvas {
                    id:timerCanvas
                    objectName:"timeCanvas"
                    anchors.fill: parent
                    property int centerX: parent.width / 2
                    property int centerY: parent.height / 2
                    property int radius : parent.width / 2 - 41

                    property int setMinute: 60
                    property int setSecond: setMinute*60

                    property color timeColor : "red"

                    onPaint: {
                        var time = setSecond;
                        var end = (45-(time/60))*6;
                        var ctx = timerCanvas.getContext("2d");
                        ctx.reset()
                        ctx.beginPath()
                        ctx.fillStyle = timeColor
                        ctx.moveTo(centerX,centerY);
                        ctx.arc(centerX, centerY, radius, (Math.PI/180)*end, (Math.PI/180)*270);
                        ctx.fill()
                        ctx.closePath()
                    }

                    function rePaint() {
                        timerCanvas.requestPaint()
                    }
                }

                Canvas {
                    id:clockNumber
                    anchors.fill: parent

                    property int centerX: parent.width / 2
                    property int centerY: parent.height / 2

                    onPaint: {
                        var ctx = clockNumber.getContext("2d");
                        ctx.font = "20px sans-serif";
                        ctx.textAlign = "center";
                        ctx.textBaseline = "middle";
                        ctx.fillStyle = "black";
                        ctx.beginPath()
                        ctx.translate(centerX,centerY);
                        for ( var number = 1; number <= 12; number++ ){
                            var angle = (Math.PI / 180) * 30;
                            ctx.rotate( angle );
                            ctx.fillText( number, 0, -120);
                        }
                        ctx.closePath()
                    }
                }

                Rectangle {
                    anchors.centerIn: parent
                    opacity: 0.5
                    width : 70
                    height : 40
                    Label {
                        id:remainTime
                        anchors.fill: parent
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        font.pixelSize: 15
                        text : (parseInt(timerCanvas.setSecond / 60)).toString().padStart(2,'0')
                               + ":" + (parseInt(timerCanvas.setSecond % 60)).toString().padStart(2,'0')
                    }
                }
            }

            SpinBox {
                id:spinBox
                Layout.alignment: Qt.AlignHCenter
                Layout.preferredHeight:35
                from: 0
                value: 60
                to: 60
                stepSize: 1
                editable: true
                onValueChanged: {
                    timerCanvas.setMinute = value
                    timerCanvas.requestPaint()
                }

                Keys.onPressed: {
                    if ( event.key === Qt.Key_Enter || event.key === Qt.Key_Return ) {
                        focus = false
                    }
                }
            }

            RowLayout {
                spacing : 10
                Layout.alignment: Qt.AlignHCenter
                Button {
                    Layout.preferredWidth: 40
                    Layout.preferredHeight:30
                    icon.name: "start"
                    icon.source: "qrc:/icon/start.png"
                    onClicked: {
                        windows.emit_Start()
                    }
                }
                Button {
                    Layout.preferredWidth: 40
                    Layout.preferredHeight:30
                    icon.name: "pause"
                    icon.source: "qrc:/icon/pause.png"
                    onClicked: {
                        windows.emit_Pause()
                    }
                }
                Button {
                    Layout.preferredWidth: 40
                    Layout.preferredHeight:30
                    icon.name: "reset"
                    icon.source: "qrc:/icon/reset.png"
                    onClicked: {
                        windows.emit_Stop()
                    }
                }
            }
        }
    }
}

ui를 좀 더 자세히 뜯어서 보겠습니다. Start / Pause / Stop Button을 클릭했을 때 Signal을 발생할 수 있게 Signal을 선언해 놨습니다.

Window {
    id:windows
    property int windowWidth : 300;
    property int windowHeight : 420;

    property double dOpacity: 1
    property bool bAlwaysOnTop : false
    
    opacity: dOpacity

    width: windowWidth
    height: windowHeight

    minimumWidth: windowWidth
    maximumWidth: windowWidth

    minimumHeight: windowHeight
    maximumHeight: windowHeight

    visible: true
    signal emit_Start();
    signal emit_Pause();
    signal emit_Stop();

 

UI들은 ColumnLayout과 RowLayout으로 배치해 주었습니다. 해당 Layout을 사용하기 위해서는 QtQuick.Layouts를 import 해주어야 합니다. 가장 상단에 있는 버튼에는 클릭 시 Q_INVOKABLE로 등록된 함수를 호출해주고 있습니다.

Item {
        anchors.fill: parent
        ColumnLayout {
            spacing : 10
            anchors.centerIn : parent
            Button {
                Layout.preferredWidth: 40
                Layout.preferredHeight:30
                Layout.alignment: Qt.AlignRight
                icon.name: "setting"
                icon.source: "qrc:/icon/setting.png"
                onClicked: {
                    timerMgr.showDialog()
                }
            }
		...
        }
            
    }

다음으로 메인인 시계 부분입니다. Canvas를 사용하였고 qml도 자바스크립트를 사용하기 때문에 자바스크립트 함수 사용이 가능했는데 덕분에 이전 예제에서 사용했던 코드를 그대로 사용할 수 있었습니다.

추가로 남은 시간을 그려주는 timerCanvas에는 function이 하나 있는데 이 함수 호출 시에 requestPaint 함수가 호출되어 paintEvent가 발생하게 됩니다. 이 rePaint 함수는 cpp에서 QMetaObject::invokeMethod를 통해 qml의 함수를 직접 호출하고 있습니다.

Rectangle {
    border.width: 1
    Layout.preferredWidth: 280
    Layout.preferredHeight:280

    Canvas {
        id:backgroundCanvas
        anchors.fill: parent
        property int centerX: parent.width / 2
        property int centerY: parent.height / 2
        property int radius : parent.width / 2 - 39
        property color backColor : "white"

        onPaint: {
            var ctx = backgroundCanvas.getContext("2d");
            ctx.beginPath()
            ctx.fillStyle = backColor
            ctx.arc(centerX, centerY, radius, 0, 2*Math.PI)
            ctx.fill()
            ctx.closePath()
        }
    }

    Canvas {
        id:timerCanvas
        objectName:"timeCanvas"
        anchors.fill: parent
        property int centerX: parent.width / 2
        property int centerY: parent.height / 2
        property int radius : parent.width / 2 - 41

        property int setMinute: 60
        property int setSecond: setMinute*60

        property color timeColor : "red"

        onPaint: {
            var time = setSecond;
            var end = (45-(time/60))*6;
            var ctx = timerCanvas.getContext("2d");
            ctx.reset()
            ctx.beginPath()
            ctx.fillStyle = timeColor
            ctx.moveTo(centerX,centerY);
            ctx.arc(centerX, centerY, radius, (Math.PI/180)*end, (Math.PI/180)*270);
            ctx.fill()
            ctx.closePath()
        }

        function rePaint() {
            timerCanvas.requestPaint()
        }
    }

    Canvas {
        id:clockNumber
        anchors.fill: parent

        property int centerX: parent.width / 2
        property int centerY: parent.height / 2

        onPaint: {
            var ctx = clockNumber.getContext("2d");
            ctx.font = "20px sans-serif";
            ctx.textAlign = "center";
            ctx.textBaseline = "middle";
            ctx.fillStyle = "black";
            ctx.beginPath()
            ctx.translate(centerX,centerY);
            for ( var number = 1; number <= 12; number++ ){
                var angle = (Math.PI / 180) * 30;
                ctx.rotate( angle );
                ctx.fillText( number, 0, -120);
            }
            ctx.closePath()
        }
    }

다음은 시간 잔량을 표시해 주는 부분입니다. 여기에는 padStart라는 남은 시간이 한 자리여도 2자리 수로 볼 수 있게 채워주는 padStart 함수가 있기에 따로 설명을 했습니다.

Rectangle {
    anchors.centerIn: parent
    opacity: 0.5
    width : 70
    height : 40
    Label {
        id:remainTime
        anchors.fill: parent
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
        font.pixelSize: 15
        text : (parseInt(timerCanvas.setSecond / 60)).toString().padStart(2,'0')
               + ":" + (parseInt(timerCanvas.setSecond % 60)).toString().padStart(2,'0')
    }
}

마지막으로 Spinbox와 Button들입니다. spinbox는 말 그대로 시간을 설정해 주고 이 시간 값을 바꿀 때마다 시계를 갱신해 주기 위해서 timerCanvas에 requestPaint를 호출하게 됩니다.

spinbox는 editable property를 true로 설정해 Keyboard 입력이 가능하게 해 주었고 큰 Enter키(Qt.Key_Return), 작은 Enter키(Qt.Key_Enter)를 누르면 Focus를 해제시키게 하였습니다.

Button들에서는 signal을 날려주고 이 signal은 TimeTimerMgr에서 connect 되어 Slot함수를 호출하게 됩니다.

SpinBox {
    id:spinBox
    Layout.alignment: Qt.AlignHCenter
    Layout.preferredHeight:35
    from: 0
    value: 60
    to: 60
    stepSize: 1
    editable: true
    onValueChanged: {
        timerCanvas.setMinute = value
        timerCanvas.requestPaint()
    }

    Keys.onPressed: {
        if ( event.key === Qt.Key_Enter || event.key === Qt.Key_Return ) {
            focus = false
        }
    }
}

RowLayout {
    spacing : 10
    Layout.alignment: Qt.AlignHCenter
    Button {
        Layout.preferredWidth: 40
        Layout.preferredHeight:30
        icon.name: "start"
        icon.source: "qrc:/icon/start.png"
        onClicked: {
            windows.emit_Start()
        }
    }
    Button {
        Layout.preferredWidth: 40
        Layout.preferredHeight:30
        icon.name: "pause"
        icon.source: "qrc:/icon/pause.png"
        onClicked: {
            windows.emit_Pause()
        }
    }
    Button {
        Layout.preferredWidth: 40
        Layout.preferredHeight:30
        icon.name: "reset"
        icon.source: "qrc:/icon/reset.png"
        onClicked: {
            windows.emit_Stop()
        }
    }
}

다음으로 main 함수입니다. QQmlApplicationEngine을 관리 객체로 넘겨주고 있습니다.

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "TimeTimerMgr.h"

int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;

    const QUrl url(QStringLiteral("qrc:/TimeTimer.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app,
        [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        }
    , Qt::QueuedConnection);

    engine.load(url);

    TimeTimerMgr timerMgr;
    timerMgr.SetEngine(&engine);
    return app.exec();
}

다음으로 TimeTimerMgr 헤더 파일입니다. 여기에 설정 버튼을 클릭했을 때 호출할 Q_INVOKABLE 함수가 선언되어 있고 각 오브젝트들을 받을 수 있게 QObject 멤버와 QQuickWindows도 가지고 있습니다.

#ifndef TIMETIMERMGR_H
#define TIMETIMERMGR_H

#include <QObject>
#include <QTimer>
#include <QQmlApplicationEngine>
#include <QQmlContext>

#include "ConfigureDialog.h"


class TimeTimerMgr : public QObject
{
    Q_OBJECT
public:
    explicit TimeTimerMgr(QObject *parent = nullptr);
    ~TimeTimerMgr();

    void SetEngine(QQmlApplicationEngine *engine);
    void InitIteragte();

    Q_INVOKABLE void showDialog();

public slots:
    void Slot_Start();
    void Slot_Pause();
    void Slot_Stop();
    void Slot_Timeout();

    void Slot_GetColor(QColor color);
    void Slot_GetOpacity(double dOpacity);
    void Slot_GetAlwaysOnTop(bool bChecked);

signals:

private:
    ConfigureDialog* pDialog;
    QQmlApplicationEngine* m_engine;
    QQuickWindow* m_pWindow;
    QObject* m_pTimeCanvas;
    QTimer timer;
    int m_nTime;
    int m_nCount;
};

#endif // TIMETIMERMGR_H

다음으로 TimeTimerMgr cpp 코드입니다.

#include "TimeTimerMgr.h"
#include <QQmlProperty>

TimeTimerMgr::TimeTimerMgr(QObject *parent)
    : QObject{parent}
    , m_pDialog(NULL)
    , m_engine(NULL)
    , m_pWindow(NULL)
    , m_pTimeCanvas(NULL)
    , m_nTime(0)
    , m_nCount(0)
{
    connect(&timer, SIGNAL(timeout()), this, SLOT(Slot_Timeout()), Qt::UniqueConnection);
}

void TimeTimerMgr::showDialog()
{
    if ( m_pDialog != NULL ) { return; }
    m_pDialog = new ConfigureDialog(this, m_engine);

    connect(m_pDialog, SIGNAL(Emit_setColor(QColor)), this, SLOT(Slot_GetColor(QColor)), Qt::UniqueConnection);
    connect(m_pDialog, SIGNAL(Emit_setOpacity(double)), this, SLOT(Slot_GetOpacity(double)), Qt::UniqueConnection);
    connect(m_pDialog, SIGNAL(Emit_setAlwaysOnTop(bool)), this, SLOT(Slot_GetAlwaysOnTop(bool)), Qt::UniqueConnection);
    connect(m_pDialog, SIGNAL(Emit_Close()), this, SLOT(Slot_DialogClose()), Qt::UniqueConnection);

    TimerSetting settingValue;
    settingValue.color        = m_pTimeCanvas->property("timeColor").value<QColor>();
    settingValue.opacity      = m_pWindow->property("dOpacity").toDouble();
    settingValue.bAlwaysOnTop = m_pWindow->property("bAlwaysOnTop").toBool();

    m_pDialog->Show();
    m_pDialog->SetSettingValue(settingValue);
}

void TimeTimerMgr::Slot_Start()
{
    m_nTime = m_pTimeCanvas->property("setMinute").toInt();
    timer.start(1000);
}

void TimeTimerMgr::Slot_Pause()
{
    timer.stop();
}

void TimeTimerMgr::Slot_Stop()
{
    timer.stop();
    m_pTimeCanvas->setProperty("setSecond", (m_nTime*60));
    QMetaObject::invokeMethod(m_pTimeCanvas, "rePaint");
    m_nCount = 0;
}

void TimeTimerMgr::Slot_Timeout()
{
    m_nCount++;
    m_pTimeCanvas->setProperty("setSecond", (m_nTime*60-m_nCount));
    QMetaObject::invokeMethod(m_pTimeCanvas, "rePaint");

    if ( m_nTime*60 == m_nCount ) {
        timer.stop();
    }
}

void TimeTimerMgr::Slot_GetColor(QColor color)
{
    m_pTimeCanvas->setProperty("timeColor", color);
    QMetaObject::invokeMethod(m_pTimeCanvas, "rePaint");
}

void TimeTimerMgr::Slot_GetOpacity(double dOpacity)
{
    m_pWindow->setProperty("dOpacity", dOpacity);
}

void TimeTimerMgr::Slot_GetAlwaysOnTop(bool bChecked)
{
    m_pWindow->setProperty("bAlwaysOnTop", bChecked);

    m_pWindow->setFlag(Qt::WindowStaysOnTopHint, bChecked);
}

void TimeTimerMgr::Slot_DialogClose()
{
    m_pDialog = NULL;
}

TimeTimerMgr::~TimeTimerMgr()
{
    if ( m_pDialog != NULL ) {
        delete m_pDialog;
        m_pDialog = NULL;
    }
}

void TimeTimerMgr::SetEngine(QQmlApplicationEngine* engine)
{
    m_engine = engine;
    InitIteragte();
}

void TimeTimerMgr::InitIteragte()
{
    if ( m_engine == NULL ) { return; }
    m_engine->rootContext()->setContextProperty("timerMgr",this);
    QList<QObject*> objectList = m_engine->rootObjects();

    if ( objectList.count() < 1 ) { return; }
    m_pWindow = qobject_cast<QQuickWindow*>(objectList.value(0));

    if ( m_pWindow == NULL ) { return; }
    connect(m_pWindow, SIGNAL(emit_Start()), this, SLOT(Slot_Start()), Qt::UniqueConnection);
    connect(m_pWindow, SIGNAL(emit_Pause()), this, SLOT(Slot_Pause()), Qt::UniqueConnection);
    connect(m_pWindow, SIGNAL(emit_Stop()), this, SLOT(Slot_Stop()), Qt::UniqueConnection);

    m_pTimeCanvas = m_pWindow->findChild<QObject*>("timeCanvas");
}

여기도 함수를 좀 나눠서 살펴보겠습니다. main에서 engine을 따로 넘겨주고 있고 이 engine의 rootContext에 TimeTimeMgr 객체를 등록했습니다. 이로써 TimeTimer.qml에서 TimeTimerMgr의 Q_INVOKABLE 함수를 호출할 수 있게 됩니다. 그리고 버튼들을 클릭했을 때 발생하는 SIGNAL을 SLOT들과 connect 시켜줍니다.

void TimeTimerMgr::SetEngine(QQmlApplicationEngine* engine)
{
    m_engine = engine;
    InitIteragte();
}

void TimeTimerMgr::InitIteragte()
{
    if ( m_engine == NULL ) { return; }
    m_engine->rootContext()->setContextProperty("timerMgr",this);
    QList<QObject*> objectList = m_engine->rootObjects();

    if ( objectList.count() < 1 ) { return; }
    m_pWindow = objectList.value(0);

    if ( m_pWindow == NULL ) { return; }
    connect(m_pWindow, SIGNAL(emit_Start()), this, SLOT(Slot_Start()), Qt::UniqueConnection);
    connect(m_pWindow, SIGNAL(emit_Pause()), this, SLOT(Slot_Pause()), Qt::UniqueConnection);
    connect(m_pWindow, SIGNAL(emit_Stop()), this, SLOT(Slot_Stop()), Qt::UniqueConnection);

    m_pTimeCanvas = m_pWindow->findChild<QObject*>("timeCanvas");
}

다음으로 Slot 함수들입니다. Slot들을 통해 timeCanvas가 가지고 있는 property에 접근하고 있습니다. 추가로 AlwaysonTop 체크 여부는 받아 Window의 flag로 넘겨줍니다.

void TimeTimerMgr::Slot_Start()
{
    m_nTime = m_pTimeCanvas->property("setMinute").toInt();
    timer.start(1000);
}

void TimeTimerMgr::Slot_Pause()
{
    timer.stop();
}

void TimeTimerMgr::Slot_Stop()
{
    timer.stop();
    m_pTimeCanvas->setProperty("setSecond", (m_nTime*60));
    QMetaObject::invokeMethod(m_pTimeCanvas, "rePaint");
    m_nCount = 0;
}

void TimeTimerMgr::Slot_Timeout()
{
    m_nCount++;
    m_pTimeCanvas->setProperty("setSecond", (m_nTime*60-m_nCount));
    QMetaObject::invokeMethod(m_pTimeCanvas, "rePaint");

    if ( m_nTime*60 == m_nCount ) {
        timer.stop();
    }
}

void TimeTimerMgr::Slot_GetColor(QColor color)
{
    m_pTimeCanvas->setProperty("timeColor", color);
    QMetaObject::invokeMethod(m_pTimeCanvas, "rePaint");
}

void TimeTimerMgr::Slot_GetOpacity(double dOpacity)
{
    m_pWindow->setProperty("dOpacity", dOpacity);
}

void TimeTimerMgr::Slot_GetAlwaysOnTop(bool bChecked)
{
    m_pWindow->setProperty("bAlwaysOnTop", bChecked);
    m_pWindow->setFlag(Qt::WindowStaysOnTopHint, bChecked);
}

마지막으로 설정 Button을 클릭했을 때 Dialog를 띄우기 위한 함수입니다. property의 정보들을 Dialog에 넘겨주고 있습니다.

void TimeTimerMgr::showDialog()
{
    pDialog = new ConfigureDialog(this, m_engine);

    connect(pDialog, SIGNAL(Emit_setColor(QColor)), this, SLOT(Slot_GetColor(QColor)), Qt::UniqueConnection);
    connect(pDialog, SIGNAL(Emit_setOpacity(double)), this, SLOT(Slot_GetOpacity(double)), Qt::UniqueConnection);
    connect(pDialog, SIGNAL(Emit_setAlwaysOnTop(bool)), this, SLOT(Slot_GetAlwaysOnTop(bool)), Qt::UniqueConnection);

    TimerSetting settingValue;
    settingValue.color        = m_pTimeCanvas->property("timeColor").value<QColor>();
    settingValue.opacity      = m_pWindow->property("dOpacity").toDouble();
    settingValue.bAlwaysOnTop = m_pWindow->property("bAlwaysOnTop").toBool();

    pDialog->Show();
    pDialog->SetSettingValue(settingValue);
}

다음은 Dialog UI입니다. 버튼을 클릭하면 Window가 띄워지고 해당 UI에는 Timer 색상 설정을 위한 Button과 AlwaysOnTop을 위한 Switch, 마지막으로 투명도 설정을 위한 Slidebar가 있습니다.

여기에선 colorDialog가 사용되고 있고 colorDialog 확인 버튼 클릭 시 onAccepted가 호출되게 됩니다. 그리고 signal들은 ConfigureDialog 객체를 통해 TimeTimerMgr로 전달되고 Time Time.qml의 Property가 설정됩니다.

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Dialogs 1.3

Window {
    id : settingDlg
    property int winWidth : 200
    property int winHeight : 240

    property color backColor : "black"
    property double dOpacity: 1
    property bool bAlwaysOnTop : false

    width: winWidth
    height: winHeight

    minimumWidth: winWidth
    minimumHeight: winHeight

    maximumWidth: winWidth
    maximumHeight: winHeight

    signal emit_changeColor(color back);
    signal emit_changeOpcaity(double dOpacity);
    signal emit_changeAlwaysOnTop(bool bChecked);

    GridLayout {
        anchors.fill: parent
        anchors.centerIn: parent
        columnSpacing: 0
        columns : 2

        Layout.alignment: Qt.AlignCenter

        property int nWidth : 50
        property int nHeight : 30

        Label {
            Layout.preferredWidth: parent.nWidth
            Layout.preferredHeight: parent.nHeight
            Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
            text : "타이머 색상"
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
        }

        Button {
            id : colorBtn
            Layout.preferredWidth: parent.nWidth
            Layout.preferredHeight: parent.nHeight
            Layout.alignment: Qt.AlignCenter
            background: Rectangle {
                color: backColor
            }
            onClicked: {
                colorDialog.open()
            }
        }

        Label {
            text : "상단 고정"
            Layout.preferredWidth: parent.nWidth
            Layout.preferredHeight: parent.nHeight
            Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
        }

        Switch {
            Layout.preferredWidth: parent.nWidth
            Layout.preferredHeight: parent.nHeight
            Layout.alignment: Qt.AlignCenter
            checked: bAlwaysOnTop
            onCheckedChanged: {
                settingDlg.emit_changeAlwaysOnTop(checked)
            }
        }

        Label {
            text : "투명도"
            Layout.preferredWidth: parent.nWidth
            Layout.preferredHeight: parent.nHeight
            Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
        }

        Slider {
            from:1
            to:0
            stepSize: 0.1
            value : dOpacity
            Layout.preferredWidth: parent.nWidth + 20
            Layout.preferredHeight: parent.nHeight
            Layout.alignment: Qt.AlignCenter

            onValueChanged: {
                console.log(value)
                settingDlg.emit_changeOpcaity(value)
            }
        }
    }

    ColorDialog {
        id:colorDialog
        modality: Qt.WindowModal
        title:"Timer Color"
        color:backColor
        onAccepted: {
            backColor = colorDialog.color
            settingDlg.emit_changeColor(backColor)
        }
    }
}

Dialog를 띄우기 위한 ConfigureDialog 객체입니다. 여기서는 QQuickWindow를 멤버로 가지고 있습니다.

#ifndef CONFIGUREDIALOG_H
#define CONFIGUREDIALOG_H

#include <QObject>
#include <QQuickWindow>
#include <QQmlApplicationEngine>

typedef struct _TimerSetting {
    QColor color;
    double opacity;
    bool bAlwaysOnTop;

    _TimerSetting() {
        color.setRgb(0,0,0);
        opacity = 0;
        bAlwaysOnTop = false;
    }
} TimerSetting;

class ConfigureDialog : public QObject
{
    Q_OBJECT
public:
    explicit ConfigureDialog(QObject *parent = nullptr, QQmlApplicationEngine* engine= NULL);
    ~ConfigureDialog();
    void Show();
    void SetSettingValue(TimerSetting& setValue);
signals:
    void Emit_setColor(QColor color);
    void Emit_setOpacity(double dOpacity);
    void Emit_setAlwaysOnTop(bool bChecked);

public slots:
    void Slot_GetColor(QColor color);
    void Slot_GetOpacity(double dOpacity);
    void Slot_GetAlwaysOnTop(bool bChecked);

private:
    QQuickWindow* m_pView;
    QQmlApplicationEngine* m_pEngine;
};

#endif // CONFIGUREDIALOG_H

ConfigureDialog의 cpp입니다. 다른 qml을 윈도우처럼 띄우기 위해 Component를 생성하였고 그 component를 QQuickWindow로 받아 띄워주게 됩니다.

#include "ConfigureDialog.h"
#include <QQmlComponent>
#include <QQmlContext>

ConfigureDialog::ConfigureDialog(QObject *parent, QQmlApplicationEngine *engine)
    : QObject{parent}
    , m_pView(NULL)
    , m_pEngine(engine)
{
    m_pView = new QQuickWindow();
}

void ConfigureDialog::Show()
{
    if ( m_pView == NULL ) { return; }

    QQmlComponent comp(m_pEngine, QUrl(QStringLiteral("qrc:/SettingDialog.qml")));
    m_pView = qobject_cast<QQuickWindow*>(comp.create(m_pEngine->rootContext()));
    connect(m_pView, SIGNAL(emit_changeColor(QColor)), this, SLOT(Slot_GetColor(QColor)), Qt::UniqueConnection);
    connect(m_pView, SIGNAL(emit_changeOpcaity(double)), this, SLOT(Slot_GetOpacity(double)), Qt::UniqueConnection);
    connect(m_pView, SIGNAL(emit_changeAlwaysOnTop(bool)), this, SLOT(Slot_GetAlwaysOnTop(bool)), Qt::UniqueConnection);

    m_pEngine->rootContext()->setContextProperty("contorlDlg",this);

    m_pView->show();
}

void ConfigureDialog::SetSettingValue(TimerSetting &setValue)
{
    m_pView->setProperty("backColor", setValue.color);
    m_pView->setProperty("dOpacity", setValue.opacity);
    m_pView->setProperty("bAlwaysOnTop", setValue.bAlwaysOnTop);
}

void ConfigureDialog::Slot_GetColor(QColor color)
{
    emit Emit_setColor(color);
}

void ConfigureDialog::Slot_GetOpacity(double dOpacity)
{
    emit Emit_setOpacity(dOpacity);
}

void ConfigureDialog::Slot_GetAlwaysOnTop(bool bChecked)
{
    emit Emit_setAlwaysOnTop(bChecked);
}

ConfigureDialog::~ConfigureDialog()
{
    if ( m_pView != NULL ) {
        delete m_pView;
        m_pView = NULL;
    }
}

색상 값을 변경하면 자동으로 시계의 색상이 변경되게 됩니다.

풀 코드는 아래 Github 링크에서 확인하실 수 있습니다.

 

GitHub - psy1064/TimeTimer_Qt: TimeTimer Use Qt/QML

TimeTimer Use Qt/QML. Contribute to psy1064/TimeTimer_Qt development by creating an account on GitHub.

github.com

다른 기능이 추가된 아래 링크도 참고하시면 좋습니다.

 

[Qt] QML(8) - SoundEffect, ComboBox ListModel 적용

이전 포스팅에서 ListView에 아이템을 QAbstractModel을 상속받은 클래스를 통해 동적으로 관리하는 예제를 진행해봤습니다. [Qt] QML(7) - ListView/Model C++ 로 처리하기 이전 포스팅에서 ListView를 QML로 보여

1d1cblog.tistory.com

 

728x90
728x90

Qt Widget Application Project에서는 ui 파일에서 ui를 배치하고 c++ 코드에서 그 ui에 접근하여 수정하기도 했습니다. 그리고 c++ 코드에서 동적으로 UI를 배치할 수도 있었습니다.

Qt Quick에서도 C++과 QML 사이에 주고받을 수 있는 방법이 여러 존재하는데 그에 대해 알아보려 합니다. 간단히 계산기 예제를 통해서 알아보겠습니다.

 

먼저 property를 이용한 방법입니다. qml 파일을 간단히 두 숫자를 계산하고 결과를 보여주는 ui로 구성하였습니다.

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    visible: true
    width : 650
    height: 200
    title: qsTr("Object Demo")
    property int nwidth : 100;

    Item {
        anchors.fill: parent
        Row {
            anchors.centerIn: parent
            spacing : 20
            TextField {
                id : number1
                width : nwidth
                validator: IntValidator {}
                onTextChanged: {
                    PropertyControl.number1 = parseInt(number1.text)
                }
            }
            ComboBox {
                id : combo
                width : nwidth
                model : ["+","-","x","/"]

                onCurrentTextChanged: {
                    PropertyControl.sOP = combo.currentText
                }
            }
            TextField {
                id : number2
                width : nwidth
                validator: IntValidator {}
                onTextChanged: {
                    PropertyControl.number2 = parseInt(number2.text)
                }
            }
            Rectangle {
                width : 30
                height : number1.height
                border.width: 1
                Label {
                    anchors.fill: parent
                    text:" = "
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                }
            }
            Rectangle {
                width : nwidth
                height : number1.height
                border.width: 1
                Label {
                    anchors.fill: parent
                    text : PropertyControl.result
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                }
            }
        }
    }
}

QObject를 상속받는 객체를 하나 만들어줍니다. 이 객체를 통해 Property를 관리하고 qml에서 Propertycontrol 객체의 데이터를 쓰고 읽을 수 있습니다.

#ifndef PROPERTYCONTROL_H
#define PROPERTYCONTROL_H

#include <QObject>

class PropertyControl : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int number1 WRITE SetNumber1)
    Q_PROPERTY(int number2 WRITE SetNumber2)
    Q_PROPERTY(QString sOP WRITE SetOp)
    Q_PROPERTY(int result READ GetResult NOTIFY ResultChanged)

public:
    explicit PropertyControl(QObject *parent = nullptr);

    void SetNumber1(int& nNum);
    void SetNumber2(int& nNum);
    void SetOp(QString& sOP);

    int GetResult();
signals:
    void ResultChanged();

private:
    int m_nResult;
    int m_nNumber1;
    int m_nNumber2;
    QString m_sOP;
};

#endif // PROPERTYCONTROL_H
#include "PropertyControl.h"
#include <QDebug>

PropertyControl::PropertyControl(QObject *parent)
    : QObject{parent}
    , m_nResult(0)
    , m_nNumber1(0)
    , m_nNumber2(0)
    , m_sOP("")
{
}

void PropertyControl::SetNumber1(int& nNum) {
    m_nNumber1 = nNum;
    emit ResultChanged();
}

void PropertyControl::SetNumber2(int& nNum) {
    m_nNumber2 = nNum;
    emit ResultChanged();
}

void PropertyControl::SetOp(QString& sOP) {
    m_sOP = sOP;
    emit ResultChanged();
}

int PropertyControl::GetResult() {
    if ( m_sOP == "+" ) {
        m_nResult = m_nNumber1 + m_nNumber2;
    } else if ( m_sOP == "-" ) {
        m_nResult = m_nNumber1 - m_nNumber2;
    } else if ( m_sOP == "x" ) {
        m_nResult = m_nNumber1 * m_nNumber2;
    } else if ( m_sOP == "/" ) {
        if ( m_nNumber2 == 0 ) { return 0; }
        m_nResult = m_nNumber1 / m_nNumber2;
    } else {
        return 0;
    }
    return m_nResult;
}

그리고 이 Propertycontrol 객체를 생성 후 engine의 rootContext에 등록해 줍니다.

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "PropertyControl.h"

int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    PropertyControl control;
    engine.rootContext()->setContextProperty("PropertyControl",&control);

    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app,
        [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        }
    , Qt::QueuedConnection);

    engine.load(url);
    return app.exec();
}

위 코드를 하나씩 보겠습니다. Propertycontrol 객체에는 Q_OBJECT가 선언되어 있고 그 밑으로 qml과 c++간 주고받을 property가 선언되어 있습니다. qml에서 해당 property에 값을 넣을 때는 WRITE 뒤에 명시된 함수가 호출되고 property의 값을 읽을 때는 READ 함수를 이용합니다.

Q_OBJECT
Q_PROPERTY(int number1 WRITE SetNumber1)
Q_PROPERTY(int number2 WRITE SetNumber2)
Q_PROPERTY(QString sOP WRITE SetOp)
Q_PROPERTY(int result READ GetResult NOTIFY ResultChanged)

아래 오브젝트를 보면 TextField에서 값이 변경되면 main에서 전달된 Propertycontrol 객체의 number1에 값을 넣어주고 있습니다.

TextField {
    id : inputNumber1
    width : nwidth
    validator: IntValidator {}
    onTextChanged: {
        PropertyControl.number1 = parseInt(inputNumber1.text)
    }
}
engine.rootContext()->setContextProperty("PropertyControl",&control);

이때 Q_PROPERTY에 number1의 WRITE 함수를 SetNumber1으로 등록했기 때문에 SetNumber1이 호출될 것입니다.

Q_PROPERTY(int number1 WRITE SetNumber1)

void PropertyControl::SetNumber1(int& nNum) {
    m_nNumber1 = nNum;
    qDebug() << "number1 Changed";
    emit ResultChanged();
}

이렇게 TextField와 Combobox는 WRITE 시에 해당 함수를 타게 됩니다. 

계산 결과를 보여주는 result 변수는 Label에서 text로 지정해놓고 있습니다. 한번 Property 지정을 해놓고 다른 코드에서는 이 Label의 Text를 건드리지 않고 있습니다.

Rectangle {
    width : nwidth
    height : inputNumber1.height
    border.width: 1
    Label {
        anchors.fill: parent
        text : PropertyControl.result
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
    }
}

이때 이 result를 READ 하고 있는 Q_PROPERTY에 등록된 NOTIFY signal을 이용해야 합니다. 위 코드에서 봤듯이 숫자나 연산자를 변경하였을 때 결과 값을 바로 경신할 수 있게 emit을 날려주고 있습니다. 

Q_PROPERTY(int number1 WRITE SetNumber1)

void PropertyControl::SetNumber1(int& nNum) {
    m_nNumber1 = nNum;
    qDebug() << "number1 Changed";
    emit ResultChanged();
}

만약 이 Emit을 없앤다면 Label에 result에 대해 Read 하지 않아 갱신이 되지 않습니다.

qml에서 C++ 함수를 직접적으로 호출할 수 있습니다. C++의 객체에서 Q_INVOKABLE로 선언된 함수는 호출이 가능합니다.

Button {
    width : nwidth
    height : inputNumber1.height
    text : "QDebug"
    onClicked: {
        PropertyControl.showlogResult()
    }
}
#ifndef PROPERTYCONTROL_H
#define PROPERTYCONTROL_H

#include <QObject>

class PropertyControl : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int number1 WRITE SetNumber1)
    Q_PROPERTY(int number2 WRITE SetNumber2)
    Q_PROPERTY(QString sOP WRITE SetOp)
    Q_PROPERTY(int result READ GetResult NOTIFY ResultChanged)

public:
    explicit PropertyControl(QObject *parent = nullptr);

    void SetNumber1(int& nNum);
    void SetNumber2(int& nNum);
    void SetOp(QString& sOP);

    int GetResult();

    Q_INVOKABLE void showlogResult()
    {
        qDebug() << "Result = " << m_nResult;
    }
signals:
    void ResultChanged();

private:
    int m_nResult;
    int m_nNumber1;
    int m_nNumber2;
    QString m_sOP;
};

#endif // PROPERTYCONTROL_H

마지막으로 SIGNAL을 이용한 방법입니다. 오브젝트 안에 signal을 등록하고 버튼을 클릭했을 때 해당 signal을 발생하게 만들었습니다.

Item {
    id : item
    objectName: "item"
    anchors.fill: parent
    signal sigDebug();
    Button {
        width : nwidth
        height : inputNumber1.height
        text : "QDebug"
        onClicked: {
            item.sigDebug()
        }
    }
}

해당 Signal을 QObject를 상속받는 객체와 connect를 시켜줍니다.

{
	...
    
    QObject* pWindow = engine.rootObjects().value(0);
    QObject* pObject = pWindow->findChild<QObject*>("item");
    if ( pObject != NULL ) {
        QObject::connect(pObject, SIGNAL(sigDebug()),&control, SLOT(Slot_ShowDebug()));
    }
    
}
#ifndef PROPERTYCONTROL_H
#define PROPERTYCONTROL_H

#include <QObject>

class PropertyControl : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int number1 WRITE SetNumber1)
    Q_PROPERTY(int number2 WRITE SetNumber2)
    Q_PROPERTY(QString sOP WRITE SetOp)
    Q_PROPERTY(int result READ GetResult NOTIFY ResultChanged)

public:
    explicit PropertyControl(QObject *parent = nullptr);

    void SetNumber1(int& nNum);
    void SetNumber2(int& nNum);
    void SetOp(QString& sOP);

    int GetResult();

signals:
    void ResultChanged();

public slots:
    void Slot_ShowDebug()
    {
        qDebug() << m_nResult;
    }

private:
    int m_nResult;
    int m_nNumber1;
    int m_nNumber2;
    QString m_sOP;
};

#endif // PROPERTYCONTROL_H
728x90
728x90

이전 포스팅까지는 스위치 ON/OFF 예제라던지 등에 Rectangle에 MouseArea를 이용하여 버튼과 비슷한 동작을 만들어 사용했습니다. QtQuick.Controls 모듈을 import할 시 사용할 수 있는 Control Object는 기존의 Button과 같은 UI들을 제공하기 때문에 이 포스팅에서는 몇개의 오브젝트 예제를 다뤄보려 합니다.

 

먼저 Button입니다. 간단하게 Button Object를 생성하면 Item에 MouseArea 처리 없이 Button으로 사용이 가능합니다.

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    width: 640
    height: 480
    visible: true
    id:windows

    Row {
        anchors.centerIn : parent
        spacing: 10

        Button {
            width: 200
            height : 100
            text : "button"
        }
        Button {
            width: 200
            height : 100
            text : "button"
        }
        Button {
            width: 200
            height : 100
            text : "button"
        }
    }
}

Button의 이벤트 처리도 onClicked같은 signal로 처리 가능합니다.

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    width: 640
    height: 480
    visible: true
    id:windows

    Row {
        anchors.centerIn : parent
        spacing: 10

        Button {
            width: windows.width / 5
            height : windows.height / 3
            text : "yellow"
            onClicked: {
                rect.color = "yellow"
            }
        }
        Button {
            width: windows.width / 5
            height : windows.height / 3
            text : "blue"
            onClicked: {
                rect.color = "blue"
            }
        }
        Button {
            width: windows.width / 5
            height : windows.height / 3
            text : "red"
            onClicked: {
                rect.color = "red"
            }
        }

        Rectangle {
            id : rect
            width: windows.width / 5
            height : windows.height / 3
            Behavior on color {
                ColorAnimation {
                    duration: 200
                }
            }
        }
    }
}

다음으로 TextInput을 대체할 수 있는 TextField 오브젝트와 TextArea 오브젝트입니다.

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    width: 500
    height: 300
    visible: true
    id:windows

    Row {
        spacing: 10
        padding: 10
        Column {
            spacing : 10
            TextField {
                id:textfield
                width:windows.width / 2
                height:windows.height / 6
                font.pixelSize: 20
            }
            Rectangle {
                border.width: 1
                width:windows.width / 2
                height:windows.height * 5 / 6
                TextArea {
                    id:textarea
                    anchors.fill: parent
                    font.pixelSize: 20
                    wrapMode: TextEdit.WordWrap
                }
            }
        }

        Button {
            width:windows.width / 6
            height:windows.height / 6
            text: "clear"
            onClicked: {
                textfield.text = ""
                textarea.text = ""
            }
        }
    }
}

TextField와 다르게 TextArea는 여러줄 입력이 가능하고 줄 바꿈에 대한 기준은 TextArae의 wrapMode Property를 통해 설정이 가능합니다.

다음은 CheckBox, ComboBox, Radiobox, SpinBox 오브젝트입니다. 

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    visible: true
    width : 650
    height: 200
    title: qsTr("CheckBox Demo")

    Item {
        anchors.fill: parent
        Row {
            anchors.centerIn: parent
            spacing: 20

            GroupBox {
                title:"CheckBox"
                anchors.verticalCenter: parent.verticalCenter

                Column {
                    spacing:10
                    CheckBox {
                        text : "Check1"
                    }
                    CheckBox {
                        text : "Check2"
                    }
                    CheckBox {
                        text : "Check3"
                    }
                }
            }

            GroupBox {
                title:"ComboBox"
                anchors.verticalCenter: parent.verticalCenter
                Column {
                    spacing : 10
                    ComboBox {
                        model: [ "Banana", "Apple", "Coconut" ]
                    }
                    ComboBox {
                        model: ListModel {
                            id: cbItems
                            ListElement { text: "Banana"; }
                            ListElement { text: "Apple"; }
                            ListElement { text: "Coconut"; }
                        }
                        onCurrentIndexChanged: console.debug(cbItems.get(currentIndex).text)
                    }
                }
            }

            GroupBox {
                title:"RadioButton"
                anchors.verticalCenter: parent.verticalCenter
                Column {
                    spacing:10
                    RadioButton {
                        text : "Radio1"
                    }
                    RadioButton {
                        text : "Radio2"
                    }
                    RadioButton {
                        text : "Radio3"
                    }
                }
            }

            GroupBox {
                title: "SpinBox"
                anchors.verticalCenter: parent.verticalCenter

                Column {
                                    spacing:10
                    SpinBox {
                        from : 0
                        to : 1000
                        stepSize : 10
                    }

                    SpinBox {
                        id: spinbox
                        from: 0
                        value: 110
                        to: 100 * 100
                        stepSize: 1

                        property int decimals: 2
                        property real realValue: value / 100

                        validator: DoubleValidator {
                            bottom: Math.min(spinbox.from, spinbox.to)
                            top:  Math.max(spinbox.from, spinbox.to)
                        }

                        textFromValue: function(value, locale) {
                            return Number(value / 100).toLocaleString(locale, 'f', spinbox.decimals)
                        }

                        valueFromText: function(text, locale) {
                            return Number.fromLocaleString(locale, text) * 100
                        }
                    }
                }
            }
        }
    }
}

 

728x90
728x90

지난 포스팅에선 간단히 오브젝트와 컨테이너를 이용해서 배치하고 이벤트 처리를 하는 예제를 해봤습니다.

 

[Qt] QML UI 구성하기(1)

지난 포스팅에서 Qt Quick Application 프로젝트를 생성하는 부분까지 포스팅했습니다. [Qt] Qt Quick Application 시작하기 기존에 Qt Widget을 이용하여 UI를 구성하는 방법 말고도 Qml을 이용하여 UI를 구성하

1d1cblog.tistory.com

이번에는 State와 Property 등에 대해 알아보려고 합니다.

 

State는 Transition이라는 개념과 같이 나오는데 스위치를 예를 들자면 스위치는 ON/OFF State가 있고 ON/OFF를 할때마다 설정한 값이 바뀌게 되는데 이에 대한 행동이나 작업을 Trasition이라고 합니다.

 

왼쪽에는 램프가 있고 오른쪽에 스위치를 하나 두었습니다. 스위치를 클릭하면 램프의 상태가 바뀌고 램프는 상태에 따라 Text와 색상이 바뀌께 됩니다.

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 200
    height: 100
    visible: true

    Row {
        width:200
        height:100
        Rectangle {
            id:lamp
            width:100
            height:100
            border.color: "black"
            Text {
                id: lampText
            }

            state: "off"

            states: [
                State {
                    name: "on"
                    PropertyChanges {
                        target: lamp
                        color:"yellow"
                    }
                    PropertyChanges {
                        target: lampText
                        text:"on"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        color:"black"
                    }
                },
                State {
                    name: "off"
                    PropertyChanges {
                        target: lamp
                        color:"black"
                    }
                    PropertyChanges {
                        target: lampText
                        text:"off"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        color:"white"
                    }
                }
            ]
        }

        Rectangle {
            id:swit
            width:100
            height:100
            border.color: "black"

            MouseArea {
                anchors.fill: parent
                onClicked: {
                    lamp.state == "on" ? lamp.state = "off" : lamp.state = "on";
                }
            }
        }
    }
}

이 때 on state에서 off state로 이동할 때 처럼 State 간 변화가 있을 때의 행동을 Transitions이라고 합니다.

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 200
    height: 100
    visible: true

    Row {
        width:200
        height:100
        Rectangle {
            id:lamp
            width:100
            height:100
            border.color: "black"
            Text {
                id: lampText
                width: parent.width
                height: parent.height
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
            }

            state: "off"

            states: [
                State {
                    name: "on"
                    PropertyChanges {
                        target: lamp
                        color:"yellow"
                    }
                    PropertyChanges {
                        target: lampText
                        text:"on"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        color:"black"
                    }
                },
                State {
                    name: "off"
                    PropertyChanges {
                        target: lamp
                        color:"black"
                    }
                    PropertyChanges {
                        target: lampText
                        text:"off"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                        color:"white"
                    }
                }
            ]

            transitions: [
                Transition {
                    from: "off"
                    to: "on"
                    ColorAnimation { duration : 1000 }
                }
            ]
        }

        Rectangle {
            id:swit
            width:100
            height:100
            border.color: "black"
            Text {
                text: "Switch"
                width: parent.width
                height: parent.height
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
            }

            MouseArea {
                anchors.fill: parent
                onClicked: {
                    lamp.state == "on" ? lamp.state = "off" : lamp.state = "on";
                }
            }
        }
    }
}

위 코드에서 off->on으로 상태가 변경될 때는 Color 변경에 대해 Animation을 적용했습니다. 

Qt에서 List나 Table 같은 데이터를 표현하기 위해서 Model 부분과 View 부를 제공합니다. 아래 그림을 보면 Model은 Data를 읽어 담아놓는 Container입니다. View는 Model로부터 읽어온 데이터를 표시하거나 핸들링하게 해줍니다.

먼저 List입니다. List도 마찬가지로 Model과 View로 데이터를 관리/표시 하며 Delegate를 통해 표시할 데이터의 Style을 꾸며줄 수 있습니다.

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 300
    height: 100
    visible: true

    ListModel {
        id:nameModel
        ListElement { name: "park" }
        ListElement { name: "kim" }
        ListElement { name: "lee" }
    } // 데이터를 담는 Model

    Component {
        id: nameDelegate
        Text {
            text : name
            font.pixelSize: 20
        }
    } // 데이터를 표현하는 Style

    ListView {
        anchors.fill: parent
        model : nameModel
        delegate: nameDelegate
    } // 데이터를 보여주는 View
}

데이터를 변경하고 싶을 땐 Model을 Style을 변경하고 싶을 땐 Component(Delegate)를 실제 표시하는 오브젝트를 변경하고 싶을 때는 View를 수정하시면 됩니다.

 

Delegate는 한 데이터(Element)에 대해 꾸며주는 Style라고 생각하면 좋습니다. 위 예제에서 Element의 데이터가 추가되고 이 추가된 데이터를 표현할 Style을 더 추가해보겠습니다.

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 300
    height: nameModel.count * 50
    visible: true
    id:windows

    ListModel {
        id:nameModel
        ListElement {
            name: "park"
            age : 20
        }
        ListElement {
            name: "kim"
            age : 25
        }
        ListElement {
            name: "lee"
            age : 30
        }
    } // 데이터를 담는 Model

    Component {
        id: nameDelegate
        Rectangle {
            width : windows.width
            height : windows.height/nameModel.count
            border.color: "black"
            border.width: 1

            Text {
                anchors.fill: parent
                text: "name : " + name + "\n" + "age : " + age;
                verticalAlignment: Text.AlignVCenter
                anchors.leftMargin: 10
            }
        }
    } // 데이터를 표현하는 Style

    ListView {
        anchors.fill: parent
        model : nameModel
        delegate: nameDelegate
    } // 데이터를 보여주는 View
}

여기에 리스트에 데이터를 추가하고 삭제하는 오브젝트도 넣어보겠습니다.

 

먼저 추가하는 코드입니다. 아래 코드에선 UI 크기 관리를 위해서 변수를 사용하였습니다. 변수는 property 키워드를 붙이고 자료형 + 변수명으로 사용 가능합니다.

import QtQuick 2.15
import QtQuick.Window 2.15


Window {
    property int listViewheight : nameModel.count * dataHeight;
    property int dataHeight : 50;
    property int inputUIHeight : 30;
    property int columnSpacing : 10;
    property int nCount : 5;

    width: 300
    height: listViewheight + inputUIHeight + columnSpacing;
    visible: true
    id:windows

    ListModel {
        id:nameModel
        ListElement {
            name: "park"
            age : 20
        }
        ListElement {
            name: "kim"
            age : 25
        }
        ListElement {
            name: "lee"
            age : 30
        }
    } // 데이터를 담는 Model

    Component {
        id: nameDelegate
        Rectangle {
            width : windows.width
            height : dataHeight
            border.color: "black"
            border.width: 1

            Text {
                anchors.fill: parent
                text: "name : " + name + "\n" + "age : " + age;
                verticalAlignment: Text.AlignVCenter
                anchors.leftMargin: 10
            }
        }
    } // 데이터를 표현하는 Style


    Column {
        spacing : columnSpacing
        width : parent.width
        height : parent.height
        ListView {
            width : parent.width
            height : listViewheight
            model : nameModel
            delegate: nameDelegate
        } // 데이터를 보여주는 View

        Row {

            width : parent.width
            height: inputUIHeight

            Rectangle {
                border.width: 1
                width : parent.width/nCount
                height : parent.height
                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text : "name"
                }
            }

            Rectangle {
                width : parent.width/nCount
                height : parent.height
                border.color: "black"
                border.width: 1
                TextInput {
                    id:nameInput
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    onFocusChanged: {
                        focus? parent.color = "deepskyblue" : parent.color = "white"
                    }
                }
            }

            Rectangle {
                border.width: 1
                width : parent.width/nCount
                height : parent.height
                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text : "age"
                }
            }
            Rectangle {
                width : parent.width/nCount
                height : parent.height
                border.color: "black"
                border.width: 1
                TextInput {
                    id:ageInput
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    onFocusChanged: {
                        focus? parent.color = "deepskyblue" : parent.color = "white"
                    }
                }
            }
            Rectangle {
                width : parent.width/nCount
                height : parent.height
                color: "gray"
                border.width: 1

                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text : "input"
                }
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        nameModel.append({ name: nameInput.text,
                                           age : parseInt(ageInput.text)
                                        })
                        nameInput.text = ""
                        ageInput.text = ""
                        nameInput.focus = false
                        ageInput.focus = false
                    }
                }
            }
        }
    }
}

TextInput이 적용된 Rectangle을 클릭할 시 Focus가 들어가고 그에 따라 Color가 변경됩니다. 데이터를 입력 후 Input 버튼을 누르게 되면 해당 데이터가 리스트에 추가가 됩니다.

다음은 삭제 처리입니다. 삭제를 위해서 클릭한 아이템을 지우는 방식으로 진행했습니다. 어떤 아이템이 클릭되었는지 확인하기 위해서 listView에 highlight component도 추가하였습니다.

import QtQuick 2.15
import QtQuick.Window 2.15


Window {
    property int listViewheight : nameModel.count * dataHeight;
    property int dataHeight : 50;
    property int inputUIHeight : 30;
    property int columnSpacing : 10;
    property int nCount : 6;

    width: 300
    height: listViewheight + inputUIHeight + columnSpacing;
    visible: true
    id:windows

    ListModel {
        id:nameModel
        ListElement {
            name: "park"
            age : 20
        }
        ListElement {
            name: "kim"
            age : 25
        }
        ListElement {
            name: "lee"
            age : 30
        }
    } // 데이터를 담는 Model

    Component {
         id: nameDelegate
         Item {
             width : windows.width
             height : dataHeight

             Text {
                 anchors.fill: parent
                 text: "name : " + name + "\n" + "age : " + age;
                 verticalAlignment: Text.AlignVCenter
                 anchors.leftMargin: 10
             }
             MouseArea {
                 anchors.fill: parent
                 onClicked: nameList.currentIndex = index
             }
         }
     } // 데이터를 표현하는 Style

    Component {
        id:listHighLight
        Rectangle{
            color : "blue"
        }
    } // Highlight

    Column {
        spacing : columnSpacing
        width : parent.width
        height : parent.height
        ListView {
            id : nameList
            width : parent.width
            height : listViewheight
            model : nameModel
            delegate: nameDelegate
            highlight: listHighLight
        } // 데이터를 보여주는 View

        Row {
            width : parent.width
            height: inputUIHeight

            Rectangle {
                border.width: 1
                width : parent.width/nCount
                height : parent.height
                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text : "name"
                }
            }

            Rectangle {
                width : parent.width/nCount
                height : parent.height
                border.color: "black"
                border.width: 1
                TextInput {
                    id:nameInput
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    onFocusChanged: {
                        focus? parent.color = "deepskyblue" : parent.color = "white"
                    }
                }
            }

            Rectangle {
                border.width: 1
                width : parent.width/nCount
                height : parent.height
                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text : "age"
                }
            }
            Rectangle {
                width : parent.width/nCount
                height : parent.height
                border.color: "black"
                border.width: 1
                TextInput {
                    id:ageInput
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    onFocusChanged: {
                        focus? parent.color = "deepskyblue" : parent.color = "white"
                    }
                }
            }
            Rectangle {
                width : parent.width/nCount
                height : parent.height
                color: "gray"
                border.width: 1

                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text : "input"
                }
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        nameModel.append({ name: nameInput.text,
                                           age : parseInt(ageInput.text)
                                        })
                        nameInput.text = ""
                        ageInput.text = ""
                        nameInput.focus = false
                        ageInput.focus = false
                    }
                }
            }

            Rectangle {
                width : parent.width/nCount
                height : parent.height
                color: "red"
                border.width: 1

                Text {
                    anchors.fill: parent
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text : "delete"
                }
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        if ( nameModel.count > 0 ) {
                            nameModel.remove(nameList.currentIndex);
                            nameList.currentIndex = -1
                        }
                    }
                }
            }
        }
    }
}

Delegate에 Rectangle 오브젝트를 사용하면 highlight가 제대로 적용되지 않아 Item 오브젝트로 변경하였습니다.

728x90
728x90

지난 포스팅에서 Qt Quick Application 프로젝트를 생성하는 부분까지 포스팅했습니다.

 

[Qt] Qt Quick Application 시작하기

기존에 Qt Widget을 이용하여 UI를 구성하는 방법 말고도 Qml을 이용하여 UI를 구성하는 방법에 대해 공부해보려 합니다. Qt Creator를 실행 후 Create Project > Qt Quick Application을 선택합니다. 프로젝트를

1d1cblog.tistory.com

오늘은 이어서 간단히 UI를 작성해보려 합니다.

 

QML의 구성은 아래와 같습니다. 모듈을 사용하기 위해 import를 가장 상단에 기입합니다. 그 아래로부터 오브젝트들을 기입하는데 오브젝트들에는 그에 맞는 속성(Property)들에 값을 수정해 UI를 구성해줍니다.

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")
}

Window안에 Rectangle 오브젝트를 배치해 보겠습니다.

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    Rectangle {
        width:360
        height:360
        border.color : "black"
        border.width : 1
        anchors.centerIn: parent
        Text {
            text: qsTr("Quit")
            anchors.centerIn: parent
        }
        MouseArea {
            anchors.fill: parent
            onClicked: {
                Qt.quit();
            }
        }
    }
}

Rectangle 안에 border property를 이용해서 테두리를 설정해줬습니다. 여기서 anchor 속성이 나오는데 오브젝트의 배치를 조정할 수 있는 속성입니다. 이 속성은 두개 이상의 오브젝트를 통해 속성 예제를 보여드리겠습니다.

오브젝트들은 Item 오브젝트로부터 상속받기 때문에 anchors 뿐 아니라 기본 속성은 Item의 Documents에서 확인할 수 있습니다.

다음은 이미지입니다. 이미지는 Image 오브젝트를 통해 사용할 수 있고 윈도우 크기를 이미지에 맞추고 싶다면 아래와 같이 사용하시면 됩니다.

사진에 텍스트도 넣고 싶다면 Text 오브젝트를 이용해 넣을 수 있습니다.

다음은 사용자 이벤트 처리입니다. 첫 예제에서 나온 MouseArea 같은 것들로 이벤트 처리가 가능합니다. MouseArea를 이용하면 오브젝트에 터치 이벤트를 넣을 수 있습니다.

여기서 Text의 wrapMode를 사용하면 Text를 자동 줄바꿈 설정해줄 수 있습니다.

이 Color 변경에 Animiation 속성을 넣어줄 수 있습니다. Behavior 속성을 이용해서 color 속성일 때 Animiation을 지정해 줍니다.

다음은 TextInput 이벤트입니다. Rectangle 안에 TextInput 이벤트를 넣어 키보드 값을 입력받을 수 있고 포커스 유무에 따라 다른 동작을 하게 할 수 있습니다.

다음으로는 Container 종류입니다. 위처럼 연속된 오브젝트나 여러 오브젝트를 배치할 때 효율적으로 사용할 수 있게 해줍니다. Container는 Row, Column, Grid 등이 있습니다.

 

728x90
728x90

Qt5부터 사용할 수 있는 windeployqt는 빌드 시 생성한 실행파일을 단독으로 실행할 수 있게 필요한 라이브러리를 자동으로 추가해주는 툴입니다.

그 후 프로젝트가 저장된 폴더로 이동하면 Release 폴더가 생긴 것을 확인할 수 있습니다.

그 폴더로 들어가보면 exe 파일이 생성되어 있습니다.

이 exe 파일을 바로 실행시키려 하면 다음과 같이 오류창이 뜨게 됩니다.

제대로 exe파일을 실행파일로 만들기 위해서 MinGW 64-bit를 실행시켜 준 다음 아까 exe 파일이 있던 위치로 이동해줍니다.

창에 windeployqt 명령어로 exe파일을 실행합니다.

명령이 완료되면 아까 폴더에 없던 파일들이 생긴 것을 볼 수 있습니다.

이제 다시 exe 파일을 실행시켜보면 제대로 실행이 되는 것을 확인할 수 있습니다.

728x90
728x90

C++11 버전에 람다 함수라는 표현식이 등장합니다. 이는 여러 예제에서 확인할 수 있는데 Qt를 하신다면 QML Quick Application Project를 생성했을 때 main.cpp에서도 확인할 수 있습니다.

QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, 
    [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }
, Qt::QueuedConnection);

람다 함수는 함수 호출 과정을 생략해 시간을 절약하고 적응하면 코드의 가독성을 높일 수 있는 함수 표현 방식입니다. 람다함수는 함수 이름이 없기 때문에 익명 함수라고도 합니다.

 

람다 함수의 문법은 아래와 같습니다.

먼저 대괄호에 둘러싸인 캡처입니다. 람다 함수에서 사용하고자 하는 외부 변수를 여기에 명시합니다. 함수 내에서 그 변수를 사용하기 위해서입니다.

int main() {
    int nHeight = 170;
    [](int nWeight) { cout << "Height = " << nHeight << "Weight = " << nWeight; } (50);

    return 0;
}

위 코드에서 람다 함수 내부에서 main 안에 있는 Height를 사용할 때 오류가 발생합니다.

이렇게 람다 외부에 있는 값을 사용하기 위해서는 캡처를 이용해야 합니다. 캡처에는 [=]와 [&]이 존재합니다. [=]는 외부 변수를 값으로 전달받고 [&]는 참조로 전달받습니다.

[=]를 통해 전달받는 외부 변수는 값을 변경할 수 없습니다. 

#include <iostream>
using namespace std;
int main() {
    int nHeight = 170;
    [=](int nWeight) { 
      cout << "Height = " << nHeight << " Weight = " << nWeight << endl;
      nHeight = 200;
    } (50);

    cout << "Height = " << nHeight << endl;

    return 0;
}

전달받은 값을 변경하기 위해서는 [&] 캡처를 사용해야 합니다.

#include <iostream>
using namespace std;
int main() {
    int nHeight = 170;
    [&](int nWeight) { 
      cout << "Height = " << nHeight << " Weight = " << nWeight << endl;
      nHeight = 200;
    } (50);

    cout << "Height = " << nHeight << endl;

    return 0;
}

캡처를 더 세세하게 설정이 가능합니다. 전달 방식과 변수를 같이 기입하여 세밀하게 설정할 수 있습니다.

  • [&a] : 외부 변수 a만 참조로 전달받아 사용합니다. 다른 외부변수는 사용 불가합니다.
#include <iostream>
using namespace std;
int main() {
    int nHeight = 170;
    int nAge = 20;
    [&nHeight](int nWeight) { 
      cout << "Height = " << nHeight << " Weight = " << nWeight << endl;
      cout << "Age = " << nAge;
      nHeight = 200;
    } (50);

    cout << "Height = " << nHeight << endl;

    return 0;
}

  • [=, &a] : 외부 변수 모두 값으로 전달받아 사용하지만 a 변수만 참조로 전달받아 사용합니다.
#include <iostream>
using namespace std;
int main() {
    int nHeight = 170;
    int nAge = 20;
    [=, &nHeight](int nWeight) { 
      cout << "Height = " << nHeight << " Weight = " << nWeight << endl;
      cout << "Age = " << nAge << endl;
      nHeight = 200;
    } (50);

    cout << "Height = " << nHeight << endl;

    return 0;
}

  • [&, a] : 외부 변수 모두 참조 값으로 전달받아 사용하지만 a 변수만 값으로 전달받아 사용합니다.
#include <iostream>
using namespace std;
int main() {
    int nHeight = 170;
    int nAge = 20;
    [&, nHeight](int nWeight) { 
      cout << "Height = " << nHeight << " Weight = " << nWeight << endl;
      cout << "Age = " << nAge << endl;
      nHeight = 200;
    } (50);

    cout << "Height = " << nHeight << endl;

    return 0;
}

 

다음은 매개변수입니다. 매개변수는 기존 함수 선언과 마찬가지로 생략이 가능하며 사용하지 않을 경우에는 괄호 자체도 넣지 않아도 무관합니다.

#include <iostream>
using namespace std;
int main() {
    int nHeight = 170;
    [&] { 
      cout << "Height = " << nHeight << endl;
      nHeight = 200;
    } ();

    cout << "Height = " << nHeight << endl;

    return 0;
}

다음은 mutable 속성입니다. 캡처를 값에 의한 전달일 때 오류가 나지 않게 해 줍니다. 하지만 결국 값에 의한 전달이기 때문에 람다 내부에서 값을 변경해도 외부에서는 그대로인 것을 확인할 수 있습니다.

#include <iostream>
using namespace std;
int main() {
    int nHeight = 170;
    [=](int nWeight) mutable { 
      cout << "Height = " << nHeight << " Weight = " << nWeight << endl;
      nHeight = 200;
    } (50);

    cout << "Height = " << nHeight << endl;

    return 0;
}

throw는 예외 처리 관련 키워드입니다. 해당 키워드가 없어도 상관없습니다.

#include <iostream>
using namespace std;
int main() {
    int a = 5;
    [=] (int nDiv) throw() {
        try {
            if ( nDiv == 0 ) {
                throw 0;
            }
        cout << a/nDiv << endl;
        }
        catch (int e) {
            cout << "Exception";
        }
    } (0);
    return 0;
}

다음으로 반환형입니다. 기본적으로 람다 함수의 반환형은 void고 return을 사용하기 위해서는 반환형을 지정해야 합니다. 만약 따로 지정 안 하고 return 시에는 return에 맞춰 반환형이 결정되고 반환형을 지정했을 경우에 그에 맞춰 변경됩니다.

#include <iostream>
using namespace std;
int main() {
  int a = 63;
  cout << [=] (int nData)  {
    int nSum = a + nData;
    return nSum;
  } (0) << endl;

  cout << [=] (int nData)->char  {
    int nSum = a + nData;
    return nSum;
  } (0) << endl;
  
  return 0;
}

람다 함수는 아래와 같이도 사용할 수 있습니다. sort 함수 같이 함수를 인자로 넘기는 경우에 함수 자리에 람다 함수를 사용할 수 있습니다. auto 키워드를 이용해서 람다 함수를 변수로 지정하여 사용할 수 있습니다.

#include <iostream>
#include <algorithm>
using namespace std;

void printArr(int pArr[]) {
  for(int i=0;i<10;i++) {
    cout << pArr[i] << " ";
  } cout << endl;
}

int main() {
  int arr[10] = {3,8,19,1,4,22,3,24,51,29};
  printArr(arr);

  sort(arr, arr+10);
  printArr(arr);

  sort(arr, arr+10, [](int a, int b) { return a > b; });
  printArr(arr);

  sort(arr, arr+10);
  printArr(arr);

  auto des = [](int a, int b) { return a > b; };
  sort(arr, arr+10, des);
  printArr(arr);
  
  return 0;
}

 

728x90
728x90

이전 포스팅에서 Qt, C++ 코드 상으로 튜토리얼을 진행하면서 각종 elements들이나 bin들에 대해 살펴봤습니다.

 

[Qt] gstreamer(7) - buffer, GLib

Application와 gstreamer의 pipeline 간에 데이터를 주고받는 내용에 대해 살펴봅니다. 이 예제와 basic tutorial 7이 큰 차이가 없어 이 포스팅에서 한번에 다루겠습니다. Basic tutorial 8: Short-cutting the pipeline Ba

1d1cblog.tistory.com

C++ 상에서 구현하는 것이 아닌 Command를 통해 바로 실행할 수 있는 방법도 있습니다. UDP streaming을 하는 예제를 Command로 진행해 보겠습니다. 

 

videotestsrc를 udp를 통해 streaming 하는 커맨드입니다. udpsink에 host property을 통해 스트리밍 대상을 지정합니다.

gst-launch-1.0 -v videotestsrc ! videoconvert ! jpegenc ! rtpjpegpay ! udpsink host=192.168.254.105 port=5000

elements들을 사용할 때는 파이프라인을 구성할 때 elements 간 sink src 사이의 Capabilities를 봐야 합니다.

다음은 udpsrc를 통해 쏘고 있는 영상을 받아오는 커맨드입니다.

gst-launch-1.0 udpsrc port=5000 ! application/x-rtp,encoding-name=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! autovideosink

실제 테스트 화면입니다. Source 쪽은 Windows Sink 쪽은 Ubuntu 가상환경에서 진행하였습니다.

다른 코덱에 대해서는 아래 링크 참고하시면 됩니다.

 

GStreamer RTP UDP 카메라 전송 명령

GStreamer 1.0 RTP UDP 카메라 전송 gst-launch-1.0을 이용하여 카메라 영상을 전송하는 방법을 정...

blog.naver.com

 

728x90

+ Recent posts