【QML 多级菜单设计】利用ListView设计多级菜单, 支持动态

 在本文中,我们将探讨如何使用Qt Quick和QML来实现一个多级折叠下拉导航菜单,该菜单支持动态添加和卸载子菜单项

复制代码
  1 import QtQuick 2.15
  2 import QtQuick.Controls 2.15
  3 import QtQuick.Layouts 1.15
  4 
  5 Item {
  6     id: root
  7     width: 200
  8     height: 40
  9 
 10     // 示例数据:支持多层级结构
 11     property var subModel: {
 12         "水果": {
 13                 "国产": {
 14                     "苹果": ["红富士", "蛇果"],
 15                     "香蕉": ["小米蕉", "皇帝蕉"],
 16                     "橙子": ["脐橙", "血橙"]
 17                 },
 18                 "进口": {
 19                     "苹果": ["富士", "嘎啦"],
 20                     "香蕉": ["小香蕉", "大蕉"],
 21                     "橙子": ["血橙", "普通橙"]
 22                 }
 23             },
 24             "蔬菜": {
 25                 "国产": {
 26                     "土豆": ["黄心土豆", "紫土豆"],
 27                     "胡萝卜": ["红胡萝卜", "黄胡萝卜"],
 28                     "黄瓜": ["旱黄瓜", "水黄瓜"]
 29                 },
 30                 "进口": {
 31                     "土豆": ["外国土豆1", "外国土豆2"],
 32                     "胡萝卜": ["外国胡萝卜1", "外国胡萝卜2"],
 33                     "黄瓜": ["外国黄瓜1", "外国黄瓜2"]
 34                 }
 35             },
 36             "1": {
 37                 "A组": {
 38                     "11": ["111", "112"],
 39                     "12": ["121", "122"],
 40                     "13": ["131", "132"]
 41                 },
 42                 "B组": {
 43                     "11": ["211", "212"],
 44                     "12": ["221", "222"],
 45                     "13": ["231", "232"]
 46                 }
 47             },
 48             "2": {
 49                 "C组": {
 50                     "21": ["311", "312"],
 51                     "22": ["321", "322"],
 52                     "23": ["331", "332"]
 53                 },
 54                 "D组": {
 55                     "21": ["411", "412"],
 56                     "22": ["421", "422"],
 57                     "23": ["431", "432"]
 58                 }
 59             }
 60     }
 61 
 62     // 当前选中的菜单项
 63     property string selectedSubItem: "设备责任人"
 64 
 65     // 用于存储所有已打开的动态菜单 Popup(索引即层级)
 66     property var openMenus: []
 67 
 68     // 关闭从指定层级开始的所有已打开菜单
 69     function closeDynamicMenus(level) {
 70         for (var i = openMenus.length - 1; i >= level; i--) {
 71             if (openMenus[i]) {
 72                 openMenus[i].close();
 73                 openMenus[i].destroy();
 74             }
 75             openMenus.splice(i, 1);
 76         }
 77     }
 78 
 79     // 在指定层级打开菜单
 80     // level:菜单层级(主菜单点击后,子菜单为 1 级,依此类推)
 81     // data:当前层级菜单数据
 82     // anchor:用于定位 Popup 的参照项
 83     function openDynamicMenu(level, data, anchor) {
 84         closeDynamicMenus(level);
 85         var menu = dynamicMenuComponent.createObject(root, { "menuData": data, "level": level });
 86         var pos = anchor.mapToItem(root, anchor.width, 0);
 87         menu.x = pos.x;
 88         menu.y = pos.y;
 89         menu.open();
 90         openMenus[level] = menu;
 91     }
 92 
 93     // 主按钮:显示当前选中项
 94     Rectangle {
 95         id: mainButton
 96         width: root.width
 97         height: root.height
 98         color: "lightgray"
 99         border.color: "gray"
100         radius: 5
101 
102         Text {
103             id: mainText
104             anchors.centerIn: parent
105             text: root.selectedSubItem || "请选择"
106         }
107 
108         MouseArea {
109             anchors.fill: parent
110             onClicked: {
111                 // 显示主菜单时,关闭所有子菜单
112                 mainListView.visible = true;
113                 closeDynamicMenus(0);
114             }
115         }
116     }
117 
118     // 主菜单 ListView,显示最外层(第一层)的菜单项
119     ListView {
120         id: mainListView
121         visible: false
122         width: root.width
123         x: mainButton.x
124         y: mainButton.y + mainButton.height
125         // 根据内容高度动态调整,但最多200像素
126         height: Math.min(contentHeight, 200)
127         model: Object.keys(root.subModel)
128         delegate: Item {
129             width: mainListView.width
130             height: root.height
131             RowLayout {
132                 anchors.fill: parent
133                 spacing: 0
134                 // 左侧显示菜单文本
135                 Rectangle {
136                     id: mainTextRect
137                     Layout.preferredWidth: parent.width * 0.75
138                     height: parent.height
139                     color: "lightgray"
140                     Text {
141                         anchors.centerIn: parent
142                         text: modelData
143                         verticalAlignment: Text.AlignVCenter
144                         padding: 10
145                     }
146                     MouseArea {
147                         anchors.fill: parent
148                         onClicked: {
149                             var childData = root.subModel[modelData];
150                             if (childData !== undefined &&
151                                ((Array.isArray(childData) && childData.length > 0) ||
152                                 (typeof childData === "object" && Object.keys(childData).length > 0))) {
153                                 // 存在子菜单,打开下一级菜单(level 1)
154                                 // openDynamicMenu(1, childData, mainTextRect);
155                                 root.selectedSubItem = modelData;
156                                 mainText.text = modelData;
157                                 mainListView.visible = false;
158                                 closeDynamicMenus(0);
159 
160                             } else {
161                                 // 叶子节点:更新选中并关闭所有菜单
162                                 root.selectedSubItem = modelData;
163                                 mainText.text = modelData;
164                                 mainListView.visible = false;
165                                 closeDynamicMenus(0);
166                             }
167                         }
168                     }
169                 }
170                 // 右侧显示箭头:如果存在子菜单则显示 "▶",否则为空
171                 Rectangle {
172                     id: mainArrowRect
173                     Layout.preferredWidth: parent.width * 0.25
174                     height: parent.height
175                     color: "lightgray"
176                     Text {
177                         anchors.centerIn: parent
178                         color: "green"
179                         text: {
180                             var childData = root.subModel[modelData];
181                             if (childData !== undefined &&
182                                ((Array.isArray(childData) && childData.length > 0) ||
183                                 (typeof childData === "object" && Object.keys(childData).length > 0))) {
184                                 return "";
185                             }
186                             return "";
187                         }
188                     }
189                     MouseArea {
190                         anchors.fill: parent
191                         onClicked: {
192                             var childData = root.subModel[modelData];
193                             if (childData !== undefined &&
194                                ((Array.isArray(childData) && childData.length > 0) ||
195                                 (typeof childData === "object" && Object.keys(childData).length > 0))) {
196                                 openDynamicMenu(1, childData, mainArrowRect);
197                             }
198                         }
199                     }
200                 }
201             }
202         }
203     }
204 
205     // 通用动态菜单 Popup 组件,用于构造任意层级的子菜单
206     Component {
207         id: dynamicMenuComponent
208         Popup {
209             id: dynPopup
210             // 当前层级菜单数据(可以是数组或对象)
211             property var menuData
212             // 当前菜单层级,主菜单点击后生成的菜单 level 为 1,依次递增
213             property int level: 0
214             width: root.width*0.75
215             padding: 1
216             height: Math.min(listView.contentHeight, 200)
217             background: Rectangle {
218                 color: "#ffffff"
219                 border.color: "#cccccc"
220                 radius: 5
221             }
222             ListView {
223                 id: listView
224                 anchors.fill: parent
225                 model: (typeof dynPopup.menuData === "object" && !Array.isArray(dynPopup.menuData))
226                        ? Object.keys(dynPopup.menuData)
227                        : dynPopup.menuData
228                 delegate: Item {
229                     width: listView.width
230                     height: root.height
231                     RowLayout {
232                         anchors.fill: parent
233                         spacing: 0
234                         Layout.margins: 0
235                         // 左侧显示菜单项文本
236                         Rectangle {
237                             id: dynTextRect
238                             Layout.fillWidth: true
239                             Layout.preferredWidth: parent.width * 0.75
240                             height: parent.height
241                             color: "lightgray"
242                             Text {
243                                 anchors.centerIn: parent
244                                 text: modelData
245                                 verticalAlignment: Text.AlignVCenter
246                                 padding: 10
247                             }
248                             MouseArea {
249                                 anchors.fill: parent
250                                 onClicked: {
251                                     var childData;
252                                     if (typeof dynPopup.menuData === "object" && !Array.isArray(dynPopup.menuData)) {
253                                         childData = dynPopup.menuData[modelData];
254                                     }
255                                     if (childData !== undefined &&
256                                        ((Array.isArray(childData) && childData.length > 0) ||
257                                         (typeof childData === "object" && Object.keys(childData).length > 0))) {
258                                         // 存在下一级子菜单,打开下一层
259                                         // root.openDynamicMenu(dynPopup.level + 1, childData, dynTextRect);
260                                         root.selectedSubItem = modelData;
261                                         mainText.text = modelData;
262                                         mainListView.visible = false;
263                                         root.closeDynamicMenus(0);
264                                     } else {
265                                         // 叶子节点:更新选中并关闭所有菜单
266                                         root.selectedSubItem = modelData;
267                                         mainText.text = modelData;
268                                         mainListView.visible = false;
269                                         root.closeDynamicMenus(0);
270                                     }
271                                 }
272                             }
273                         }
274                         // 右侧箭头,若存在子菜单则显示,否则为空
275                         Rectangle {
276                             id: dynArrowRect
277                              Layout.fillWidth: true
278                             Layout.preferredWidth: parent.width * 0.25
279                             height: parent.height
280                             color: "lightgray"
281                             Text {
282                                 anchors.centerIn: parent
283                                 color: "green"
284                                 text: {
285                                     var childData;
286                                     if (typeof dynPopup.menuData === "object" && !Array.isArray(dynPopup.menuData)) {
287                                         childData = dynPopup.menuData[modelData];
288                                     }
289                                     if (childData !== undefined &&
290                                        ((Array.isArray(childData) && childData.length > 0) ||
291                                         (typeof childData === "object" && Object.keys(childData).length > 0))) {
292                                         return "";
293                                     }
294                                     return "";
295                                 }
296                             }
297                             MouseArea {
298                                 anchors.fill: parent
299                                 onClicked: {
300                                     var childData;
301                                     if (typeof dynPopup.menuData === "object" && !Array.isArray(dynPopup.menuData)) {
302                                         childData = dynPopup.menuData[modelData];
303                                     }
304                                     if (childData !== undefined &&
305                                        ((Array.isArray(childData) && childData.length > 0) ||
306                                         (typeof childData === "object" && Object.keys(childData).length > 0))) {
307                                         root.openDynamicMenu(dynPopup.level + 1, childData, dynArrowRect);
308                                     }
309                                 }
310                             }
311                         }
312                     }
313                 }
314             }
315         }
316     }
317 }
复制代码

 

 

posted @   taohuaxiaochunfeng  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示