Skip to content

fix(ComboBox): size popup before it is mapped - #712

Merged
mhduiy merged 1 commit into
linuxdeepin:masterfrom
mhduiy:bug-370839-combobox-popup-size
Sep 30, 2026
Merged

mhduiy merged 1 commit into
linuxdeepin:masterfrom
mhduiy:bug-370839-combobox-popup-size

Conversation

@mhduiy

@mhduiy mhduiy commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

问题

Popup.Window 模式下,弹窗窗口是在内容布局之前创建并映射的:QQuickPopupPrivate::adjustPopupItemParentAndWindow() 先按 popupItem 当时的尺寸建窗并 setVisible(true),之后才定位、才布局内容。打开前列表一个 delegate 都没有(contentHeight == 0),隐式高只剩 padding = 20px,窗口就先以 20px 显示,等内容量出来再 resize 到真实高度,并且位置也随之被重算 —— 表现为"先很扁、突然变高、y 跳"。

改动

  • ArrowListView 暴露 sizedHeight:整列高度 = 实测行高 × min(行数, maxVisibleItems) + 滚动时的上下箭头条。行高取自已实例化的第一个 delegate;在还没有 delegate 时退回 itemHeight(size hint),并用 contentHeight > 0 守卫保证只在布局完成后取值。
  • 弹窗关闭期间提前实例化一项(onHeightChanged / onCountChanged → forceLayout()):ListView 在弹窗窗口显示前不会自己创建 delegate,这一步让真实行高在窗口映射之前就可得到。
  • 箭头 Loader 的 Layout.preferredHeight 固定为 stepButtonIconSize.height(即它实际布局到的高度):箭头是懒加载的,原来会让布局多走一遍、尺寸变两次。
  • ComboBox 的 popup 用 sizedHeight 设置 implicitHeight;测量完成前用一个临时视口(2 × itemHeight)让列表有高度、能把第一项造出来。

验证

本地复现(offscreen,窗口首个事件):

场景 窗口映射时 映射后
3 项 / 行高 30 200x110 不变
3 项 / 行高 44 200x152 不变
30 项 / 行高 30 200x534 不变
30 项 / 行高 44 200x758 不变
30 项 / 行高 50 200x854 不变
30 项 / 非均匀行高 200x534 靠内容兜底长到 694

改动规模:2 文件 +17 −2。

已知边界

  • 行高以第一个 delegate 代表整列(均匀行高假设);非均匀行高时窗口会再长一次,但不会裁切;
  • 与本次改动无关的遗留问题:打开过程中窗口位置会在两个值间来回(DPopupWindowHandle::adjustPopupPosition() 的夹取与 Qt positioner 互相回写),属于另一个问题。

PMS: BUG-370839

@sourcery-ai

sourcery-ai Bot commented Sep 28, 2026

Copy link
Copy Markdown
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

The PR prevents Popup.Window ComboBox popups from being mapped at a collapsed height by realizing and measuring a delegate before opening, deriving the list height from that measurement, and using stable arrow-control dimensions.

Sequence diagram for sizing the ComboBox popup before mapping

sequenceDiagram
    participant ComboBox
    participant Popup
    participant ArrowListView
    participant ItemsView
    participant Delegate
    participant PopupWindow

    ComboBox->>Popup: implicitHeight
    Popup->>ArrowListView: set temporary viewport
    ArrowListView->>ItemsView: forceLayout()
    ItemsView->>Delegate: instantiate first item
    ArrowListView->>ArrowListView: itemDelegateHeight
    ArrowListView->>ArrowListView: sizedHeight
    ArrowListView-->>Popup: calculated list height
    Popup-->>PopupWindow: set final height
    PopupWindow->>PopupWindow: setVisible(true)
Loading

Flow diagram for calculating the ComboBox popup height

flowchart TD
    A[ComboBox popup opens] --> B{First delegate measured?}
    B -- No --> C[Use temporary viewport: 2 × itemHeight]
    C --> D["ItemsView.forceLayout()"]
    D --> E[Read itemDelegateHeight]
    B -- Yes --> E
    E --> F[Calculate sizedHeight]
    F --> G[Add top and bottom padding]
    G --> H[Map Popup.Window at final height]
Loading

File-Level Changes

Change Details Files
Measure delegate-based list height before the popup window is mapped.
  • Expose the realized delegate row height with a style fallback.
  • Force list layout when the viewport becomes available so an item can be instantiated and measured.
  • Compute the required list height from visible item count, measured row height, and arrow controls instead of relying on contentHeight.
qt6/src/qml/ArrowListView.qml
Make ComboBox popup sizing resolve before opening.
  • Use the list's measured sizedHeight for popup implicitHeight.
  • Provide a temporary two-row viewport until delegate measurement completes.
  • Include popup padding in the precomputed height.
qt6/src/qml/ComboBox.qml
Stabilize arrow-control contributions to list layout height.
  • Set both arrow loaders' preferred layout height to the actual configured icon height, avoiding delayed implicit-size changes.
qt6/src/qml/ArrowListView.qml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="qt6/src/qml/ArrowListView.qml" line_range="20" />
<code_context>
     property int itemHeight:  DS.Style.arrowListView.itemHeight
     property alias view: itemsView

+    readonly property real itemDelegateHeight: {
+        const item = itemsView.itemAtIndex(0)
+        return itemsView.contentHeight > 0 && item && item.implicitHeight > 0 ? item.implicitHeight : itemHeight
+    }
+    readonly property real sizedHeight: itemsView.count > 0
</code_context>
<issue_to_address>
**issue (bug_risk):** `itemDelegateHeight` measures `item.implicitHeight` instead of the delegate's actual `height`. A delegate that assigns an explicit height (for example, `height: 44`) while leaving its implicit height at zero falls back to the style `itemHeight`, so `sizedHeight` computes the popup using the wrong row size.

