QML 동적 연결 및 바인딩 활용

QML은 런타임에 객체 간의 상호 작용을 동적으로 제어할 수 있는 강력한 기능을 제공합니다. 특히 Connections 요소와 Binding 요소를 사용하면 객체의 신호나 속성을 상황에 맞게 변경하여 더욱 유연하고 반응성 높은 UI를 구현할 수 있습니다.

1. 동적 연결 (Connecting Indirectly)

Connections 요소를 사용하면 특정 객체의 신호를 다른 객체의 슬롯(함수)에 연결할 수 있습니다. 런타임에 연결할 대상 객체(target)를 변경해야 하는 경우, Connectionstarget 속성을 동적으로 수정하여 구현할 수 있습니다. 이는 여러 객체의 이벤트를 하나의 Connections 요소로 처리해야 할 때 유용합니다.

예제 코드:


Rectangle {
    id: container
    width: 600
    height: 400
    color: "white"

    // 왼쪽 영역
    Column {
        anchors.top: parent.top
        anchors.left: parent.left
        spacing: 20

        Rectangle {
            width: 290
            height: 50
            color: "lightGray"
            MouseArea {
                anchors.fill: parent
                onClicked: container.state = "leftActive"
            }
            Text {
                anchors.centerIn: parent
                font.pixelSize: 30
                text: container.state === "leftActive" ? "Active" : "Inactive"
            }
        }

        Rectangle {
            id: leftRect
            width: 290
            height: 200
            color: "green"
            MouseArea {
                anchors.fill: parent
                onClicked: leftClickedAnim.start()
            }
            Text {
                anchors.centerIn: parent
                font.pixelSize: 30
                color: "white"
                text: "Left Click"
            }
        }
    }

    // 오른쪽 영역
    Column {
        anchors.top: parent.top
        anchors.right: parent.right
        spacing: 20

        Rectangle {
            width: 290
            height: 50
            color: "lightGray"
            MouseArea {
                anchors.fill: parent
                onClicked: container.state = "rightActive"
            }
            Text {
                anchors.centerIn: parent
                font.pixelSize: 30
                text: container.state === "rightActive" ? "Active" : "Inactive"
            }
        }

        Rectangle {
            id: rightRect
            width: 290
            height: 200
            color: "blue"
            MouseArea {
                anchors.fill: parent
                onClicked: rightClickedAnim.start()
            }
            Text {
                anchors.centerIn: parent
                font.pixelSize: 30
                color: "white"
                text: "Right Click"
            }
        }
    }

    // 상태 표시 텍스트
    Text {
        id: statusText
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 50
        font.pixelSize: 30
        color: "red"
        text: "Area Clicked!"
        opacity: 0
    }

    // 왼쪽 클릭 애니메이션
    SequentialAnimation {
        id: leftClickedAnim
        PropertyAction { target: leftRect; property: "color"; value: "white" }
        ColorAnimation { target: leftRect; property: "color"; to: "green"; duration: 3000 }
    }

    // 오른쪽 클릭 애니메이션
    SequentialAnimation {
        id: rightClickedAnim
        PropertyAction { target: rightRect; property: "color"; value: "white" }
        ColorAnimation { target: rightRect; property: "color"; to: "blue"; duration: 3000 }
    }

    // 상태 텍스트 애니메이션
    SequentialAnimation {
        id: statusTextAnim
        PropertyAction { target: statusText; property: "opacity"; value: 1 }
        PropertyAnimation { target: statusText; property: "opacity"; to: 0; duration: 3000 }
    }

    Connections {
        id: handlerConnections
        // 초기 target은 오른쪽 마우스 영역으로 설정
        target: rightMouseArea
        onClicked: statusTextAnim.start()
    }

    states: [
        State {
            name: "leftActive"
            StateChangeScript {
                script: handlerConnections.target = leftMouseArea // 왼쪽 마우스 영역으로 target 변경
            }
        },
        State {
            name: "rightActive"
            StateChangeScript {
                script: handlerConnections.target = rightMouseArea // 오른쪽 마우스 영역으로 target 변경
            }
        }
    ]

    // 초기 상태 설정
    Component.onCompleted: {
        container.state = "leftActive"
    }
}
    

위 예제에서 Connections 요소는 처음에는 rightMouseAreatarget으로 설정합니다. 그러나 Component.onCompletedstates를 통해 container의 상태가 변경될 때마다 handlerConnections.target이 동적으로 leftMouseArea 또는 rightMouseArea로 변경됩니다. 이를 통해 사용자가 어느 영역을 클릭하느냐에 따라 연결되는 신호의 대상이 달라지게 됩니다.

2. 동적 바인딩 (Binding Indirectly)

