如何避免Eclipse/JFace/SWT应用中CTabFolder内控件闪烁问题
SWT中CTabFolder内控件调用requestLayout时闪烁问题
我在大型应用中使用SWT、JFace和Eclipse控件时遇到问题:打开关闭新Shell(部分为弹出菜单)的同时,主应用调用requestLayout()方法,会导致部分控件出现闪烁现象。
经过大量调试,已得出以下结论:
- 问题仅出现在
org.eclipse.swt.custom.CTabFolder/org.eclipse.swt.custom.CTabItem内部使用特定控件的场景下 - 默认SWT控件(例如
Combo/Text)不会出现闪烁 - 在标签页内使用
org.eclipse.swt.widgets.Link组件(org.eclipse.riena.ui.swt.CompletionCombo也存在相同问题)会触发闪烁 - 简单复现步骤:打开并关闭第二个Shell(与应用中弹出弹窗的逻辑一致),同时对CTabFolder内的元素调用
requestLayout()方法 - CTabFolder外部的控件不会出现该问题
闪烁效果可通过运行复现代码查看,CompletionCombo部分属于Riena实现,但Link是Eclipse原生控件,以下是仅使用Link的复现代码:
import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Shell; public class SWTTestCaseFlickering { public static void main(String[] args) { SWTTestCaseFlickering window = new SWTTestCaseFlickering(); window.open(); } public void open() { final Display display = new Display(); final Shell shell = new Shell(); shell.setLayout(new GridLayout()); shell.setText("SWT Application"); org.eclipse.jface.layout.GridLayoutFactory.fillDefaults().numColumns(2).applyTo(shell); CTabFolder f = new CTabFolder(shell, SWT.NONE); org.eclipse.jface.layout.GridLayoutFactory.fillDefaults().applyTo(f); org.eclipse.jface.layout.GridDataFactory.fillDefaults().grab(true, true).span(1, 21).hint(100, 100).applyTo(f); CTabItem i = new CTabItem(f, SWT.NONE); i.setText("YOYOYOO"); for (int j = 0; j < 20; j++) { Link l = new Link(shell, SWT.NONE); l.setText("link!!"); } Composite mainComp = new Composite(f, SWT.NONE); org.eclipse.jface.layout.GridDataFactory.fillDefaults().grab(true, true).applyTo(mainComp); org.eclipse.jface.layout.GridLayoutFactory.fillDefaults().numColumns(2).applyTo(mainComp); i.setControl(mainComp); for (int j = 0; j < 20; j++) { Link l = new Link(mainComp, SWT.NONE); l.setText("link!!"); Combo c = new Combo(mainComp, SWT.BORDER); c.setItems("a", "b", "c"); c.select(0); } Link l = new Link(mainComp, SWT.NONE); l.setText("link!!"); org.eclipse.jface.layout.GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo(l); Button b = new Button(mainComp, SWT.PUSH); b.setText("open-close-shell-requestLayout"); org.eclipse.jface.layout.GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo(b); b.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent pE) { Shell s = new Shell(shell); org.eclipse.jface.layout.GridLayoutFactory.fillDefaults().applyTo(s); s.pack(); s.open(); Display.getDefault().asyncExec(() -> { s.close(); b.requestLayout(); }); } }); Button b2 = new Button(mainComp, SWT.PUSH); b2.setText("open-dispose-shell-requestLayout"); org.eclipse.jface.layout.GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo(b2); b2.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent pE) { Shell s = new Shell(shell); org.eclipse.jface.layout.GridLayoutFactory.fillDefaults().applyTo(s); s.pack(); s.open(); Display.getDefault().asyncExec(() -> { s.dispose(); b2.requestLayout(); }); } }); shell.pack(); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } } }
运行环境信息:
- Java版本:
openjdk version "1.8.0_252" OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_252-b09) OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.252-b09, mixed mode) - SWT版本:org.eclipse.swt.win32.win32.x86_64_3.114.0.v20200304-0601.jar
- JFace版本:org.eclipse.jface_3.19.0.v20200218-1607.jar
解决方案
可尝试以下几种方案解决闪烁问题,按落地成本从低到高排序:
- 开启双缓冲
给CTabFolder以及其内部承载控件的父Composite添加SWT.DOUBLE_BUFFERED样式,SWT会通过离屏渲染合并重绘过程,直接避免闪烁。修改代码如下:
// 创建CTabFolder时添加双缓冲样式 CTabFolder f = new CTabFolder(shell, SWT.DOUBLE_BUFFERED); // 创建内部容器时也添加双缓冲样式 Composite mainComp = new Composite(f, SWT.DOUBLE_BUFFERED);
该方案改动最小,对绝大多数SWT控件重绘闪烁问题都有效。
- 重绘事件合并
在触发布局前暂停父控件的重绘,布局完成后再恢复重绘,避免多次重绘触发闪烁:
Display.getDefault().asyncExec(() -> { s.close(); // 暂停重绘 mainComp.setRedraw(false); b.requestLayout(); // 恢复重绘,主动触发一次刷新 mainComp.setRedraw(true); mainComp.update(); });
- 错开布局触发时机
当前代码是在Shell销毁的同一事件循环周期内触发布局,和Shell销毁的系统重绘事件冲突,将布局调用延后一个事件周期即可避开冲突:
Display.getDefault().asyncExec(() -> { s.dispose(); // 延后到下一个事件周期执行布局 Display.getDefault().asyncExec(b2::requestLayout); });
若仍然有闪烁,可以再加极短的定时延迟:
Display.getDefault().timerExec(10, b2::requestLayout);
- 升级SWT版本
当前使用的是2020年发布的SWT 3.114版本,后续Eclipse官方修复过多个CTabFolder相关的重绘缺陷,升级到2023-09及之后的稳定版SWT/JFace组件,也可以从根源解决这个原生控件的bug。
内容的提问来源于stack exchange,提问作者Nico Smeenk
相关产品推荐
相关产品推荐

