これまで3回にわたってSwingでのドラッグ&ドロップについて解説してきましたが,今週が最終回です。

今週扱うのはフレームやダイアログなどのトップレベルのコンポーネントに対するドロップです。

ドラッグ&ドロップのドロップは,実際にデータを転送させるコンポーネントに設定するのが普通です。しかし,アプリケーション全体でドロップさせたいときもあります。例えば,ファイル・ビューアやイメージ・ビューアなど単一の情報しか扱わないアプリケーションではどうでしょう。アプリケーションのフレームのどこでもドロップしたくなりませんか。

サンプルとして,図1のようなイメージ・ビューアを作ってみました。このイメージ・ビューアは,エクスプローラなどからイメージ・ファイルをドロップすることができます。

イメージ・ビューア
図1 イメージ・ビューア

トップレベルはJFrameです。JMenuBarとJDesktopPaneがその上に貼られています。オープンしたイメージはJInternalFrameで表示します。

このとき,TransferHandlerをどこに設定すればいいでしょうか。まず考えられるのが,JDesktopPaneに設定するということです。

JDesktopPaneに設定してみた結果が図2です。

JDesktopPaneに設定
図2 JDesktopPaneに設定

JDesktopPaneに設定したにもかかわらず,JInternalFrameでもドロップ可能になっています。

しかし,メニューバーやタイトルバーにはドロップできません。

それではJDesktopPaneとJMenuBarの両方にしてみたら…。そんなときはもっと上のレベルにTransferHandlerを設定すべきです。JDesktopPaneはコンテントペインに貼られています。そして,コンテントペイントとJMenuBarはJRootPaneに貼られています。

そこで,JRootPaneにTransferHandlerを設定してみました。JRootPaneを取得するにはJFrameクラスのgetRootPaneメソッドを使用します。実行した結果を図3に示します。

JRootPaneに設定
図3 JRootPaneに設定

JRootPaneにTransferHandlerを設定することにより,JMenuBarにもドロップできるようになりました。しかし,タイトルバーには依然としてドロップできません。

それならば,JFrameにTransferHandlerを設定すれば,と思いますよね。ところが,J2SE 5.0まではJFrameクラスにTransferHandlerを設定するためのsetTransferHandlerメソッドが定義されていなかったのです。

Java SE 6でようやくJFrameクラスにsetTransferHandlerメソッドが定義されました。さっそくJFrameにTransferHandlerを設定してみましょう(図4)。

JFrameに設定
図4 JFrameに設定

図4を見ればわかるように,タイトルバーにもドロップできるようになっています。

最後にこのサンプルのソースコードを示しておきましょう。

サンプルのソースコード ImageViewer.javaImageTransferHandler.java

先週までのサンプルはすべて文字列だけを扱っていましたが,ここではファイルをドロップさせます。ファイルを扱うためのフレーバはDataFlavor.javaFileListFlavorです。Listとなっていることからわかるように,単一のファイルだけでなく,複数ファイルを扱うことができます。

ファイルがドロップできるかどうかを判定するのがcanImportメソッドです。

public boolean canImport(TransferSupport support) {
    // クリップボード経由のデータ転送は扱わない
    if (!support.isDrop()) {
        return false;
    }

    // ファイル以外のフレーバは受け入れない
    if (!support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
        return false;
    }

    // ドロップする位置を常に表示する
    support.setShowDropLocation(true);

    return true;
}

転送する情報がドロップできるかどうかを調べるには,TransferHandler.TransferSupportクラスのisDataFlavorSupportedメソッドを使用します。文字列の場合はisDataFlavorSupportdメソッドの引数はDataFlavor.stringFlavorですが,ファイルの場合は前述したDataFlavor.javaFileListFlavorを使用します。

それ以外の部分はフレーバが変化しても同じです。

実際にドロップの処理を行うのはimportDataメソッドです。

public boolean importData(TransferSupport support) {
    if (!canImport(support)) {
        return false;
    }

    try {
        Object obj = support.getTransferable()
            .getTransferData(DataFlavor.javaFileListFlavor);
        List<File> files = (List<File>)obj;
        
        for (File file: files) {
            if (filter.accept(file)) {
                viewer.loadImage(file);
            }
        }
        
        return true;
    } catch (UnsupportedFlavorException ex) {
        // 失敗したら false を返すだけ
    } catch (IOException ex) {
        // 失敗したら false を返すだけ
    }

    return false;
}

フレーバがDataFlavor.javaFileListFlavorの場合,Transferable#getTransferDataメソッドで取得できるオブジェクトはFileオブジェクトを保持したListオブジェクトです。

そこで,赤字で示したようにList<File>にキャストします。残念ながら,getTransferDataメソッドはジェネリクスに対応していないのでキャストが必要です。このため,コンパイルのときに,「未チェックまたは安全ではありません」という警告が出てしまいます。もし,警告が気になるようであれば,@SuppressWarningアノテーションをメソッド定義の前に付加することで,警告を抑制できます。

取り出したファイルがイメージ・ファイルであるかどうかをチェックしているのがfilterです。filterはJFileChooser用のjavax.swing.filechooser.FileNameExtensionFilterクラスを使用しています。次の行のviewerがイメージ・ビューアの本体です。loadImageメソッドがイメージ・ファイルからイメージをロードする処理を行っています。

ImageViewerクラスは特に難しいところはないので,説明を省略します。興味のある方はソースコードを読んでみてください。

このサンプルではJFrameに対してTransferHandlerを設定しました。これ以外に,JDialogクラス,JWindowクラス,JAppletクラスにもsetTransferHandlerメソッドが定義されました。

これで「タイトルバーにはドロップができない」という言い訳は通用しないですよ。

著者紹介 櫻庭祐一

横河電機 ネットワーク開発センタ所属。Java in the Box 主筆

今月の櫻庭

毎年,JavaOneに参加すると,自分の英語力のなさを思い知ります。

JavaOneにはそれこそ世界中からJavaの技術者が集まってきます。ランチ会場やパビリオンなどで,参加者同士で議論が始まったりします。そういう場になかなか入っていけないのです。

毎年,英語を勉強しなくてはと思うのですが,それが長続きしたことはありません。長続きしていれば,もうペラペラになっているはずなのですが…

5月30日にJavaOne報告会が行われ,その中のライトニングトークで複数の講師がニンテンドーDSのえいご漬けを購入したと話していました。やっぱり,みな考えることは同じなのかもしれませんね。