**Triggers:** When a ComboBox uses a custom delegate with an explicit `height` that differs from the style row height.

**Suggested fix:** Use the realized delegate's actual `height` when it is valid, falling back to `implicitHeight` or `itemHeight` only when necessary.

```suggestion
        return item && item.height > 0 ? item.height : item && item.implicitHeight > 0 ? item.implicitHeight : itemHeight
```
</issue_to_address>

### Comment 2
<location path="qt6/src/qml/ArrowListView.qml" line_range="28-31" />
<code_context>
             visible: itemsView.interactive
             Layout.alignment: Qt.AlignHCenter
             Layout.fillWidth: true
-            Layout.preferredHeight: implicitHeight
+            Layout.preferredHeight: DS.Style.arrowListView.stepButtonIconSize.height
             view: itemsView
             stepSize: control.itemHeight
</code_context>
<issue_to_address>
**issue (bug_risk):** The arrow Loader rows are allocated `stepButtonIconSize.height` (12px in the checked style), while the loaded `ActionButton` is explicitly sized to `stepButtonSize.height` (16px). The ColumnLayout therefore gives each arrow only the icon height, shrinking or clipping the button and making the popup's arrow-space calculation too small.

**Triggers:** When the style has different `stepButtonSize.height` and `stepButtonIconSize.height`, as in the checked FlowStyle configuration.

**Suggested fix:** Use the actual step-button layout height, such as `DS.Style.arrowListView.stepButtonSize.height`, consistently in both the Loader layout and `sizedHeight` calculation.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread qt6/src/qml/ArrowListView.qml Outdated
Comment thread qt6/src/qml/ArrowListView.qml
@mhduiy
mhduiy force-pushed the bug-370839-combobox-popup-size branch from e10a8a5 to 7725739 Compare September 29, 2026 02:33
Comment thread qt6/src/qml/ArrowListView.qml Outdated
Comment thread qt6/src/qml/ArrowListView.qml Outdated
@mhduiy
mhduiy force-pushed the bug-370839-combobox-popup-size branch 2 times, most recently from 41ee9e1 to fb47527 Compare September 29, 2026 03:38
1. Size the window popup from the model and the measured delegate height, instead of
   the content item, which has not been laid out yet when the window is created.
2. Measure the delegate height in ArrowListView by realizing one item while the popup
   is still closed, and pin the scroll-arrow height so the layout settles in one pass.
3. Keep the style item height only as the viewport that makes that first measurement
   possible.

Log: ComboBox popups appear at their final size instead of being resized after being mapped.
Influence: Removes the popup size jump with Window popup type.

fix(ComboBox): 弹窗按最终尺寸显示

1. 窗口弹窗改用模型与实测行高计算尺寸,不再使用尚未布局的内容项。
2. ArrowListView 在弹窗关闭时提前实例化一项以量出行高,并固定箭头条高度,使布局一次到位。
3. 样式行高仅作为首次测量所需的引导视口。

Log: 下拉弹窗显示时即为其最终尺寸,不再在显示后改变尺寸。
PMS: BUG-370839
Influence: 修复 Window 类型弹窗的尺寸跳动。
@mhduiy
mhduiy force-pushed the bug-370839-combobox-popup-size branch 3 times, most recently from cf0e406 to 5214691 Compare September 29, 2026 07:20
@deepin-ci-robot

Copy link
Copy Markdown
Contributor

deepin pr auto review

🤖 AI 代码审查报告

总体评分: 98 分 (通过阈值: 70分)

Pass


📊 总体评价

项目 结果
审查结论 代码审查通过
评分详情 总体评分 98 分,大于 70 分通过阈值,代码质量符合要求。本次修改修复了 ComboBox 弹窗在 Popup.Window 模式下先以极小尺寸显示再突然变高的视觉问题,实现方案合理,边界条件处理完善。

🔍 详细分析

1. 语法逻辑 ✅

评价: 优秀 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: 语法和逻辑均无问题,边界条件处理完善


2. 代码质量 ✅

评价: 优秀 ✅ 通过

潜在问题:

  1. qt6/src/qml/ComboBox.qml:176 - 注释"60 is a temporary viewport"描述的是默认值而非实际表达式,当 itemHeight 变化时会产生误导

建议: 建议将注释修改为更准确的表达,如"2 * itemHeight is a temporary viewport so the list can realize one row and be measured."


3. 代码性能 ✅

评价: 优秀 ✅ 通过

潜在问题:

  1. qt6/src/qml/ArrowListView.qml:50 - forceLayout() 同步布局操作在极端情况下可能影响性能,但由条件守卫限制为单次调用

建议: 对于 ComboBox 场景,条目数量通常有限,forceLayout() 的性能影响可接受。如需进一步优化,可考虑异步延迟布局方案


4. 代码安全 🔒

评价: 优秀 ✅ 通过

🔐 发现 0 个安全漏洞

安全漏洞详情:
✅ 未发现安全漏洞

建议: 本次修改为纯 UI 布局计算代码,不涉及用户输入处理、网络操作、文件系统操作、命令执行等安全敏感操作,无需安全加固


💡 改进建议代码示例

// ComboBox.qml - 建议修改注释为更准确的表达
// 2 * itemHeight is a temporary viewport so the list can realize one row and be measured.
implicitHeight: (contentItem.sizedHeight > 0 ? contentItem.sizedHeight : 2 * DS.Style.arrowListView.itemHeight)
                + topPadding + bottomPadding

本报告由 AI 代码审查工具自动生成

@deepin-ci-robot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 18202781743, mhduiy

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@mhduiy
mhduiy merged commit 4769ee4 into linuxdeepin:master Sep 30, 2026
36 of 37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants