SWT对话框无法正确显示

时间:2017-03-22 14:56:11

标签: user-interface windows-7 dialog swt

打开新对话框时,在加载时,在父shell上单击几次,显然新对话框无法正确显示。 请参阅以下示例:

实施例

最初我在2014年12月遇到了这个问题,当时我还报道了使用不同开发系统的房屋开发人员,然后我们的几个客户报告了同样的问题。

可以使用以下环境重现此行为:

  
      
  • Windows版本:7 Pro 64位 - 6.1.7601
  •   
  • Java版本:RE 1.8.0_121_b13
  •   
  • SWT版本      
        
    • 3.8.2
    •   
    • 4.6.2
    •   
    • 4.7M6
    •   
    • I20170319-2000
    •   
  •   

我只能在Windows 7上使用windows基本主题/设计/风格(不使用经典或航空)重现问题。 在Windows 10上它不可重复。

再生

重现的代码

package test;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

public class Main {

    public static void main(String[] args) {
        Display display = new Display();
        final Shell shell = createShell(display);
        createButton(shell);
        shell.open();
        eventLoop(display, shell);
        display.dispose();
    }

    private static Shell createShell(Display display) {
        final Shell shell = new Shell(display);
        shell.setLayout(new RowLayout());
        shell.setSize(500, 200);
        return shell;
    }

    private static void createButton(final Shell shell) {
        final Button openDialog = new Button(shell, SWT.PUSH);
        openDialog.setText("Click here to open Dialog ...");
        openDialog.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                TestDialog inputDialog = new TestDialog(shell);
                inputDialog.open();
            }
        });
    }

    private static void eventLoop(Display display, final Shell shell) {
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }
}

class TestDialog extends Dialog {

    public TestDialog(Shell parent) {
        super(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.MIN | SWT.MAX | SWT.RESIZE);
        setText("Dialog");
    }

    public void open() {
        Shell shell = new Shell(getParent(), getStyle());
        shell.setText(getText());
        createContents(shell);
        shell.pack();
        initializeBounds(shell);
        shell.open();
        eventLoop(shell);
    }

    private void createContents(final Shell shell) {
        shell.setLayout(new GridLayout(2, true));

        Label label = new Label(shell, SWT.NONE);
        label.setText("Some Label text ...");

        final Text text = new Text(shell, SWT.BORDER);
        GridData data = new GridData(GridData.FILL_HORIZONTAL);
        text.setLayoutData(data);

        createCloseButton(shell);

        /* time for the user to create the misbehavior */
        try {
            Thread.sleep(15000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void createCloseButton(final Shell shell) {
        Button closeButton = new Button(shell, SWT.PUSH);
        closeButton.setText("Close");
        GridData data = new GridData(GridData.FILL_HORIZONTAL);
        closeButton.setLayoutData(data);
        closeButton.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
                shell.close();
            }
        });
        shell.setDefaultButton(closeButton);
    }

    private void initializeBounds(Shell shell) {
        Rectangle bounds = shell.getBounds();
        Rectangle parentBounds = getParent().getBounds();
        bounds.x = parentBounds.x;
        bounds.y = parentBounds.y;
        shell.setBounds(bounds);
    }

    private void eventLoop(Shell shell) {
        Display display = getParent().getDisplay();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }
}

重现的步骤

  1. 启动应用程序
  2. 点击按钮。
  3. 继续单击父shell的右下角(避免点击新的打开对话框),直到鼠标光标变为等待图标并且父shell更改其颜色。
  4. 等到新对话框出现。
  5. 在视频中重现已完成的步骤

    当鼠标悬停某些UI元素(原来未正确绘制)时,您会注意到其中一些要绘制(例如表格行)。

    1. https://i.stack.imgur.com/kkMKn.png(在打开对话框之前)
    2. https://i.stack.imgur.com/ZXIKc.png(打开对话框后)
    3. https://i.stack.imgur.com/25M7S.jpg(鼠标悬停后)
    4. 在对话框打开后即使调用Shell.update()Shell.redraw()也无法修复它。

      在Windows性能选项中 - >视觉效果 - >禁用“在窗口和按钮上使用视觉样式”是我找到的唯一提供解决方法的选项, 这似乎与将设计/主题/风格改为经典相同。

      最后,我有以下问题: 这是SWT还是Windows问题? Windows或Eclipse Bugzilla的错误条目中是否有相关主题? 是否有其他人遇到过同样的问题?请分享经验。 SWT或Windows中是否存在可能影响其外观并解决问题的设置?

2 个答案:

答案 0 :(得分:1)

  

最后,我有以下问题:是SWT还是Windows问题?

都不是。正如其他人所提到的,你当然不应该将UI线程与任何长期运行的任务联系起来。这项工作属于后台主题。

关于使用后台线程,有几种方法可以解决这个问题,具体取决于您希望Dialog的行为方式。

一个选项是启动后台线程,然后在任务完成时打开对话框。我个人并不关心这一点,因为当任务正在运行时,用户可能会认为没有任何事情发生。

另一种选择是打开对话框但显示"正在加载"消息或其他相关内容,以提供有意义的反馈,并让用户知道应用程序未被冻结(例如它在您的示例中的外观/响应方式)。

