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

如何避免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

解决方案

可尝试以下几种方案解决闪烁问题,按落地成本从低到高排序:

  1. 开启双缓冲
    给CTabFolder以及其内部承载控件的父Composite添加SWT.DOUBLE_BUFFERED样式,SWT会通过离屏渲染合并重绘过程,直接避免闪烁。修改代码如下:
// 创建CTabFolder时添加双缓冲样式
CTabFolder f = new CTabFolder(shell, SWT.DOUBLE_BUFFERED);
// 创建内部容器时也添加双缓冲样式
Composite mainComp = new Composite(f, SWT.DOUBLE_BUFFERED);

该方案改动最小,对绝大多数SWT控件重绘闪烁问题都有效。

  1. 重绘事件合并
    在触发布局前暂停父控件的重绘,布局完成后再恢复重绘,避免多次重绘触发闪烁:
Display.getDefault().asyncExec(() -> {
    s.close();
    // 暂停重绘
    mainComp.setRedraw(false);
    b.requestLayout();
    // 恢复重绘,主动触发一次刷新
    mainComp.setRedraw(true);
    mainComp.update();
});
  1. 错开布局触发时机
    当前代码是在Shell销毁的同一事件循环周期内触发布局,和Shell销毁的系统重绘事件冲突,将布局调用延后一个事件周期即可避开冲突:
Display.getDefault().asyncExec(() -> {
    s.dispose();
    // 延后到下一个事件周期执行布局
    Display.getDefault().asyncExec(b2::requestLayout);
});

若仍然有闪烁,可以再加极短的定时延迟:

Display.getDefault().timerExec(10, b2::requestLayout);
  1. 升级SWT版本
    当前使用的是2020年发布的SWT 3.114版本,后续Eclipse官方修复过多个CTabFolder相关的重绘缺陷,升级到2023-09及之后的稳定版SWT/JFace组件,也可以从根源解决这个原生控件的bug。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 10:15:03