'2026/01'에 해당되는 글 54건
- 2026.01.15 멍
- 2026.01.14 qt quick websocket
- 2026.01.14 qt qml loader
- 2026.01.13 qstackedwidget, qstackedlayout
- 2026.01.13 qt quick 예제 calqlatr 코드분석
- 2026.01.13 하모닉 드라이브, 사이클로이드 드라이브
- 2026.01.13 qt qml view
- 2026.01.12 qt quick 이미지 클릭
- 2026.01.12 esp32 와 spi lcd 성능 비..교?
- 2026.01.12 엔코더 파형
socket은 좀더 찾아 봐야 할 것 같은데
내부통신이 아닌 외부장치와의 통신은 qt quick / qml 에서 어떻게 구현되는지 궁금해짐

| qmlwebsocketserver main.qml | qmlwebsocketclient main.qml |
| // Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com> // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick import QtWebSockets Rectangle { width: 360 height: 360 function appendMessage(message) { messageBox.text += "\n" + message } WebSocketServer { id: server listen: true onClientConnected: function(webSocket) { webSocket.onTextMessageReceived.connect(function(message) { appendMessage(qsTr("Server received message: %1").arg(message)); webSocket.sendTextMessage(qsTr("Hello Client!")); }); } onErrorStringChanged: { appendMessage(qsTr("Server error: %1").arg(errorString)); } } WebSocket { id: socket url: server.url onTextMessageReceived: function(message) { appendMessage(qsTr("Client received message: %1").arg(message)); } onStatusChanged: { if (socket.status == WebSocket.Error) { appendMessage(qsTr("Client error: %1").arg(socket.errorString)); } else if (socket.status == WebSocket.Closed) { appendMessage(qsTr("Client socket closed.")); } } } Timer { interval: 100 running: true onTriggered: { socket.active = true; } } Text { id: messageBox text: qsTr("Click to send a message!") anchors.fill: parent MouseArea { anchors.fill: parent onClicked: { socket.sendTextMessage(qsTr("Hello Server!")); } } } } |
// Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick import QtWebSockets Rectangle { width: 640 height: 360 WebSocket { id: socket url: "ws://ws.ifelse.io" onTextMessageReceived: function(message) { messageBox.text = messageBox.text + "\nReceived message: " + message } onStatusChanged: if (socket.status == WebSocket.Error) { console.log("Error: " + socket.errorString) } else if (socket.status == WebSocket.Open) { socket.sendTextMessage("Hello World") } else if (socket.status == WebSocket.Closed) { messageBox.text += "\nSocket closed" } active: false } WebSocket { id: secureWebSocket url: "wss://ws.ifelse.io" onTextMessageReceived: function(message) { messageBox.text = messageBox.text + "\nReceived secure message: " + message } onStatusChanged: if (secureWebSocket.status == WebSocket.Error) { console.log("Error: " + secureWebSocket.errorString) } else if (secureWebSocket.status == WebSocket.Open) { secureWebSocket.sendTextMessage("Hello Secure World") } else if (secureWebSocket.status == WebSocket.Closed) { messageBox.text += "\nSecure socket closed" } active: false } Text { id: messageBox text: socket.status == WebSocket.Open ? qsTr("Sending...") : qsTr("Welcome!") anchors.centerIn: parent } MouseArea { anchors.fill: parent onClicked: { socket.active = !socket.active secureWebSocket.active = !secureWebSocket.active; //Qt.quit(); } } } |
다른 예제
'Programming > qt' 카테고리의 다른 글
| qt qml 와 c++ 상호연동 (0) | 2026.01.16 |
|---|---|
| qt qml loader (0) | 2026.01.14 |
| qstackedwidget, qstackedlayout (0) | 2026.01.13 |
| qt quick 예제 calqlatr 코드분석 (0) | 2026.01.13 |
| qt qml view (0) | 2026.01.13 |
로더라는 말은 몇번 봤는데 예제는 이제 찾아봄.
구현 자체는 무지 쉬워보이는데 라이프 사이클이라던가 좀 봐야할 듯.
| import QtQuick Item { width: 200; height: 200 Loader { id: pageLoader } MouseArea { anchors.fill: parent onClicked: pageLoader.source = "Page1.qml" } } |
[링크 : https://doc.qt.io/qt-6/ko/qml-qtquick-loader.html]
'Programming > qt' 카테고리의 다른 글
| qt qml 와 c++ 상호연동 (0) | 2026.01.16 |
|---|---|
| qt quick websocket (0) | 2026.01.14 |
| qstackedwidget, qstackedlayout (0) | 2026.01.13 |
| qt quick 예제 calqlatr 코드분석 (0) | 2026.01.13 |
| qt qml view (0) | 2026.01.13 |
dialog 띄우고 show, hide 하는것도 방법이지만
layout 에다가 widget을 추가하고 layout을 stack 해서 새 창을 띄울 것들을 새로운 stack에 넣어서 하는 것도 방법이고
아니면 위젯을 스택해서 쓰는것도 방법일 듯 한데
구조적으로는 stackedlayout에 widget을 넣어 운영하는게 좀 더 맞는 듯?
| QStackedLayout | QStackedWidget |
| import sys, os # Qt 바인딩을 PySide6 우선 사용하고, 실패 시 PyQt6 사용 qt_modules = None # PySide6을 먼저 시도 try: from PySide6.QtWidgets import ( QApplication, QWidget, QStackedLayout, QVBoxLayout, QLabel, QComboBox, ) from PySide6.QtGui import QPixmap qt_modules = 'PySide6' except ImportError: # 실패 시 PyQt6 시도 try: from PyQt6.QtWidgets import ( QApplication, QWidget, QStackedLayout, QVBoxLayout, QLabel, QComboBox, ) from PyQt6.QtGui import QPixmap qt_modules = 'PyQt6' except ImportError: # 둘 다 실패하면 메시지 출력 후 종료 print("There is no Qt Binding for Python.") sys.exit(1) # 사용된 Qt 바인딩 이름 출력 print(f"Using {qt_modules} binding.") # ------------------------------------ # 메인 윈도우 클래스 정의 class MW(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): # 윈도우 타이틀 설정 self.setWindowTitle("Ex Input Widgets") # 메인 위젯 및 레이아웃 구성 self.setup_main_wnd() # 윈도우 표시 self.show() def setup_main_wnd(self): # 현재 파일의 절대 경로 기준 디렉토리 경로 얻기 fpath = os.path.dirname( os.path.abspath(__file__) ) # 콤보박스에 표시될 페이지 이름들 pages = ['faith', 'hope', 'love'] # 각 페이지에 해당하는 이미지 파일 경로 리스트 self.imgs = [ os.path.join(fpath, 'img','faith.png'), os.path.join(fpath, 'img','hope.png'), os.path.join(fpath, 'img','love.png') ] # 콤보박스 생성 및 항목 추가 combo_box = QComboBox() combo_box.addItems(pages) # 콤보박스 항목이 선택되면 change_page 함수 실행 combo_box.activated.connect(self.change_page) # QStackedLayout 생성 (여러 페이지를 겹쳐서 보관, 하나만 보여줌) self.stacked_lm = QStackedLayout() # 페이지 수만큼 QLabel 생성 후 QStackedLayout에 추가 for idx, c in enumerate(pages): label = self.setup_page(idx) # QLabel을 생성하고 이미지 설정 self.stacked_lm.addWidget(label) # 스택에 추가 # 수직 박스 레이아웃 생성 v_box_lm = QVBoxLayout() v_box_lm.addWidget(combo_box) # 콤보박스를 위에 추가 # 스택 레이아웃을 아래에 추가 # v_box_lm.addLayout(self.stacked_lm) # 이 한줄로 아래 3개라인을 대체 가능. tmp_c = QWidget() tmp_c.setLayout(self.stacked_lm) v_box_lm.addWidget(tmp_c) # 최종 레이아웃을 윈도우에 설정 self.setLayout(v_box_lm) # 페이지용 QLabel 설정 함수 def setup_page(self, page_num): label = QLabel() pixmap = QPixmap(self.imgs[page_num]) # 해당 이미지 불러오기 label.setPixmap(pixmap) # QLabel에 이미지 설정 label.setScaledContents(True) # QLabel 크기에 맞게 이미지 자동 조절 return label # 콤보박스에서 선택된 인덱스에 해당하는 페이지를 보여줌 def change_page(self, idx): self.stacked_lm.setCurrentIndex(idx) # ------------------------------------ # 프로그램 진입점 if __name__ == "__main__": print(os.path.realpath(__file__)) # 현재 실행 중인 파일 경로 출력 app = QApplication(sys.argv) # QApplication 인스턴스 생성 main_wnd = MW() # 메인 윈도우 생성 sys.exit(app.exec()) # 이벤트 루프 실행 |
import sys, os # Qt 바인딩을 PySide6 우선, PyQt6는 백업용 qt_modules = None try: from PySide6.QtWidgets import ( QApplication, QWidget, QLabel, QVBoxLayout, QComboBox, QStackedWidget ) from PySide6.QtGui import QPixmap qt_modules = 'PySide6' except ImportError: try: from PyQt6.QtWidgets import ( QApplication, QWidget, QLabel, QVBoxLayout, QComboBox, QStackedWidget ) from PyQt6.QtGui import QPixmap qt_modules = 'PyQt6' except ImportError: print("There is no Qt Binding for Python.") sys.exit(1) print(f"Using {qt_modules} binding.") # ------------------------------------ class MW(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("Ex: QStackedWidget with ComboBox") self.setup_main_wnd() self.show() def setup_main_wnd(self): # 현재 스크립트 경로를 기준으로 이미지 파일 위치 설정 fpath = os.path.dirname(os.path.abspath(__file__)) # 페이지 이름과 이미지 경로 정의 pages = ['faith', 'hope', 'love'] self.imgs = [ os.path.join(fpath, 'img/faith.png'), os.path.join(fpath, 'img/hope.png'), os.path.join(fpath, 'img/love.png') ] # 콤보박스 생성 및 페이지 이름 추가 combo_box = QComboBox() combo_box.addItems(pages) combo_box.activated.connect(self.change_page) # QStackedWidget 생성 self.stack_widget = QStackedWidget() # 각 페이지에 해당하는 QLabel + 이미지 추가 for idx in range(len(pages)): label = QLabel() pixmap = QPixmap(self.imgs[idx]) label.setPixmap(pixmap) label.setScaledContents(True) # 이미지가 QLabel에 맞게 리사이즈됨 self.stack_widget.addWidget(label) # 수직 레이아웃 구성: 콤보박스 위, 이미지 아래 layout = QVBoxLayout() layout.addWidget(combo_box) layout.addWidget(self.stack_widget) self.setLayout(layout) # 콤보박스 인덱스 선택 시 보여줄 페이지 변경 def change_page(self, idx): self.stack_widget.setCurrentIndex(idx) # ------------------------------------ if __name__ == "__main__": print(os.path.realpath(__file__)) app = QApplication(sys.argv) main_wnd = MW() sys.exit(app.exec()) |
[링크 : https://wikidocs.net/185879]
qstackedwidget
[링크 : https://wikidocs.net/162838]
[링크 : https://doc.qt.io/qt-6/qwidget.html]
[링크 : https://doc.qt.io/qt-6/qstackedwidget.html]
[링크 : https://qt-dev.com/board.php?board=qnaboard&page=4&category=1&command=body&no=758]
[링크 : https://hobbylife.tistory.com/entry/PySide6-QStackedWidget-완전-정복-–-클릭-이벤트와-사용법]
qstackedlayout
'Programming > qt' 카테고리의 다른 글
| qt quick websocket (0) | 2026.01.14 |
|---|---|
| qt qml loader (0) | 2026.01.14 |
| qt quick 예제 calqlatr 코드분석 (0) | 2026.01.13 |
| qt qml view (0) | 2026.01.13 |
| qt quick 이미지 클릭 (0) | 2026.01.12 |
qt quick 으로 작성된 계산기 어플 소스 분석

실행하면 평범한 계산기가 뜬다.

가로로 길게 늘리면 공학계산기로 바뀐다.

프로젝트는 아래와 같이 구성되어 있고

main.cpp
언제나 그렇듯(?) main.cpp 는 조촐하고 별 내용이 없다.
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickStyle>
int main(int argc, char *argv[])
{
QCoreApplication::setOrganizationName("QtProject");
QCoreApplication::setApplicationName("Calqlatr");
QGuiApplication app(argc, argv);
QQuickStyle::setStyle("Basic");
QQmlApplicationEngine engine;
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
&app, []() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.loadFromModule("demos.calqlatr", "Main");
return app.exec();
}
Main.qml
전체 모양을 그리는 녀석인데, design 에서 보면 아래처럼 보인다.

다만 qt designer의 버그인지 ApplicationState 의 id: state 구문이 에러가 나는데
위지윅 에디터에서 보려면 해당 라인을 주석처리 하면 된다. (물론 실행하면 작동안하게 되는 버그 발생)

column, row Layout 으로 배치를 어떻게 하는것 같고
Keys.onPressed: function (event) {} 를 통해서 키 입력시 state에 추가하는 식으로 작동하게 된다.
| // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick import QtQuick.Layouts Window { visible: true width: 320 height: 480 minimumWidth: Math.max(numberPad.portraitModeWidth, display.minWidth) + root.margin * 2 minimumHeight: display.minHeight + numberPad.height + root.margin * 3 color: root.backgroundColor Item { id: root anchors.fill: parent anchors.topMargin: parent.SafeArea.margins.top anchors.leftMargin: parent.SafeArea.margins.left anchors.rightMargin: parent.SafeArea.margins.right anchors.bottomMargin: parent.SafeArea.margins.bottom readonly property int margin: 18 readonly property color backgroundColor: "#222222" readonly property int minLandscapeModeWidth: numberPad.landscapeModeWidth + display.minWidth + margin * 3 property bool isPortraitMode: root.width < root.minLandscapeModeWidth ApplicationState { id: state display: display } Display { id: display readonly property int minWidth: 210 readonly property int minHeight: 60 Layout.minimumWidth: minWidth Layout.fillWidth: true Layout.fillHeight: true Layout.margins: root.margin // remove the margin on the side that the numberPad is on, to prevent a double margin Layout.bottomMargin: root.isPortraitMode ? 0 : root.margin Layout.rightMargin: root.isPortraitMode ? root.margin : 0 } NumberPad { id: numberPad Layout.margins: root.margin isPortraitMode: root.isPortraitMode state: state } // define the responsive layouts ColumnLayout { id: portraitMode anchors.fill: parent visible: root.isPortraitMode LayoutItemProxy { target: display Layout.minimumHeight: display.minHeight } LayoutItemProxy { target: numberPad Layout.alignment: Qt.AlignHCenter } } RowLayout { id: landscapeMode anchors.fill: parent visible: !root.isPortraitMode LayoutItemProxy { target: display } LayoutItemProxy { target: numberPad Layout.alignment: Qt.AlignVCenter } } Keys.onPressed: function (event) { switch (event.key) { case Qt.Key_0: state.digitPressed("0"); break; case Qt.Key_1: state.digitPressed("1"); break; case Qt.Key_2: state.digitPressed("2"); break; case Qt.Key_3: state.digitPressed("3"); break; case Qt.Key_4: state.digitPressed("4"); break; case Qt.Key_5: state.digitPressed("5"); break; case Qt.Key_6: state.digitPressed("6"); break; case Qt.Key_7: state.digitPressed("7"); break; case Qt.Key_8: state.digitPressed("8"); break; case Qt.Key_9: state.digitPressed("9"); break; case Qt.Key_E: state.digitPressed("e"); break; case Qt.Key_P: state.digitPressed("π"); break; case Qt.Key_Plus: state.operatorPressed("+"); break; case Qt.Key_Minus: state.operatorPressed("-"); break; case Qt.Key_Asterisk: state.operatorPressed("×"); break; case Qt.Key_Slash: state.operatorPressed("÷"); break; case Qt.Key_Enter: case Qt.Key_Return: state.operatorPressed("="); break; case Qt.Key_Comma: case Qt.Key_Period: state.digitPressed("."); break; case Qt.Key_Backspace: state.operatorPressed("bs"); break; } } } } |
ApplicationState.qml
말로는 qml 인데 design에서 보여지는 요소는 존재하지 않고
main.qml 에서 호출되는 operatorPressed() 나 digitPressed()와 같은 함수가 존재한다.
특이한건 import as 인데 js를 불러서 CalcEngine 으로 사용하는 부분 정도?
그 와중에 Display는 display.qml 에서 끌려오는건가?
| // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQml import "calculator.js" as CalcEngine QtObject { required property Display display function operatorPressed(operator) { CalcEngine.operatorPressed(operator, display); } function digitPressed(digit) { CalcEngine.digitPressed(digit, display); } function isButtonDisabled(op) { return CalcEngine.isOperationDisabled(op, display); } } |
calculator.js
평범한(?) js로 작성된 코드가 존재한다.
| // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause let accumulator = 0 let pendingOperator = "" let lastButton = "" let digits = "" function isOperationDisabled(op, display) { if (digits !== "" && lastButton !== "=" && (op === "π" || op === "e")) return true if (digits === "" && !((op >= "0" && op <= "9") || op === "π" || op === "e" || op === "AC")) return true if (op === "bs" && (display.isOperandEmpty() || !((lastButton >= "0" && lastButton <= "9") || lastButton === "π" || lastButton === "e" || lastButton === "."))) return true if (op === '=' && pendingOperator.length != 1) return true if (op === "." && digits.search(/\./) != -1) return true if (op === "√" && digits.search(/-/) != -1) return true if (op === "AC" && display.isDisplayEmpty()) return true return false } function digitPressed(op, display) { if (isOperationDisabled(op, display)) return if (lastButton === "π" || lastButton === "e") return // handle mathematical constants if (op === "π") { lastButton = op digits = Math.PI.toPrecision(display.maxDigits - 1).toString() display.appendDigit(digits) return } if (op === "e") { lastButton = op digits = Math.E.toPrecision(display.maxDigits - 1).toString() display.appendDigit(digits) return } // append a digit to another digit or decimal point if (lastButton.toString().length === 1 && ((lastButton >= "0" && lastButton <= "9") || lastButton === ".") ) { if (digits.length >= display.maxDigits) return digits = digits + op.toString() display.appendDigit(op.toString()) // else just write a single digit to display } else { digits = op.toString() display.appendDigit(digits) } lastButton = op } function operatorPressed(op, display) { if (isOperationDisabled(op, display)) return if (op === "±") { digits = Number(digits.valueOf() * -1).toString() display.setDigit(display.displayNumber(Number(digits))) return } if (op === "bs") { digits = digits.slice(0, -1) if (digits === "-") digits = "" display.backspace() return } lastButton = op if (pendingOperator === "+") { digits = (Number(accumulator) + Number(digits.valueOf())).toString() } else if (pendingOperator === "−") { digits = (Number(accumulator) - Number(digits.valueOf())).toString() } else if (pendingOperator === "×") { digits = (Number(accumulator) * Number(digits.valueOf())).toString() } else if (pendingOperator === "÷") { digits = (Number(accumulator) / Number(digits.valueOf())).toString() } if (op === "+" || op === "−" || op === "×" || op === "÷") { pendingOperator = op accumulator = digits.valueOf() digits = "" display.displayOperator(pendingOperator) return } accumulator = 0 pendingOperator = "" if (op === "=") { display.newLine("=", Number(digits)) } if (op === "√") { digits = (Math.sqrt(digits.valueOf())).toString() display.newLine("√", Number(digits)) } else if (op === "⅟x") { digits = (1 / digits.valueOf()).toString() display.newLine("⅟x", Number(digits)) } else if (op === "x²") { digits = (digits.valueOf() * digits.valueOf()).toString() display.newLine("x²", Number(digits)) } else if (op === "x³") { digits = (digits.valueOf() * digits.valueOf() * digits.valueOf()).toString() display.newLine("x³", Number(digits)) } else if (op === "|x|") { digits = (Math.abs(digits.valueOf())).toString() display.newLine("|x|", Number(digits)) } else if (op === "⌊x⌋") { digits = (Math.floor(digits.valueOf())).toString() display.newLine("⌊x⌋", Number(digits)) } else if (op === "sin") { digits = Number(Math.sin(digits.valueOf())).toString() display.newLine("sin", Number(digits)) } else if (op === "cos") { digits = Number(Math.cos(digits.valueOf())).toString() display.newLine("cos", Number(digits)) } else if (op === "tan") { digits = Number(Math.tan(digits.valueOf())).toString() display.newLine("tan", Number(digits)) } else if (op === "log") { digits = Number(Math.log10(digits.valueOf())).toString() display.newLine("log", Number(digits)) } else if (op === "ln") { digits = Number(Math.log(digits.valueOf())).toString() display.newLine("ln", Number(digits)) } if (op === "AC") { display.allClear() accumulator = 0 lastButton = "" digits = "" pendingOperator = "" } } |
Display.qml
design 상에서는 별 내용이 없어 보이지만

코드에서는 라인별로 추가하는 등 제법 ui를 건드리는 작동을 많이 한다.
| // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause pragma ComponentBehavior: Bound import QtQuick Item { id: display property int fontSize: 22 readonly property int maxDigits: Math.min((width / fontSize) + 1, 9) readonly property color backgroundColor: "#262626" readonly property color qtGreenColor: "#2CDE85" property string displayedOperand: "" readonly property string errorString: qsTr("ERROR") readonly property bool isError: displayedOperand === errorString property bool enteringDigits: false function displayOperator(operator) { calculationsListView.model.append({ "operator": operator, "operand": "" }); enteringDigits = true; calculationsListView.positionViewAtEnd(); } function newLine(operator, operand) { displayedOperand = displayNumber(operand); calculationsListView.model.append({ "operator": operator, "operand": displayedOperand }); enteringDigits = false; calculationsListView.positionViewAtEnd(); } function appendDigit(digit) { if (!enteringDigits) calculationsListView.model.append({ "operator": "", "operand": "" }); const i = calculationsListView.model.count - 1; calculationsListView.model.get(i).operand = calculationsListView.model.get(i).operand + digit; enteringDigits = true; calculationsListView.positionViewAtEnd(); } function setDigit(digit) { const i = calculationsListView.model.count - 1; calculationsListView.model.get(i).operand = digit; calculationsListView.positionViewAtEnd(); } function backspace() { const i = calculationsListView.model.count - 1; if (i >= 0) { let operand = calculationsListView.model.get(i).operand.toString().slice(0, -1); if (operand === "-") operand = ""; calculationsListView.model.get(i).operand = operand; return; } return; } function isOperandEmpty() { const i = calculationsListView.model.count - 1; return i >= 0 ? calculationsListView.model.get(i).operand === "" : true; } function isDisplayEmpty() { const i = calculationsListView.model.count - 1; return i == -1 ? true : (i == 0 ? calculationsListView.model.get(0).operand === "" : false); } function clear() { displayedOperand = ""; if (enteringDigits) { const i = calculationsListView.model.count - 1; if (i >= 0) calculationsListView.model.remove(i); enteringDigits = false; } } function allClear() { display.clear(); calculationsListView.model.clear(); enteringDigits = false; } // Returns a string representation of a number that fits in // display.maxDigits characters, trying to keep as much precision // as possible. If the number cannot be displayed, returns an // error string. function displayNumber(num) { if (typeof (num) !== "number") return errorString; // deal with the absolute const abs = Math.abs(num); if (abs.toString().length <= maxDigits) { return isFinite(num) ? num.toString() : errorString; } if (abs < 1) { // check if abs < 0.00001, if true, use exponential form // if it isn't true, we can round the number without losing // too much precision if (Math.floor(abs * 100000) === 0) { const expVal = num.toExponential(maxDigits - 6).toString(); if (expVal.length <= maxDigits + 1) return expVal; } else { // the first two digits are zero and . return num.toFixed(maxDigits - 2); } } else { // if the integer part of num is greater than maxDigits characters, use exp form const intAbs = Math.floor(abs); if (intAbs.toString().length <= maxDigits) return parseFloat(num.toPrecision(maxDigits - 1)).toString(); const expVal = num.toExponential(maxDigits - 6).toString(); if (expVal.length <= maxDigits + 1) return expVal; } return errorString; } Item { anchors.fill: parent Rectangle { anchors.fill: parent radius: 8 color: display.backgroundColor ListView { id: calculationsListView x: 5 y: 10 width: parent.width height: parent.height - 2 * y clip: true delegate: Item { height: display.fontSize * 1.1 width: calculationsListView.width required property string operator required property string operand Text { x: 6 font.pixelSize: display.fontSize color: display.qtGreenColor text: parent.operator Accessible.name: parent.operator } Text { font.pixelSize: display.fontSize anchors.right: parent.right anchors.rightMargin: 16 text: parent.operand Accessible.name: parent.operand color: "white" } } model: ListModel {} onHeightChanged: positionViewAtEnd() } } } } |
Numpad.qml
계산기의 버튼 부분인것 같은데 rectangle 안에 Rowlayout 안에 GridLayout이 있는데 그래서 그런가 이상하게 보여지는 느낌.

그나저나 제곱이나 sin tan 같은 과학계산기도 넣으려다가 흔적만 남은건가?
| // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts Item { id: controller required property bool isPortraitMode required property ApplicationState state readonly property color qtGreenColor: "#2CDE85" readonly property color backspaceRedColor: "#DE2C2C" readonly property int spacing: 5 property int portraitModeWidth: mainGrid.width property int landscapeModeWidth: scientificGrid.width + mainGrid.width implicitWidth: isPortraitMode ? portraitModeWidth : landscapeModeWidth implicitHeight: mainGrid.height function updateDimmed() { for (let i = 0; i < mainGrid.children.length; i++) { mainGrid.children[i].dimmed = state.isButtonDisabled(mainGrid.children[i].text); } for (let j = 0; j < scientificGrid.children.length; j++) { scientificGrid.children[j].dimmed = state.isButtonDisabled( scientificGrid.children[j].text); } } component DigitButton: CalculatorButton { onClicked: { controller.state.digitPressed(text); controller.updateDimmed(); } } component OperatorButton: CalculatorButton { dimmable: true implicitWidth: 48 textColor: controller.qtGreenColor onClicked: { controller.state.operatorPressed(text); controller.updateDimmed(); } } Component.onCompleted: updateDimmed() Rectangle { id: numberPad anchors.fill: parent radius: 8 color: "transparent" RowLayout { spacing: controller.spacing GridLayout { id: scientificGrid columns: 3 columnSpacing: controller.spacing rowSpacing: controller.spacing visible: !controller.isPortraitMode OperatorButton { text: "x²" Accessible.name: "x squared" } OperatorButton { text: "⅟x" Accessible.name: "one over x" } OperatorButton { text: "√" } OperatorButton { text: "x³" Accessible.name: "x cubed" } OperatorButton { text: "sin" Accessible.name: "sine" } OperatorButton { text: "|x|" Accessible.name: "absolute value" } OperatorButton { text: "log" } OperatorButton { text: "cos" Accessible.name: "cosine" } DigitButton { text: "e" dimmable: true implicitWidth: 48 } OperatorButton { text: "ln" } OperatorButton { text: "tan" } DigitButton { text: "π" dimmable: true implicitWidth: 48 } } GridLayout { id: mainGrid columns: 5 columnSpacing: controller.spacing rowSpacing: controller.spacing BackspaceButton { onClicked: { controller.state.operatorPressed(this.text); controller.updateDimmed(); } } DigitButton { text: "7" } DigitButton { text: "8" } DigitButton { text: "9" } OperatorButton { text: "÷" implicitWidth: 38 } OperatorButton { text: "AC" textColor: controller.backspaceRedColor accentColor: controller.backspaceRedColor } DigitButton { text: "4" } DigitButton { text: "5" } DigitButton { text: "6" } OperatorButton { text: "×" implicitWidth: 38 } OperatorButton { text: "=" implicitHeight: 81 Layout.rowSpan: 2 } DigitButton { text: "1" } DigitButton { text: "2" } DigitButton { text: "3" } OperatorButton { text: "−" implicitWidth: 38 } OperatorButton { text: "±" implicitWidth: 38 } DigitButton { text: "0" } DigitButton { text: "." dimmable: true } OperatorButton { text: "+" implicitWidth: 38 } } } // RowLayout } } |
BackspaceButton.qml
numpad 에서 호출되는 버튼. 그 외에는 크게 눈에 띄진 않네

| // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick import QtQuick.Controls RoundButton { id: button implicitWidth: 48 implicitHeight: 38 radius: buttonRadius icon.source: getIcon() icon.width: 38 icon.height: 38 icon.color: getIconColor() // include this text property as the calculator engine // differentiates buttons through text. The text is never drawn. text: "bs" Accessible.name: "backspace" property bool dimmable: true property bool dimmed: false readonly property color backgroundColor: "#222222" readonly property color borderColor: "#A9A9A9" readonly property color backspaceRedColor: "#DE2C2C" readonly property int buttonRadius: 8 function getBackgroundColor() { if (button.dimmable && button.dimmed) return backgroundColor; if (button.pressed) return backspaceRedColor; return backgroundColor; } function getBorderColor() { if (button.dimmable && button.dimmed) return borderColor; if (button.pressed || button.hovered) return backspaceRedColor; return borderColor; } function getIconColor() { if (button.dimmable && button.dimmed) return Qt.darker(backspaceRedColor); if (button.pressed) return backgroundColor; return backspaceRedColor; } function getIcon() { if (button.dimmable && button.dimmed) return "images/backspace.svg"; if (button.pressed) return "images/backspace_fill.svg"; return "images/backspace.svg"; } background: Rectangle { radius: button.buttonRadius color: button.getBackgroundColor() border.color: button.getBorderColor() } } |
CalculatorButton.qml
numpad 에서 호출되는 버튼. 그 외에는 크게 눈에 띄진 않네 2

| // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick import QtQuick.Controls RoundButton { id: button implicitWidth: 38 implicitHeight: 38 radius: buttonRadius property bool dimmable: false property bool dimmed: false readonly property int fontSize: 22 readonly property int buttonRadius: 8 property color textColor: "#FFFFFF" property color accentColor: "#2CDE85" readonly property color backgroundColor: "#222222" readonly property color borderColor: "#A9A9A9" function getBackgroundColor() { if (button.dimmable && button.dimmed) return backgroundColor; if (button.pressed) return accentColor; return backgroundColor; } function getBorderColor() { if (button.dimmable && button.dimmed) return borderColor; if (button.pressed || button.hovered) return accentColor; return borderColor; } function getTextColor() { if (button.dimmable && button.dimmed) return Qt.darker(textColor); if (button.pressed) return backgroundColor; if (button.hovered) return accentColor; return textColor; } background: Rectangle { radius: button.buttonRadius color: button.getBackgroundColor() border.color: button.getBorderColor() } contentItem: Text { text: button.text font.pixelSize: button.fontSize horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: button.getTextColor() Behavior on color { ColorAnimation { duration: 120 easing.type: Easing.OutElastic } } } } |
'Programming > qt' 카테고리의 다른 글
| qt qml loader (0) | 2026.01.14 |
|---|---|
| qstackedwidget, qstackedlayout (0) | 2026.01.13 |
| qt qml view (0) | 2026.01.13 |
| qt quick 이미지 클릭 (0) | 2026.01.12 |
| qt creator 18.0.1 design 활성화 하기 (0) | 2026.01.09 |
협동로봇에 많이 사용된다는 고단 변속 드라이브.. 라고 해야하나?
왼쪽이 하모닉 드라이브, 오른쪽으 사이클로이드 드라이브
하모닉 드라이브는 총 3개의 부품으로 끝나고(크게) 사이클로이드는 부품이 좀 많이 들어간다.
높은 토크가 필요한게 아니라면 하모닉 드라이브도 괜찮아 보이지만 중간의 유연성을 지는 부품이 핵심일 것 같은데
아무래도 변형되는 녀석이 있다 보니 내구도가 좋지 않을것 같긴한데.. 알아서 잘 해결했겠지 머

'하드웨어 > 모터(motor)' 카테고리의 다른 글
| 스텝 모터 2상 4상 (0) | 2025.11.28 |
|---|---|
| NEMA 17 스텝 모터? (0) | 2025.02.11 |
| MG-996R 하이토크 디지털 서보 (0) | 2025.01.07 |
| 로봇 다리 (0) | 2021.07.21 |
| 무한회전 서보모터? (0) | 2021.07.13 |
전체적인 레이아웃 잡는데에는 split view 나 stack view 나 tabview 정도가 적절해 보이는군.
| ScrollView | Provides a scrolling view within another Item | ![]() |
| SplitView | Lays out items with a draggable splitter between each item | ![]() |
| StackView | Provides a stack-based navigation model | ![]() |
| TabView | A control that allows the user to select one of multiple stacked items | ![]() |
| TableView | Provides a list view with scroll bars, styling and header sections | ![]() |
| TreeView | Provides a tree view with scroll bars, styling and header sections | ![]() |
[링크 : https://doc.qt.io/archives/qt-5.15/qtquick-controls-qmlmodule.html]
[링크 : https://doc.qt.io/archives/qt-5.15/qml-qtquick-controls-stackview.html]
onclicked 이벤트에서 stackview.push 함수를 이용하여 qml을 넣어주면 끝인듯. 편하겠네
넣는건 push 빼는건 pop. 직관적이네~
| onClicked: { stackView.push(Qt.resolvedUrl("qrc:/screen2.qml"))//다음 화면을 출력하기 위해 stack에 화면을 쌓는 코드 //stack에 메인화면 두번째화면 세번째 화면이 쌓이게 된다. } onClicked: { stackView.pop();//이전화면을 호출하기위해 stack의 제일 위에 화면을 밖으로 빼주어 첫화면을 보여준다. } |
[링크 : https://youonlyliveonce1.tistory.com/16]
qt creator 에서 찾아보니 swipe view라는게 추가되어 보이고, 다른 tab 이나 split view가 안보인다.

'Programming > qt' 카테고리의 다른 글
| qstackedwidget, qstackedlayout (0) | 2026.01.13 |
|---|---|
| qt quick 예제 calqlatr 코드분석 (0) | 2026.01.13 |
| qt quick 이미지 클릭 (0) | 2026.01.12 |
| qt creator 18.0.1 design 활성화 하기 (0) | 2026.01.09 |
| qt 변수 초기화 문법, cpp 초기화 리스트 (0) | 2021.12.08 |
2026.01.13
다시보다 보니 기본 컴포넌트에 mouse area 라는 것이 있다.
이미지 하나 띄우고 마우스 영역을 지정하고 원하는 크기로 조절하면
과거 html 에서 이미지 맵 하듯 가능해진다.

mouse area에서 area는 활성화 되는데 hover는 활성화 되어있지 않아서 체크를 해줘야 하고
체크를 해주면 의도한 대로 작동되게 된다.

----
qt quick 으로 생성해서 이미지를 넣고 사진을 띄우는것 까진 성공!(프로젝트 외부 디렉토리에 이미지는 안됨)
그래서 이미지를 클릭하면 무언가 하려고 하는데
connections 라던가 event list 에서 무언가 쓸만해 보이는게 뜨지 않는다.


그래서 우클릭 후 Add Mouse Area를 해주면

image가 아니라 mouseArea로 잡히고

평범하게 보던 mouse 클릭 이벤트 들을 사용할 수 있게 된다.

확인필요
'Programming > qt' 카테고리의 다른 글
| qt quick 예제 calqlatr 코드분석 (0) | 2026.01.13 |
|---|---|
| qt qml view (0) | 2026.01.13 |
| qt creator 18.0.1 design 활성화 하기 (0) | 2026.01.09 |
| qt 변수 초기화 문법, cpp 초기화 리스트 (0) | 2021.12.08 |
| qt5 fb reset (0) | 2021.02.23 |
문득 남에건 빠른데 내껀 왜 느리지 싶어서 찾아보는 중
내가 가진건 st7735 칩셋이고 얘가 좀 많이 느린듯
[링크 : https://minimonk.tistory.com/524246]
라즈베리 파이용으로 구했던건 dtb 상으로 ili9486 같다. (3.5inch rpi lcd (a) v3)
돌이켜 보면 mame 하기 버거울 정도로 느렸던것 같은데. 아무튼 16MHz 였군
| waveshare35a: waveshare35a@0{ compatible = "ilitek,ili9486"; reg = <0>; pinctrl-names = "default"; pinctrl-0 = <&waveshare35a_pins>; spi-max-frequency = <16000000>; rotate = <90>; bgr; fps = <30>; buswidth = <8>; regwidth = <16>; |
[링크 : https://minimonk.tistory.com/5694]
그래서 나중에 구매한 3.5inch rpi lcd (c) 고속 lcd 인데 칩셋 이름이 없다.
| Supports 125MHz high-speed SPI signal transmission |
dtb 받아서 확인해보니 클럭이 어마어하게 올랐다. 어우 115Mhz 라니 기존대비 7배 올랐네
| $ dtc -I dtb -O dts waveshare35c.dtbo <stdout>: Warning (unit_address_vs_reg): /fragment@0/__overlay__/spidev@0: node has a unit name, but no reg or ranges property <stdout>: Warning (unit_address_vs_reg): /fragment@0/__overlay__/spidev@1: node has a unit name, but no reg or ranges property <stdout>: Warning (gpios_property): /fragment@2/__overlay__/tft35a@0:reset-gpios: Could not get phandle node for (cell 0) <stdout>: Warning (gpios_property): /fragment@2/__overlay__/tft35a@0:dc-gpios: Could not get phandle node for (cell 0) <stdout>: Warning (gpios_property): /fragment@2/__overlay__/tft35a-ts@1:pendown-gpio: Could not get phandle node for (cell 0) <stdout>: Warning (gpios_property): /__fixups__:gpio: property size (218) is invalid, expected multiple of 4 <stdout>: Warning (interrupts_property): /fragment@2/__overlay__/tft35a-ts@1:interrupt-parent: Bad phandle /dts-v1/; / { compatible = "brcm,bcm2835\0brcm,bcm2708\0brcm,bcm2709"; fragment@0 { target = <0xdeadbeef>; __overlay__ { status = "okay"; spidev@0 { status = "disabled"; }; spidev@1 { status = "disabled"; }; }; }; fragment@1 { target = <0xdeadbeef>; __overlay__ { tft35a_pins { brcm,pins = <0x11 0x19 0x18>; brcm,function = <0x00 0x00 0x00>; linux,phandle = <0x01>; phandle = <0x01>; }; }; }; fragment@2 { target = <0xdeadbeef>; __overlay__ { #address-cells = <0x01>; #size-cells = <0x00>; tft35a@0 { compatible = "ilitek,ili9486"; reg = <0x00>; pinctrl-names = "default"; pinctrl-0 = <0x01>; spi-max-frequency = <0x6dac2c0>; txbuflen = <0x8000>; rotate = <0x5a>; bgr = <0x00>; fps = <0x1e>; buswidth = <0x08>; regwidth = <0x10>; reset-gpios = <0xdeadbeef 0x19 0x01>; dc-gpios = <0xdeadbeef 0x18 0x00>; debug = <0x00>; init = <0x10000f1 0x36 0x04 0x00 0x3c 0x0f 0x8f 0x10000f2 0x18 0xa3 0x12 0x02 0xb2 0x12 0xff 0x10 0x00 0x10000f8 0x21 0x04 0x10000f9 0x00 0x08 0x1000036 0x08 0x10000b4 0x00 0x10000c1 0x41 0x10000c5 0x00 0x91 0x80 0x00 0x10000e0 0x0f 0x1f 0x1c 0x0c 0x0f 0x08 0x48 0x98 0x37 0x0a 0x13 0x04 0x11 0x0d 0x00 0x10000e1 0x0f 0x32 0x2e 0x0b 0x0d 0x05 0x47 0x75 0x37 0x06 0x10 0x03 0x24 0x20 0x00 0x100003a 0x55 0x1000011 0x1000036 0x28 0x20000ff 0x1000029>; linux,phandle = <0x02>; phandle = <0x02>; }; tft35a-ts@1 { compatible = "ti,ads7846"; reg = <0x01>; spi-max-frequency = <0x1e8480>; interrupts = <0x11 0x02>; interrupt-parent = <0xdeadbeef>; pendown-gpio = <0xdeadbeef 0x11 0x01>; ti,x-plate-ohms = [00 3c]; ti,pressure-max = [00 ff]; linux,phandle = <0x03>; phandle = <0x03>; }; }; }; __overrides__ { speed = <0x02 0x7370692d 0x6d61782d 0x66726571 0x75656e63 0x793a3000>; txbuflen = [00 00 00 02 74 78 62 75 66 6c 65 6e 3a 30 00]; rotate = [00 00 00 02 72 6f 74 61 74 65 3a 30 00]; fps = [00 00 00 02 66 70 73 3a 30 00]; bgr = [00 00 00 02 62 67 72 3a 30 00]; debug = <0x02 0x64656275 0x673a3000>; swapxy = <0x03 0x74692c73 0x7761702d 0x78793f00>; }; __symbols__ { tft35a_pins = "/fragment@1/__overlay__/tft35a_pins"; tft35a = "/fragment@2/__overlay__/tft35a@0"; tft35a_ts = "/fragment@2/__overlay__/tft35a-ts@1"; }; __fixups__ { spi0 = "/fragment@0:target:0\0/fragment@2:target:0"; gpio = "/fragment@1:target:0\0/fragment@2/__overlay__/tft35a@0:reset-gpios:0\0/fragment@2/__overlay__/tft35a@0:dc-gpios:0\0/fragment@2/__overlay__/tft35a-ts@1:interrupt-parent:0\0/fragment@2/__overlay__/tft35a-ts@1:pendown-gpio:0"; }; __local_fixups__ { fixup = "/fragment@2/__overlay__/tft35a@0:pinctrl-0:0\0/__overrides__:speed:0\0/__overrides__:txbuflen:0\0/__overrides__:rotate:0\0/__overrides__:fps:0\0/__overrides__:bgr:0\0/__overrides__:debug:0\0/__overrides__:swapxy:0"; }; }; |
[링크 : https://minimonk.tistory.com/9776]
예전에 2.4만원 넘어서 안샀던 것들이 ili9341 계열인데, 아래 보면 65fps까지 되는 쩌는 애들이네
[링크 : https://minimonk.tistory.com/12398]
아무튼 저런 애들을 써야 성능이 잘 나온다~ 라는거군
| ESP32 LCDs performance report 1. ST7796 4-wire SPI 16fps 2. ST7796 8-bit 34fps 3. ILI9488 4-wire SPI 11fps 4. ILI9488 8-bit 34fps 5. ST7789 4-wire SPI 65fps 6. ST7789 8-bit 5xfps(weird) 7. ILI9341 4-wire SPI 65fps 8. GC9A01 8xfps 9. ST7789 240x240 8xfps |
[링크 : https://www.facebook.com/groups/797613180807626/posts/1048617505707191/]
'embeded > esp32' 카테고리의 다른 글
| esp32-2432S028 데이터 시트 (0) | 2026.01.17 |
|---|---|
| esp32-2432S028 보드 st7789 spi lcd driver? (0) | 2026.01.16 |
| esp32 c6 zigbee thread matter (0) | 2026.01.10 |
| esp32 benchmark (0) | 2025.11.18 |
| esp32 lvgl (0) | 2025.11.15 |
오실로스코프에 대충 연결하고
파워서플라이에서 3.3V 100mA 만들어서 넣음
1채널 노란색(프로브 노란색) - CLK(보라색)
2채널 파란색(프로브 빨간색) - DT(파란색)

시계방향(CW)로 돌리니 CLK가 먼저 반응하고

반시계방향(CCW)로 돌리니 DT가 먼저 반응한다.

위치에 따라서는 falling이 될수도 rising이 될수도 있긴한데..
이걸 어떻게 해석해야 한 틱 옮겨지고, 갑자기 방향이 바뀌는지 알 수 있을까?






