You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

QML可滚动日期选择器构建遇应用冻结问题求助

QML可滚动日期选择器导致应用冻结无响应

我之前实现的时间选择器运行正常,但参考网上代码制作的可滚动日期选择器,在打开包含该组件的页面时会直接导致应用冻结无响应。以下是DatePicker.qml的代码:

import QtQuick 2.0

ListView {
    id: root

    function set(date) { // new Date(2019, 10 - 1, 4)
        selectedDate = new Date(date)
        positionViewAtIndex((selectedDate.getFullYear()) * 12 + selectedDate.getMonth(), ListView.Center) // index from month year
    }

    signal clicked(date date);  // onClicked: print('onClicked', date.toDateString())

    property date selectedDate: new Date()

    width: 500;  height: 500 // default size
    snapMode:    ListView.SnapOneItem
    orientation: Qt.Horizontal
    clip:        true

    model: 3000 * 12 // index == months since January of the year 0

    delegate: Item {
        property int year:      Math.floor(index / 12)
        property int month:     index % 12 // 0 January
        property int firstDay:  new Date(year, month, 1).getDay() // 0 Sunday to 6 Saturday

        width: root.width;  height: root.height

        Column {
            Item { // month year header
                width: root.width;  height: root.height - grid.height

                Text { // month year
                    anchors.centerIn: parent
                    text: ['January', 'February', 'March', 'April', 'May', 'June',
                           'July', 'August', 'September', 'October', 'November', 'December'][month] + ' ' + year
                    font {pixelSize: 0.5 * grid.cellHeight}
                }
            }

            Grid { // 1 month calendar
                id: grid

                width: root.width;  height: 0.875 * root.height
                property real cellWidth:  width  / columns;
                property real cellHeight: height / rows // width and height of each cell in the grid.

                columns: 7 // days
                rows:    7

                Repeater {
                    model: grid.columns * grid.rows // 49 cells per month

                    delegate: Rectangle { // index is 0 to 48
                        property int day:  index - 7 // 0 = top left below Sunday (-7 to 41)
                        property int date: day - firstDay + 1 // 1-31

                        width: grid.cellWidth;  height: grid.cellHeight
                        border.width: 0.3 * radius
                        border.color: new Date(year, month, date).toDateString() == selectedDate.toDateString()  &&  text.text  &&  day >= 0?
                                      'black': 'transparent' // selected
                        radius: 0.02 * root.height
                        opacity: !mouseArea.pressed? 1: 0.3  //  pressed state

                        Text {
                            id: text

                            anchors.centerIn: parent
                            font.pixelSize: 0.5 * parent.height
                            font.bold:      new Date(year, month, date).toDateString() == new Date().toDateString() // today
                            text: {
                                if(day < 0)                                               ['S', 'M', 'T', 'W', 'T', 'F', 'S'][index] // Su-Sa
                                else if(new Date(year, month, date).getMonth() == month)  date // 1-31
                                else                                                      ''
                            }
                        }

                        MouseArea {
                            id: mouseArea

                            anchors.fill: parent
                            enabled:    text.text  &&  day >= 0

                            onClicked: {
                                selectedDate = new Date(year, month, date)
                                root.clicked(selectedDate)
                            }
                        }
                    }
                }
            }
        }
    }

    // Component.onCompleted: set(new Date()) // today (otherwise Jan 0000)
}

在其他页面中的调用代码:

DatePicker {
    width: 0.98 * root.width;  height: 0.8 * root.width // resize

    Component.onCompleted: set(new Date()) // today
    onClicked:             print('onClicked', Qt.formatDate(date, 'M/d/yyyy'))
}

问题原因与修复方案

核心问题:超大量的Model导致组件爆炸

原代码中model: 3000 * 12意味着ListView会尝试创建36000个月份委托,每个委托又包含49个单元格组件,总计超过170万个组件——这直接耗尽了UI线程资源,导致应用冻结。

具体修复步骤:

  1. 缩小Model范围
    不需要从公元0年开始展示,只保留日常使用的年份区间(比如前后50年),同时调整年份计算逻辑:
model: 100 * 12 // 前后50年,共100年

function set(date) {
    const startYear = 1974; // 起始年份
    selectedDate = new Date(date);
    // 计算当前日期对应的ListView索引
    const index = (selectedDate.getFullYear() - startYear) * 12 + selectedDate.getMonth();
    positionViewAtIndex(index, ListView.Center);
}

delegate: Item {
    // 从起始年份开始计算当前委托的年份
    property int year: 1974 + Math.floor(index / 12)
    property int month: index % 12
    // ... 其他属性
}
  1. 缓存重复计算的日期值
    原代码中多次创建new Date(year, month, date),频繁触发JS垃圾回收,进一步拖慢性能。可以将常用判断缓存为属性:
delegate: Item {
    // ... 其他属性
    property int daysInMonth: new Date(year, month + 1, 0).getDate() // 当月总天数
    
    Repeater {
        delegate: Rectangle {
            // ... 其他属性
            property bool isCurrentMonth: date > 0 && date <= daysInMonth
            property bool isSelected: isCurrentMonth && new Date(year, month, date).toDateString() === selectedDate.toDateString()
            property bool isToday: isCurrentMonth && new Date(year, month, date).toDateString() === new Date().toDateString()
            
            // 用缓存属性替代重复计算
            border.color: isSelected && day >= 0 ? 'black' : 'transparent'
            
            Text {
                font.bold: isToday
                text: {
                    if (day < 0) {
                        return ['S', 'M', 'T', 'W', 'T', 'F', 'S'][index]
                    } else if (isCurrentMonth) {
                        return date
                    } else {
                        return ''
                    }
                }
            }
        }
    }
}
  1. 开启ListView缓存优化
    添加cacheBuffer属性,让ListView预加载可见区域外的少量委托,提升滚动流畅度:
ListView {
    // ... 其他属性
    cacheBuffer: 200 // 预加载上下200px范围内的委托
}

修改后,应用不会再因组件过载冻结,同时滚动和交互性能也会大幅提升。

内容的提问来源于stack exchange,提问作者utkuyceng

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 15:15:57