← 返回索引

范围:源码基线 26.7.0 下布局尺寸、交互响应、覆盖层选型、自定义与混用四类常见问题的结论与最小示例。


布局尺寸与排列

布局问题主要有三类:容器分配的空间未被利用、容器选型不明确、修改数值后界面不更新。

Q: 为什么剩余空间没被利用?

StackPanel 不会自动拉伸子元素填满剩余空间,此时需为容器或子元素开启自动尺寸:

StackPanel panel = new StackPanel(Orientation.HORIZONTAL)
    .autoWidth(true)    // 水平容器自动扩展到父级宽度
    .autoHeight(true);  // 垂直容器按内容计算高度

Q: 什么时候该用 StackPanel,什么时候该用 Canvas?

StackPanel 按方向线性排列子元素并自动计算尺寸;Canvas 提供绝对定位或锚点摆放子元素。选型规则:能用一行或一列排下的界面使用 StackPanel,因为其对动态内容与滚动的适应性最好;只有仪表盘、自由拖拽面板这类没有线性关系的布局才使用 Canvas。完整的锚点与定位 API 见布局容器。

Q: 修改值后界面未更新,原因是什么?

布局是惰性的:只有 dirty 标志置位后,容器才会在下一次绘制前重跑测量与排列。链式 setter(.width(...)、.text(...)、list.items(...))都会自动调用 markDirty();这是一条不变量:组件不得直接给字段赋值绕过标脏。违反时整条父链都停留在旧几何上(自身、父容器、祖先全部不重排)。需要手动标脏的时机与 dirty 沿父链的传播方式,见布局系统。运行时增删子元素的完整写法见常见使用模式的「动态列表」。

Q: 如何调试布局?

先打印每个元素的绝对包围盒与命中结果,确认几何是否符合预期:

System.out.println(el.getClass().getSimpleName()
    + " @ " + el.getAbsoluteX() + "," + el.getAbsoluteY()
    + " size=" + el.getWidth() + "x" + el.getHeight()
    + " hit=" + Element.isHit(el, mouseX, mouseY));

若几何正确却仍不响应,问题位于事件层。


交互响应

Q: 为什么组件没有响应点击?

按以下四条依次排查,覆盖绝大多数情况:

  1. 几何:点击是否落在组件的绝对包围盒内(用调试打印核对)。
  2. 可见性:visible 为 false 的组件既不绘制也不参与命中,绘制侧与命中侧同时生效,InteractionManager.hovering 对不可见元素直接返回 false。
  3. 父级消费:父容器可能先行消费该事件。事件遵循「命中且消费才排他」,在父级 mousePressed 中加打印即可确认返回 true 的一方。
  4. 覆盖层拦截:浮层优先于根树接收事件。覆盖层行为对照表的权威在覆盖层。

自定义形状的组件还需多查一项:命中覆写的不变量是只覆盖 shapeHit、绝不覆盖 isHit,因为覆写 isHit 会丢失裁剪边界检查,波及分发、外部关闭与块查询。命中分层(H0/H1/L1/H3/L3)的定义与分发优先级的权威在交互引擎;按下期间的捕获与拦截标志(pressFlags)见元素系统。

Q: 如何显示/隐藏一个组件?

调用 element.visible(false) 即可,该调用同时隐藏元素与整棵子树:不参与绘制,也不参与命中。恢复用 visible(true)。可见性的语义见核心概念;各组件守卫与命中检查的具体位置见UI 组件的「特殊行为」。

Q: 如何处理键盘输入?

让组件获得焦点即可。继承 FocusableElement 的组件(TextBox、AutoCompleteBox、Stepper)内置焦点与键盘处理;通过 TextBox 的 .focus() 或 FocusableElement 的 claimFocus() 主动聚焦,或由用户点击进入。焦点家族(focus family)决定外部点击是否算“点在自己人身上“(不失焦),配对规则见交互与事件。FluentScreen 必须把键盘事件转发给 interactionManager,共 7 个方法:mousePressed、mouseReleased、mouseDragged、mouseScrolled、keyPressed、charTyped、preeditUpdated;可直接复制的完整转发代码在快速开始。


覆盖层选型

Q: 模态对话框和普通覆盖层怎么选?

按 dismissOnOutsideClick 决策,即外部点击是否自动关闭。推入使用 InteractionManager.active.overlays().push(...):单参 push(el) 将 dismissOnOutsideClick 定为 true;显式指定时使用双参 push(el, dismissOnOutsideClick)。独占输入、只随按钮关闭的确认框推 push(el, false);仅作旁浮、点击后关闭的通知使用单参即可(外部点击自动关闭)。完整对照表(含 Tooltip、MessageFlyout、ContextMenu、自定义 Dialog 各自的取值)见覆盖层。

对话框与 Toast 的示例见常见使用模式的「模态对话框」;FluentScreen 本身不持有根树与覆盖层字段,字段的创建方与销毁方属于所有权模型,见根树与层级系统。


自定义与混用的取舍

Q: 如何自定义组件的外观?

按改动深度有两条路径:

  • 换颜色 record:Button、Toggle、Slider 等都接受构造参数中的颜色 record(并带 blue() 默认值),整体替换 record 即可换肤,无需继承。
  • 继承 Element 编写新组件:覆写 extractRenderState 自行绘制,覆写 mousePressed 自行响应。一个悬停高亮的色块组件完整示例见常见使用模式的「自定义组件」;实现新的排列算法见同页的「自定义布局容器」。

Q: 可以把 FluentUI 组件和原版/其他模组的 widget 混用吗?

不建议。分层模型、事件坐标系、生命周期都不同:原版 widget 不支持 markDirty 惰性布局,点击也可能被对方的事件循环抢先消费,两套坐标系叠放还会出现难以复现的偏移。确需桥接时,将原版内容渲染进自定义组件的 extractRenderState,而非让两棵树平铺互操作。


相关文档