策略是:

  1. 创建对话框
  2. 在后台线程上启动长任务并注册回调
  3. 使用" Loading"打开对话框。消息
  4. 任务完成后,将从回调
  5. 更新对话框

    如果您使用Executors搜索一下,您应该找到一些更好的示例和详细说明如何使用它们。

    这是一个简短的例子来说明可能的样子: (注意:这段代码肯定有一些问题,但为了简洁和说明我选择了一个稍微天真的解决方案。还有Java 8式的方式会更短,但是再次,这说明了使用后台线程背后的想法;相同的概念适用)

    给定Callable(如果您不需要返回值,则为Runnable),

    public class LongTask implements Callable<String> {
        @Override
        public String call() throws Exception {
            Thread.sleep(15000);
            return "Hello, World!";
        }
    }
    

    您可以使用Executors类创建线程池,然后使用ExecutorService提交Callable以执行。然后,使用Futures.addCallback(),您可以注册一个回调,该回调将根据任务是成功还是失败执行两种方法之一。

    final ExecutorService threadPool = Executors.newFixedThreadPool(1);
    final ListeningExecutorService executorService = MoreExecutors.listeningDecorator(threadPool);
    final ListenableFuture<String> future = executorService.submit(new LongTask());
    Futures.addCallback(future, new FutureCallback(){...});
    

    在这种情况下,我使用了Google Guava实现ListeningExecutorService,在我看来,这使得事情变得更清晰,更简单。但是,如果你选择更多的Java 8&#34;你可能甚至不需要这个。方法

    至于回调,当任务成功时,我们会用结果更新Dialog。如果失败,我们可以用一些东西来更新它以表明失败:

    public static class DialogCallback implements FutureCallback<String> {
    
        private final MyDialog dialog;
    
        public DialogCallback(final MyDialog dialog) {
            this.dialog = dialog;
        }
    
        @Override
        public void onSuccess(final String result) {
            dialog.getShell().getDisplay().asyncExec(new Runnable() {
                @SuppressWarnings("synthetic-access")
                @Override
                public void run() {
                    dialog.setStatus(result);
                }
            });
        }
    
        @Override
        public void onFailure(final Throwable t) {
            dialog.getShell().getDisplay().asyncExec(new Runnable() {
                @SuppressWarnings("synthetic-access")
                @Override
                public void run() {
                    dialog.setStatus("Failure");
                }
            });
        }
    
    }
    

    在这种情况下,我选择Callable返回String,因此FutureCallback应该使用String参数化。您可能想要使用您创建的其他类,这也可以正常工作。

    请注意,我们使用Display.asyncExec()方法确保更新UI的代码在UI线程上运行,因为回调可以在后台线程上执行。

    就像我说的,这里仍然存在一些问题,包括在任务完成之前单击取消按钮时会发生什么等等。但希望这有助于说明处理长时间运行的后台任务而不阻止UI线程的方法

    完整的示例代码:

    public class DialogTaskExample {
    
        private final Display display;
        private final Shell shell;
        private final ListeningExecutorService executorService;
    
        public DialogTaskExample() {
            display = new Display();
            shell = new Shell(display);
            shell.setLayout(new GridLayout());
    
            executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1));
    
            final Button button = new Button(shell, SWT.PUSH);
            button.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
            button.setText("Start");
            button.addSelectionListener(new SelectionAdapter() {
                @SuppressWarnings("synthetic-access")
                @Override
                public void widgetSelected(final SelectionEvent e) {
                    final MyDialog dialog = new MyDialog(shell);
                    dialog.setBlockOnOpen(false);
                    dialog.open();
                    dialog.setStatus("Doing stuff...");
    
                    final ListenableFuture<String> future = executorService.submit(new LongTask());
                    Futures.addCallback(future, new DialogCallback(dialog));
                }
            });
        }
    
        public void run() {
            shell.setSize(200, 200);
            shell.open();
    
            while (!shell.isDisposed()) {
                if (!display.readAndDispatch()) {
                    display.sleep();
                }
            }
    
            executorService.shutdown();
            display.dispose();
        }
    
        public static void main(final String... args) {
            new DialogTaskExample().run();
        }
    
        public static class DialogCallback implements FutureCallback<String> {
    
            private final MyDialog dialog;
    
            public DialogCallback(final MyDialog dialog) {
                this.dialog = dialog;
            }
    
            @Override
            public void onSuccess(final String result) {
                dialog.getShell().getDisplay().asyncExec(new Runnable() {
                    @SuppressWarnings("synthetic-access")
                    @Override
                    public void run() {
                        dialog.setStatus(result);
                    }
                });
            }
    
            @Override
            public void onFailure(final Throwable t) {
                dialog.getShell().getDisplay().asyncExec(new Runnable() {
                    @SuppressWarnings("synthetic-access")
                    @Override
                    public void run() {
                        dialog.setStatus("Failure");
                    }
                });
            }
    
        }
    
        public static class LongTask implements Callable<String> {
    
            /**
             * {@inheritDoc}
             */
            @Override
            public String call() throws Exception {
                Thread.sleep(15000);
                return "Hello, World!";
            }
    
        }
    
        public static class MyDialog extends Dialog {
    
            private Composite baseComposite;
            private Label label;
    
            /**
             * @param parentShell
             */
            protected MyDialog(final Shell parentShell) {
                super(parentShell);
            }
    
            /**
             * {@inheritDoc}
             */
            @Override
            protected Control createDialogArea(final Composite parent) {
                baseComposite = (Composite) super.createDialogArea(parent);
                label = new Label(baseComposite, SWT.NONE);
                return baseComposite;
            }
    
            public void setStatus(final String text) {
                label.setText(text);
                baseComposite.layout();
            }
    
        }
    
    }
    

答案 1 :(得分:0)

代码似乎是直截了当的,只是你让主线程休眠了15秒,因此延迟了。如果不需要,请取消睡眠或将睡眠时间减少到5秒左右。