Binding 요소를 사용하면 한 객체의 속성 값을 다른 객체의 속성 값에 연결(바인딩)할 수 있습니다. 런타임에 바인딩할 대상 객체(target)나 속성(property)을 동적으로 결정해야 할 경우 Binding을 활용할 수 있습니다. 이는 UI의 상태나 로딩된 컴포넌트에 따라 다른 객체의 속성을 참조해야 할 때 유용합니다.

예제 코드:


Rectangle {
    id: root
    width: 600
    height: 400
    property int currentSpeed: 0 // 현재 속도 값

    // 속도 변화 애니메이션
    SequentialAnimation {
        running: true
        loops: Animation.Infinite
        NumberAnimation { target: root; property: "currentSpeed"; to: 145; easing.type: Easing.InOutQuad; duration: 4000 }
        NumberAnimation { target: root; property: "currentSpeed"; to: 10; easing.type: Easing.InOutQuad; duration: 2000 }
    }

    Loader {
        id: contentLoader
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.top: parent.top
        anchors.bottom: controlsRect.top
        onLoaded: {
            // Loader가 컴포넌트를 로드한 후, 바인딩 대상(target)을 설정
            speedBinder.target = contentLoader.item
        }
    }

    Binding {
        id: speedBinder
        // target은 onLoaded 시점에 동적으로 설정됨
        property: "speed" // 바인딩할 속성 이름
        value: root.currentSpeed // root의 currentSpeed 값을 사용
    }

    // 하단 컨트롤 영역
    Rectangle {
        id: controlsRect
        anchors.left: parent.left
        anchors.bottom: parent.bottom
        width: parent.width / 2
        height: 100
        color: "gray"
        Text { anchors.centerIn: parent; text: "Analog" }
        MouseArea {
            anchors.fill: parent
            onClicked: root.state = "analogView"
        }
    }

    Rectangle {
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        width: parent.width / 2
        height: 100
        color: "gray"
        Text { anchors.centerIn: parent; text: "Digital" }
        MouseArea {
            anchors.fill: parent
            onClicked: root.state = "digitalView"
        }
    }

    states: [
        State {
            name: "analogView"
            PropertyChanges { target: controlsRect; color: "green" }
            PropertyChanges { target: contentLoader; source: "AnalogDisplay.qml" } // AnalogDisplay.qml 로드
        },
        State {
            name: "digitalView"
            PropertyChanges { target: controlsRect; color: "green" } // 해당 컨트롤 영역 색상 변경
            PropertyChanges { target: contentLoader; source: "DigitalDisplay.qml" } // DigitalDisplay.qml 로드
        }
    ]
    initial: "analogView" // 초기 상태 설정
}
    

이 예제에서는 Loader를 사용하여 AnalogDisplay.qml 또는 DigitalDisplay.qml을 동적으로 로드합니다. LoaderonLoaded 신호 핸들러에서 speedBindertargetcontentLoader.item (즉, 로드된 QML 컴포넌트)으로 설정합니다. speedBinderroot.currentSpeed 값을 로드된 컴포넌트의 speed 속성에 바인딩합니다. 이를 통해 로드되는 컴포넌트가 다르더라도 항상 root.currentSpeed 값에 따라 해당 컴포넌트의 speed 속성이 업데이트됩니다.

3. 바인딩 관리

바인딩은 속성 간의 연결을 유지하지만, 직접적인 할당(assignment)은 바인딩을 파괴합니다. 바인딩을 복원하려면 Qt.binding() 함수를 사용하거나 Binding 요소의 when 조건을 활용할 수 있습니다.

바인딩 파괴 및 복원 (직접 할당 vs Qt.binding):


// 직접 할당은 바인딩을 파괴합니다.
// item2.value = item1.value1 + 1

// Qt.binding()을 사용하면 바인딩을 복원하거나 새로 생성할 수 있습니다.
// item2.value = Qt.binding(function() { return item1.value1 + 1; })
    

바인딩 조건 제어 (Binding when):


property bool enableBinding: true

Binding {
    target: item2
    property: "value2"
    value: item1.value1 + 1
    when: enableBinding // enableBinding이 true일 때만 바인딩 활성화
}

// 버튼 클릭 시 바인딩 비활성화 및 값 직접 할당
Button {
    onClicked: {
        enableBinding = false // 바인딩 비활성화
        item2.value = item1.value1 + 10 // 직접 값 할당 (바인딩 파괴)
    }
}

// 버튼 클릭 시 바인딩 다시 활성화
Button {
    onClicked: {
        enableBinding = true // 바인딩 활성화 (다시 연결됨)
    }
}
    

이러한 기법들을 활용하면 QML 애플리케이션에서 동적인 데이터 흐름과 UI 업데이트를 효과적으로 관리할 수 있습니다.

태그: QML Qt Connections Binding State

7월 20일 16:11에 게시됨