Qt小心得之3: Layout存在默认Margins和Spacing

本文介绍Qt中layout的Margins和Spacing默认值可能导致的问题及解决方法,通过setContentsMargins和setSpacing自定义布局,实现更精确的界面设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Qt中layout存在默认的Margins和Spacing值, 如果没有出现预期的效果,可能和默认值有关,可以通过设置setContentsMargins和setSpacing来改变设置。

import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Dialogs Item { id: root width: 1000 height: 700 // 主布局 ColumnLayout { anchors.fill: parent anchors.margins: 15 spacing: 15 // 标题 Label { text: "六轴机械手轨迹编辑器" font.bold: true font.pixelSize: 24 Layout.alignment: Qt.AlignHCenter topPadding: 10 bottomPadding: 10 color: "#2c3e50" } // 添加方式选择 RowLayout { Layout.fillWidth: true spacing: 15 Label { text: "添加方式:" font.bold: true font.pixelSize: 14 color: "#34495e" } ButtonGroup { id: addMethodGroup } RadioButton { id: coordMode text: "按坐标添加" checked: true font.pixelSize: 14 ButtonGroup.group: addMethodGroup } RadioButton { id: jointMode text: "按关节角度添加" font.pixelSize: 14 ButtonGroup.group: addMethodGroup } } // 主内容区域 RowLayout { Layout.fillWidth: true Layout.fillHeight: true spacing: 20 // 左侧 - 轨迹点列表 GroupBox { title: "轨迹点列表" Layout.preferredWidth: 350 Layout.fillHeight: true topPadding: 15 bottomPadding: 15 leftPadding: 10 rightPadding: 10 background: Rectangle { color: "#ffffff" border.color: "#dcdde1" radius: 5 } ColumnLayout { anchors.fill: parent spacing: 10 // 轨迹点表格 Rectangle { Layout.fillWidth: true Layout.fillHeight: true color: "#f5f6fa" border.color: "#dcdde1" radius: 4 ListView { id: trajectoryList anchors.fill: parent anchors.margins: 5 clip: true model: ListModel { id: trajectoryModel ListElement { index: 1 x: 100; y: 50; z: 200 roll: 0; pitch: 0; yaw: 0 j1: 0; j2: 0; j3: 0; j4: 0; j5: 0; j6: 0 time: 500 selected: false } ListElement { index: 2 x: 150; y: 80; z: 180 roll: 10; pitch: 5; yaw: 0 j1: 15; j2: 30; j3: 0; j4: 10; j5: 5; j6: 0 time: 800 selected: false } ListElement { index: 3 x: 200; y: 120; z: 150 roll: -5; pitch: 15; yaw: 10 j1: 45; j2: 60; j3: 10; j4: -10; j5: 20; j6: 5 time: 1200 selected: false } } delegate: Rectangle { id: pointItem width: trajectoryList.width height: 50 color: model.selected ? "#d6e4ff" : (index % 2 === 0 ? "#f8f9fa" : "#ffffff") border.color: model.selected ? "#4d8df5" : "#e9ecef" border.width: model.selected ? 2 : 1 radius: 3 CheckBox { id: pointCheckbox anchors.left: parent.left anchors.leftMargin: 5 anchors.verticalCenter: parent.verticalCenter checked: model.selected onClicked: { trajectoryModel.setProperty(index, "selected", checked) } } RowLayout { anchors.fill: parent anchors.leftMargin: 35 anchors.margins: 5 spacing: 10 Label { text: model.index font.bold: true color: "#2c3e50" Layout.preferredWidth: 30 } ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true spacing: 2 Label { Layout.fillWidth: true text: addMethodGroup.checkedButton === coordMode ? `坐标: (${model.x}, ${model.y}, ${model.z})` : `关节: (${model.j1}°, ${model.j2}°, ${model.j3}°)` font.pixelSize: 12 color: "#34495e" } Label { Layout.fillWidth: true text: addMethodGroup.checkedButton === coordMode ? `RPY: (${model.roll}°, ${model.pitch}°, ${model.yaw}°)` : `关节: (${model.j4}°, ${model.j5}°, ${model.j6}°)` font.pixelSize: 12 color: "#7f8c8d" } } Label { text: model.time + "ms" font.pixelSize: 12 color: "#e74c3c" Layout.preferredWidth: 60 } // 快捷删除按钮 Button { icon.source: "qrc:/icons/close.png" icon.width: 12 icon.height: 12 width: 24 height: 24 flat: true ToolTip.text: "删除此点" ToolTip.visible: hovered onClicked: { deletePoint(index) } } } } } } // 操作按钮 RowLayout { Layout.alignment: Qt.AlignRight spacing: 10 Button { text: "添加" icon.source: "qrc:/icons/add.png" font.pixelSize: 14 onClicked: addDialog.open() } // 选择按钮组 RowLayout { spacing: 5 Button { text: "全选" font.pixelSize: 12 onClicked: { for (var i = 0; i < trajectoryModel.count; i++) { trajectoryModel.setProperty(i, "selected", true) } } } Button { text: "反选" font.pixelSize: 12 onClicked: { for (var i = 0; i < trajectoryModel.count; i++) { var current = trajectoryModel.get(i).selected trajectoryModel.setProperty(i, "selected", !current) } } } Button { text: "取消选择" font.pixelSize: 12 onClicked: { for (var i = 0; i < trajectoryModel.count; i++) { trajectoryModel.setProperty(i, "selected", false) } } } } Button { text: "删除选中" icon.source: "qrc:/icons/delete.png" font.pixelSize: 14 enabled: hasSelectedPoints() onClicked: deleteSelectedPoints() } Button { text: "清空" icon.source: "qrc:/icons/clear.png" font.pixelSize: 14 onClicked: { clearConfirmationDialog.open() } } } } } // 右侧 - 轨迹点编辑预览 ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true spacing: 20 // 轨迹点编辑区域 GroupBox { title: "轨迹点参数" Layout.fillWidth: true Layout.preferredHeight: 350 topPadding: 15 bottomPadding: 15 leftPadding: 15 rightPadding: 15 background: Rectangle { color: "#ffffff" border.color: "#dcdde1" radius: 5 } StackLayout { id: editStack anchors.fill: parent currentIndex: addMethodGroup.checkedButton === coordMode ? 0 : 1 // 按坐标编辑的页面 GridLayout { columns: 2 columnSpacing: 15 rowSpacing: 10 // 坐标编辑 Label { text: "X坐标 (mm):" font.pixelSize: 14 color: "#34495e" } SpinBox { id: xCoord from: -1000; to: 1000; stepSize: 1 value: 100 Layout.preferredWidth: 150 } Label { text: "Y坐标 (mm):" font.pixelSize: 14 color: "#34495e" } SpinBox { id: yCoord from: -1000; to: 1000; stepSize: 1 value: 50 Layout.preferredWidth: 150 } Label { text: "Z坐标 (mm):" font.pixelSize: 14 color: "#34495e" } SpinBox { id: zCoord from: -1000; to: 1000; stepSize: 1 value: 200 Layout.preferredWidth: 150 } // RPY位姿 Label { text: "Roll (度):" font.pixelSize: 14 color: "#34495e" } SpinBox { id: roll from: -180; to: 180; stepSize: 1 value: 0 Layout.preferredWidth: 150 } Label { text: "Pitch (度):" font.pixelSize: 14 color: "#34495e" } SpinBox { id: pitch from: -180; to: 180; stepSize: 1 value: 0 Layout.preferredWidth: 150 } Label { text: "Yaw (度):" font.pixelSize: 14 color: "#34495e" } SpinBox { id: yaw from: -180; to: 180; stepSize: 1 value: 0 Layout.preferredWidth: 150 } } // 按关节角度编辑的页面 ColumnLayout { spacing: 10 // 关节1 GridLayout { columns: 3 rowSpacing: 5 Label { text: "关节1 (度):" font.pixelSize: 14 color: "#34495e" Layout.columnSpan: 1 } Slider { id: joint1 from: -180; to: 180; stepSize: 0.1 value: 0 Layout.fillWidth: true Layout.columnSpan: 2 } Item { Layout.fillWidth: true } // 占位符 Label { text: joint1.value.toFixed(1) + "°" Layout.preferredWidth: 50 Layout.alignment: Qt.AlignRight } } // 关节2 GridLayout { columns: 3 rowSpacing: 5 Label { text: "关节2 (度):" font.pixelSize: 14 color: "#34495e" Layout.columnSpan: 1 } Slider { id: joint2 from: -180; to: 180; stepSize: 0.1 value: 0 Layout.fillWidth: true Layout.columnSpan: 2 } Item { Layout.fillWidth: true } // 占位符 Label { text: joint2.value.toFixed(1) + "°" Layout.preferredWidth: 50 Layout.alignment: Qt.AlignRight } } // 关节3 GridLayout { columns: 3 rowSpacing: 2 Label { text: "关节3 (度):" font.pixelSize: 14 color: "#34495e" Layout.columnSpan: 1 } Slider { id: joint3 from: -180; to: 180; stepSize: 0.1 value: 0 Layout.fillWidth: true Layout.columnSpan: 2 } Item { Layout.fillWidth: true } // 占位符 Label { text: joint3.value.toFixed(1) + "°" Layout.preferredWidth: 50 Layout.alignment: Qt.AlignRight } } // 关节4 GridLayout { columns: 3 rowSpacing: 5 Label { text: "关节4 (度):" font.pixelSize: 14 color: "#34495e" Layout.columnSpan: 1 } Slider { id: joint4 from: -180; to: 180; stepSize: 0.1 value: 0 Layout.fillWidth: true Layout.columnSpan: 2 } Item { Layout.fillWidth: true } // 占位符 Label { text: joint4.value.toFixed(1) + "°" Layout.preferredWidth: 50 Layout.alignment: Qt.AlignRight } } // 关节5 GridLayout { columns: 3 rowSpacing: 5 Label { text: "关节5 (度):" font.pixelSize: 14 color: "#34495e" Layout.columnSpan: 1 } Slider { id: joint5 from: -180; to: 180; stepSize: 0.1 value: 0 Layout.fillWidth: true Layout.columnSpan: 2 } Item { Layout.fillWidth: true } // 占位符 Label { text: joint5.value.toFixed(1) + "°" Layout.preferredWidth: 50 Layout.alignment: Qt.AlignRight } } // 关节6 GridLayout { columns: 3 rowSpacing: 5 Label { text: "关节6 (度):" font.pixelSize: 14 color: "#34495e" Layout.columnSpan: 1 } Slider { id: joint6 from: -180; to: 180; stepSize: 0.1 value: 0 Layout.fillWidth: true Layout.columnSpan: 2 } Item { Layout.fillWidth: true } // 占位符 Label { text: joint6.value.toFixed(1) + "°" Layout.preferredWidth: 50 Layout.alignment: Qt.AlignRight } } } } // 时间参数(两种模式共用) RowLayout { anchors.bottom: parent.bottom anchors.right: parent.right spacing: 10 Label { text: "时间(ms):" font.pixelSize: 14 color: "#34495e" } SpinBox { id: timeValue from: 0; to: 10000; stepSize: 10 value: 500 Layout.preferredWidth: 120 } } } // 3D预览区域 GroupBox { title: "轨迹预览" Layout.fillWidth: true Layout.fillHeight: true background: Rectangle { color: "#ffffff" border.color: "#dcdde1" radius: 5 } Rectangle { anchors.fill: parent anchors.margins: 5 color: "#2c3e50" radius: 4 // 3D预览占位符 Rectangle { anchors.centerIn: parent width: parent.width * 0.8 height: parent.height * 0.8 color: "#34495e" radius: 4 border.color: "#3498db" border.width: 2 Column { anchors.centerIn: parent spacing: 15 Image { source: "qrc:/icons/robot-arm.png" anchors.horizontalCenter: parent.horizontalCenter width: 80 height: 80 } Label { text: "3D轨迹预览区域" font.pixelSize: 18 color: "#ecf0f1" anchors.horizontalCenter: parent.horizontalCenter } Label { text: "实时显示机械手运动轨迹" font.pixelSize: 14 color: "#bdc3c7" anchors.horizontalCenter: parent.horizontalCenter } // 显示当前选中点 Rectangle { visible: hasSelectedPoints() width: parent.width * 0.8 height: 40 color: "#4d8df5" radius: 4 Label { anchors.centerIn: parent text: "已选择 " + getSelectedCount() + " 个轨迹点" color: "white" font.bold: true } } } } } } } } // 底部操作按钮 RowLayout { Layout.alignment: Qt.AlignRight spacing: 15 Button { text: "保存轨迹" icon.source: "qrc:/icons/save.png" font.pixelSize: 14 onClicked: saveDialog.open() } Button { text: "加载轨迹" icon.source: "qrc:/icons/load.png" font.pixelSize: 14 onClicked: loadDialog.open() } Button { text: "运行轨迹" icon.source: "qrc:/icons/play.png" font.pixelSize: 14 onClicked: { if (trajectoryModel.count > 0) { statusText.text = "轨迹运行中..." } else { statusText.text = "错误:没有轨迹点可运行" } } } Button { text: "停止" icon.source: "qrc:/icons/stop.png" font.pixelSize: 14 onClicked: statusText.text = "已停止" } } // 状态栏 Rectangle { Layout.fillWidth: true height: 30 color: "#f0f0f0" radius: 4 border.color: "#dcdde1" Label { id: statusText anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 10 text: "就绪" font.pixelSize: 12 color: "#7f8c8d" } // 显示选中点数量 Label { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: 10 text: "选中点: " + getSelectedCount() font.pixelSize: 12 color: "#e74c3c" visible: getSelectedCount() > 0 } } } // 添加轨迹点对话框 Dialog { id: addDialog title: "添加轨迹点" standardButtons: Dialog.Ok | Dialog.Cancel modal: true width: 400 GridLayout { columns: 2 anchors.fill: parent columnSpacing: 10 rowSpacing: 10 Label { text: "名称:" font.pixelSize: 14 } TextField { id: pointName placeholderText: "轨迹点名称" Layout.fillWidth: true } Label { text: "模式:" font.pixelSize: 14 } ComboBox { id: pointMode model: ["坐标模式", "关节模式"] currentIndex: 0 Layout.fillWidth: true } Label { text: "时间(ms):" font.pixelSize: 14 } SpinBox { id: pointTime from: 0; to: 10000; stepSize: 10 value: 500 Layout.fillWidth: true } } onAccepted: { const newIndex = trajectoryModel.count; trajectoryModel.append({ index: newIndex + 1, x: xCoord.value, y: yCoord.value, z: zCoord.value, roll: roll.value, pitch: pitch.value, yaw: yaw.value, j1: joint1.value, j2: joint2.value, j3: joint3.value, j4: joint4.value, j5: joint5.value, j6: joint6.value, time: pointTime.value, selected: false }); statusText.text = "已添加轨迹点 #" + (newIndex + 1); } } // 保存对话框 FileDialog { id: saveDialog title: "保存轨迹文件" nameFilters: ["轨迹文件 (*.path)"] onAccepted: statusText.text = "轨迹已保存到: " + fileUrl } // 加载对话框 FileDialog { id: loadDialog title: "加载轨迹文件" nameFilters: ["轨迹文件 (*.path)"] onAccepted: statusText.text = "轨迹已从: " + fileUrl + " 加载" } // 清空确认对话框 Dialog { id: clearConfirmationDialog title: "确认清空" standardButtons: Dialog.Yes | Dialogconsult - Kristin Hippe modal: true Label { text: "确定要清空所有轨迹点吗?此操作不可恢复!" font.pixelSize: 14 padding: 20 } onAccepted: { trajectoryModel.clear() statusText.text = "已清空所有轨迹点" } } // 获取选中点数量 function getSelectedCount() { var count = 0 for (var i = 0; i < trajectoryModel.count; i++) { if (trajectoryModel.get(i).selected) { count++ } } return count } // 检查是否有选中的点 function hasSelectedPoints() { return getSelectedCount() > 0 } // 删除单个点 function deletePoint(index) { trajectoryModel.remove(index) statusText.text = "已删除轨迹点" // 更新剩余点的索引 for (var i = 0; i < trajectoryModel.count; i++) { trajectoryModel.setProperty(i, "index", i + 1) } } // 删除选中点 function deleteSelectedPoints() { var deletedCount = 0 for (var i = trajectoryModel.count - 1; i >= 0; i--) { if (trajectoryModel.get(i).selected) { trajectoryModel.remove(i) deletedCount++ } } // 更新剩余点的索引 for (var j = 0; j < trajectoryModel.count; j++) { trajectoryModel.setProperty(j, "index", j + 1) } if (deletedCount > 0) { statusText.text = "已删除 " + deletedCount + " 个轨迹点" } } }选中某个复选框下面一个复选框也会被选中,这个bug修改一下
最新发布
08-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值