SWTでマルチウインドウアプリケーション

マルチウインドウアプリケーション

自分が作りたいのは、1つのアプリケーションが複数の(基本的には対等な)ウインドウをもつモデルです。一般のアプリケーションは「完全に単一ウインドウ」「文書の数だけウインドウ」といった形が多いわけですが、自分が考えているものは異なった機能をもつウインドウが並列される形なので、ちょっと違います。

こういった形のアプリケーションは、Windowsでは、モードレスウインドウとして実装するのが普通だろうと思います。SWTではどうかというと、普通にShellを複数開くと、デフォルトがモードレスなので、基本的には単にShellを作ればよさそうです。

参考

SWTで、複数画面の表示を実現したいです。 下記URLのようにやれ… - 人力検索はてな http://q.hatena.ne.jp/1304749844

ここにコード例がありますね。

試してみる

上記サンプルから、少しクラスを分けてみます。

まずはダイアログ用のクラス。

package com.example.swttest03;

import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

public class MyDialog {
    private Shell parentShell;
    
    public MyDialog(Shell sh) {
        parentShell = sh;
    }
    
    public void open() {
        Shell shell;
        shell = new Shell(parentShell,SWT.DIALOG_TRIM);
        shell.setText("Dialog Window");
        shell.setLayout(new FillLayout());
        new Text(shell, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
        shell.setSize(300, 200);
        shell.open();
        Display display;
        display = parentShell.getDisplay();
        while(!shell.isDisposed()) {
            if (!display.readAndDispatch()) { display.sleep(); }
        }
        display.dispose();
    }
}

次にメインウインドウとmainメソッド

package com.example.swttest03;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class Swt03Main {
    public static void main(String[] args) {
        Display display = new Display();
        
        Shell invisible = new Shell(display);
        invisible.setText("Invisible");
        
        Shell shell = new Shell(display);
        shell.setText("Main Window");
        shell.setLayout(new FillLayout());
        shell.addShellListener(new ShellAdapter() {
            public void shellClosed(ShellEvent e) {
                invisible.dispose();
            }
        });
        
        Button button = new Button(shell, SWT.BORDER);
        button.setText("Open Dialog");
        button.addSelectionListener(new SelectionListener() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                MyDialog dialog = new MyDialog(invisible);
                dialog.open();
            }
            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
            }
        });
        
        shell.setSize(200, 150);
        shell.open();
        
        while(!shell.isDisposed()) {
            if (!display.readAndDispatch()) { display.sleep(); }
        }
        display.dispose();
    }
}

実行してみました。メイン以外に複数のウインドウが展開できています。

f:id:mokake:20151017175551p:plain

ただ、これだと、ダイアログウインドウを選択すると、全てのダイアログウインドウがメインよりも手前に来てしまいます。トップレベルのShellがshellとinvisibleの2つなので、invisible側を選ぶとshell側は奥になってしまうのですね。それなら、全てをトップレベルShellにしてしまいましょうか。

トップレベルにも、子にもなれるダイアログクラス(コンストラクタで振り分け)です。

package com.example.swttest03;

import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

public class MyDialog {
    private Shell parentShell;
    private Display parentDisplay;
    
    public MyDialog(Display d) {
        parentDisplay = d;
        parentShell = null;
    }
    
    public MyDialog(Shell sh) {
        parentShell = sh;
        parentDisplay = null;
    }
    
    public void open() {
        Shell shell;
        if (parentDisplay != null) {
            shell = new Shell(parentDisplay, SWT.DIALOG_TRIM);
        } else {
            shell = new Shell(parentShell,SWT.DIALOG_TRIM);
        }
        shell.setText("Dialog Window");
        shell.setLayout(new FillLayout());
        new Text(shell, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
        shell.setSize(300, 200);
        shell.open();
        Display display;
        if (parentDisplay != null) {
            display = parentDisplay;
        } else {
            display = parentShell.getDisplay();
        }
        while(!shell.isDisposed()) {
            if (!display.readAndDispatch()) { display.sleep(); }
        }
        display.dispose();
    }
}

次に呼び出し側。MyDialogの生成時引数だけが違います。

package com.example.swttest03;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class Swt03Main {
    public static void main(String[] args) {
        Display display = new Display();
        
        Shell invisible = new Shell(display);
        invisible.setText("Invisible");
        
        Shell shell = new Shell(display);
        shell.setText("Main Window");
        shell.setLayout(new FillLayout());
        shell.addShellListener(new ShellAdapter() {
            public void shellClosed(ShellEvent e) {
                invisible.dispose();
            }
        });
        
        Button button = new Button(shell, SWT.BORDER);
        button.setText("Open Dialog");
        button.addSelectionListener(new SelectionListener() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                MyDialog dialog = new MyDialog(display);
                dialog.open();
            }
            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
            }
        });
        
        shell.setSize(200, 150);
        shell.open();
        
        while(!shell.isDisposed()) {
            if (!display.readAndDispatch()) { display.sleep(); }
        }
        display.dispose();
    }
}

実行してみます。今度は任意のZオーダーができています。

f:id:mokake:20151017180028p:plain

でもこれ、実際には問題が残ってます。

  • もはや使ってないinvisibleが残っている
  • shellのdispose時にinvisibleはdisposeしているが、生成したMyDialogインスタンスはdisposeしていない

つまり、生成したダイアログは全部登録して一括disposeする必要があるのですね。この辺などを考慮しつつ、汎用的な基底ダイアログクラスなどを考えていく必要がありそうです。