Java AWT: データ転送
最終更新: 1997年 2 月3 日
注: クリップボード API は JDK 1.1 にありますが、ドラッグとドロップ API は( 1.1 の時間的制約により)次のバージョンになります 。ここでは、Java Beans ドキュメントから参照ができ、できる限り早くフィードバックを得られるように、ドラッグとドロップのドキュメントを入れています。
目的
今日の典型的な GUI ユーザは、切り取り/コピー/貼り付けおよびドラッグとドロップのような操作を使用して、アプリケーション間でデータ転送できることを期待しています。現在の Java 環境でのこの唯一の機構は、AWT ネイティブウィジット (TextField、TextArea) がデフォルトで提供する機能を介するものです。しかし、アプリケーションがこれらネイティブウィジット外でそのような操作を有効にする必要のあるケースがたくさんあります。そのため、Java プラットフォームは、基本的なデータ転送機能を有効化するために、 API を提供する必要があります。このドキュメントでは、様々な転送プロトコルをその上に構築できる Java オブジェクトのベースラインデータ転送を定義します。このドキュメントではまた、これら上位レベル転送プロトコルの 2 つの API、クリップボード および ドラッグとドロップ について説明します。
Java データ転送
転送 API の設計目標
データ転送 API の設計目標は次のとおりです:
java.awt.datatransfer.Transferable
Transferable オブジェクトは、最も豊富な内容説明のフレーバから最低のフレーバまで要求された、(データフレーバと呼ばれる)データ提供フォーマットのリストを出せなければなりません。これはまた、特定のフレーバで要求されたとき、(オブジェクト参照の形式で)データを返すことができなければなりません(あるいは、そのフレーバがサポートされていない場合、またはそのデータがもはや使用できない場合には、例外をスローします)。
共通データ型に Transferable インタフェースを実装する convenience クラスは、開発者がこれらの共通型の転送を容易に行えるようにするために提供されます。例えば次のとおりです:
java.awt.datatransfer.StringSelection
この API の目的は、特定のエレメントまたはデータ型を転送可能にする作業に入ったとき、上位レベル転送プロトコル(クリップボード、ドラッグとドロップなど)のどれかを使用して、簡単にそれを渡せることを保証することです。
データフレーバ
典型的な共通データ転送操作(クリップボート、ドラッグとドロップ)には、データを転送するフレーバ上での、プロバイダと要求者の間のネゴシエーションという様相があります。例えば、html テキストがブラウザで選択され、別個のワードプロセスアプリケーションにコピー/貼り付けされるとき、貼り付け操作の可能な対象アプリケーション数を最大化するために、ブラウザは通常データを複数のフレーバ(大抵 htmlフ ォーマットのテキストおよび単なる ASCII )で提供します。
このネゴシエーションでは、個別のアプリケーションでこれら様々なフレーバとデータ型が一意に定義され認識できる、データ型分類用名前空間の定義が必要です。「フォーマット」などの過負荷な用語との混同を避けるために、「フレーバ」という用語を選びこの概念を表します。
データフレーバは、アプリケーション間のフレーバネゴシエーションと転送を可能にするために、特定のフレーバに関する必要な情報すべてをカプセル化したオブジェクトによって表します:
java.awt.datatransfer.DataFlavor
この情報には、フレーバの論理名(プログラム上の識別を可能にする)、人間紹介名(ユーザに紹介するために使用し、ローカライズが可能)、データを実際に転送するオブジェクトクラスを定義するために使用する表現クラスなどが入っています。
MIME 型の登録は、現在 Internet Assigned Numbers Authority (IANA) というサードパーティが行っており、開発者が簡単に公開データフォーマット用に使用する標準型/サブ型名を検索できるようになっています。幸い、あまり一般的でないフォーマットに対して新しい MIME 型/サブ型名を定義するためには、公式の登録は必要ありません(このような公式の要件が、基本的な Java データ転送で容認されないのは好ましいことです)。新しい型名は公式な登録はせずに、名前の前に "x-" を追加して作成できます。
Object getTransferData(DataFlavor flavor)
現在の DataFlavorクラスは、次の 2 つの一般的なデータフレーバを定義します。
MIME-type="application/x-java-serialized-object; class=<implemenation class>"
RepresentationClass=<implemenation class>
たとえば、AWT GUI コンポーネントを表す DataFlavor:
MIME-type="application/x-java-serialized-object; class=java.awt.Component"
RepresentationClass=java.awt.Component
転送操作の要求側がこのフレーバのデータを求める場合、Componentクラスのインスタンスが戻されます。
MIME-type="application/<mime-subtype>"
RepresentationClass=java.io.InputStream
たとえば、RTFテキストを表す DataFlavor:
MIME-type="application/rtf"
RepresentationClass=java.io.InputStream
転送操作の要求側がこのフレーバのデータを求める場合、それから
RTFフォーマットのテキストを読み込み/解析できる InputStream
インスタンスが戻されます。
与えられた MIME 型(上記型 #2)については、Java プログラムは異なる表現クラスを持つ複数のフレーバを自由に作成できます。例えば、MIME 型 application/rtf にフレーバを提供することに加えて、プログラムは別のフレーバを指定することもできます:
たとえば、RTFテキストを表す DataFlavor:
MIME-type="application/rtf"
RepresentationClass=foobar.fooRTF
DataFlavor の概念は複雑で困惑させるように思えるかもしれませんが、意図しているのは、共通に使用するデータフレーバのセットを定義して、これを開発者にとってできる限り使用しやすくすることです。
複数項目の転送
転送プロトコルが、単一の転送操作で複数の別個なデータの転送をサポートすること(すなわち、ファイルマネジャーアプリケーションから、複数ファイルのアイコンをドラッグ、ドロップすること)は、一般的ではありません。転送 API は、複数データ項目の同時転送をある形式でサポートする必要があり、現在の提案は、個別データオブジェクトの集まりを扱うことができる Transferable を実装して、この機能をカプセル化することです。この計画は現在綿密に調査されており、この提案を将来改訂するときに詳細に検討されます。
Transferableオブジェクト作成のコード例
次のコードは StringSelection クラスソースを示します。これは当提案で単なる Unicode{tm} テキストを転送できるクラスを作成するための API の使用方法の例です。
package java.awt.datatransfer;
import java.io.*;
/**
* A class which implements the capability required to transfer a
* simple java String in plain text format.
*/
public class StringSelection implements Transferable, ClipboardOwner {
final static int STRING = 0;
final static int PLAIN_TEXT = 1;
DataFlavor flavors[] = {DataFlavor.stringFlavor, DataFlavor.plainTextFlavor};
private String data;
/**
* Creates a transferable object capable of transferring the
* specified string in plain text format.
*/
public StringSelection(String data) {
this.data = data;
}
/**
* Returns the array of flavors in which it can provide the data.
*/
public synchronized DataFlavor[] getTransferDataFlavors() {
return flavors;
}
/**
* Returns whether the requested flavor is supported by this object.
* @param flavor the requested flavor for the data
*/
public boolean isDataFlavorSupported(DataFlavor flavor) {
return (flavor.equals(flavors[STRING]) || flavor.equals(flavors[PLAIN_TEXT]));
}
/**
* If the data was requested in the "java.lang.String" flavor, return the
* String representing the selection, else throw an UnsupportedFlavorException.
* @param flavor the requested flavor for the data
*/
public synchronized Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
if (flavor.equals(flavors[STRING])) {
return (Object)data;
} else if (flavor.equals(flavors[PLAIN_TEXT])) {
return new StringReader(data);
} else {
throw new UnsupportedFlavorException(flavor);
}
}
public void lostOwnership(Clipboard clipboard, Transferable contents) {
}
}
クリップボードアーキテクチャは、Java データ転送 API が定義するデータ転送機構に依存しています。クリップボード API には、標準クリップボード用のデータ転送モデルを実装する次の単一のクラスがあります:
java.awt.datatransfer.Clipboard
そして、データをクリップボードに書き込んでいるクラスが実装する次のインタフェースがあります:
java.awt.datatransfer.ClipboardOwner
Clipboardクラスは、クリップボードとの読み込み/書き込み用の次の 2 つの基本的メソッドを提供します:
void setContents(Transferable content, ClipboardOwner owner)
Transferable getContents(Object requestor)
ClipboardOwner インタフェースは次の単一のメソッドからなり、これは別のオブジェクトがクリップボードの所有権を主張した場合に呼び出されます。
void lostOwnership(Clipboard clipboard)
開発者が一般的なデータ型にクリップボード操作を実装するジョブを簡単に行えるように、標準的な方法で ClipboardOwner インタフェースを実装する convenience クラスが提供されます:
java.awt.datatransfer.StringSelection
java.awt.Toolkit 内の次のメソッドは、ネイティブプラットフォーム機能とのインタフェースを持つクリップボードインスタンスへのアクセスを提供します:
Clipboard getSystemClipboard();
「貼り付け」を実装するプログラムの一般的操作手順:
(注: 簡単にするために、コピー/貼り付け操作の元/先として TextArea を使用します; ほとんどのプラットフォームで、切り取り/コピー/貼り付けは既にネイティブピア内の TextArea および TextField 用に実装されています)
import java.awt.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;
public class ClipboardTest extends Frame
implements ClipboardOwner, ActionListener {
TextArea srcText, dstText;
Button copyButton, pasteButton;
Clipboard clipboard = getToolkit().getSystemClipboard();
public ClipboardTest() {
super("Clipboard Test");
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
setLayout(gridbag);
srcText = new TextArea(8, 32);
c.gridwidth = 2;
c.anchor = GridBagConstraints.CENTER;
gridbag.setConstraints(srcText, c);
add(srcText);
copyButton = new Button("Copy Above");
copyButton.setActionCommand("copy");
copyButton.addActionListener(this);
c.gridy = 1;
c.gridwidth = 1;
gridbag.setConstraints(copyButton, c);
add(copyButton);
pasteButton = new Button("Paste Below");
pasteButton.setActionCommand("paste");
pasteButton.addActionListener(this);
pasteButton.setEnabled(false);
c.gridx = 1;
gridbag.setConstraints(pasteButton, c);
add(pasteButton);
dstText = new TextArea(8, 32);
c.gridx = 0;
c.gridy = 2;
c.gridwidth = 2;
gridbag.setConstraints(dstText, c);
add(dstText);
pack();
}
public void actionPerformed(ActionEvent evt) {
String cmd = evt.getActionCommand();
if (cmd.equals("copy")) {
// Implement Copy operation
String srcData = srcText.getText();
if (srcData != null) {
StringSelection contents = new StringSelection(srcData);
clipboard.setContents(contents, this);
pasteButton.setEnabled(true);
}
} else if (cmd.equals("paste")) {
// Implement Paste operation
Transferable content = clipboard.getContents(this);
if (content != null) {
try {
String dstData = (String)content.getTransferData(
DataFlavor.stringFlavor);
dstText.append(dstData);
} catch (Exception e) {
System.out.println("Couldn't get contents in format: "+
DataFlavor.stringFlavor.getHumanPresentableName());
}
}
}
}
public void lostOwnership(Clipboard clipboard, Transferable contents) {
System.out.println("Clipboard contents replaced");
}
public static void main(String[] args) {
ClipboardTest test = new ClipboardTest();
test.show();
}
}
注: ドラッグとドロップ APIは、このバージョンの時間的制約によって、JDK1.1 には入っていません。
ドラッグとドロップAPI の設計目標
この API の設計目標は次のとおりです:
各ネイティブプラットフォームは、ドラッグとドロップに関してやや異なるモデルと
機能セットを提供しています。この API は、機能と動作の組み合わせにおいて、 OLE のドラッグとドロップに最も近いものです。このアーキテクチャの決定は、多数の Windows ユーザによるものではありませんが、Motif の制限によって制約された設計は受け入れがたく、かたや Macintosh のドラッグとドロップ機能のすべてを包含したアーキテクチャは実装不可能であるという理解に基づいています。
ドラッグとドロップ APIの概要
コンポーネントは、インタフェースを実装することによって、ドラッグ可能であると宣言します:
java.awt.dnd.DragSource
DragSources は次のことに対して責任を持ちます:
注: COPY、MOVE、NONE アクション、加えてそのスクロールの変種 (SCROLL_COPYおよび SCROLL_MOVE) 用の標準カーソルオブジェクトがAWT に追加され、デフォルトのカーソル動作の実装をサポートし、他の同様なコンテキストでこれらのカーソルを AWT ユーザが使用できるようにする。
コンポーネントは次のインタフェースを実装し、ドロップを受け取ることができると宣言します:
java.awt.dnd.DropTarget
DropTarget は次のことに責任を持ちます:
dropEnter() は、ターゲットが DragSource の DragFlavor とデータの互換性に関する状態をキャッシュして、dropOver() のパフォーマンスを向上できるようにするために存在する。これは、DropTarget の dropOver() メソッドが dropEnter() に比べて呼び出される回数によっては、効果的である。
DropTarget がユーザへのフィードバックの下でドラッグを提供する場合、通常 dropOver() 内で行う。このフィードバックの目標は、現在のドラッグ位置でのドロップの結果がどんなものかをユーザに明確に分かってもらうためである。この例としては、挿入キャレットと共にドラッグされるテキストの位置に付き従うテキストエディタ DropTarget があり、これはユーザにドロップされたときのテキストが挿入される正確な位置を示す。
ドラッグが DropTarget に入ったとき必ず、AWT 実装は DropTarget の dropEnter() メソッドを呼び出す。そのため、ドラッグ がDropTarget 上を移動するか、またはユーザがドロップに応じて実行されるアクションを変更したいと指示したとき、常に dropOver() が呼び出される。これはドラッグが DropTarget を抜けるまで続く。
[DropEnter() が呼び出された場合、最低一度は DropOver() が呼び出されると保証したい。これはしばしば余分な DropOver() を生成してパフォーマンスを悪くするが、プログラミングモデルを幾分簡単にする。]
Motifでは、dropScroll() の pulse を生成する際問題がいくつかあり、まだ解決に向けて作業の必要がある。
注: ACTION_LINK は、 API の複雑さを減少させるために除外されています。
もちろん、異なるアプリケーションまたは Java Virtual Machine 間でドラッグとドロップをするとき、ターゲットが希望するオブジェクトをソースからコピーすることはできません。各ケースで実際なにが起こるかというと、これはかなり複雑で、ネイティブのドラッグとドロップ機構の例として OLE ドラッグとドロップを使用して次に概要を述べます。
ドラッグとドロップによって転送するためには、Java オブジェクトは Transferable インタフェースをサポートする必要があります。このインタフェースは、オブジェクト(Transferable インタフェースの getTransferDataFlavors() メソッド) がサポートする異なる DataFlavorを列挙し、元のオブジェクトの所定の DataFlavor を表す新しいオブジェクトを返す責任を持っています。
DragSource が、ドラッグされるオブジェクトの集まりに Java オブジェクトを追加するとき、AWT 実装はオブジェクトの周囲に IDataObject インタフェースをラップします。IDataObject->EnumFormatEtc() はオブジェクトの getTransferFormats() メソッドに結び付けられ、IDataObjectGetData() は getTransferData() に結び付けられます。
EnumFormatEtc() が返す値は、DragSource が供給する DataFlavor の MIME 型によって決定されます。各 MIME 型文字列は、MIME 型を Windows のクリップボードフォーマットに割り当てるレジストリで参照されます。割り当てが見つかった場合 (例えば、text/plain = CF_TEXT)、その割り当ては FormatEtc.cfFormat の値のために使用されます。見つからない場合、MIME 型の呼び出ししている RegisterClipboardFormat() から返された値が使用されます。
ターゲットがソースからドラッグされる Java オブジェクトの 1 つの固有の DataFlavorを要求するとき、ドラッグされるオブジェクトの getTransferData() メソッドが、指定した DataFlavor で呼び出されます。これは次の 2 つのうち 1 つを実行する必要があります:
これは Windows の下でのドラッグとドロップの実装についての動作ですが、 Macintosh と Motif についても基本的に同様です。重要なことは、DragSource 側と DropTargets 側の動作は常にお互いに独立しており、他の側が別の Java アプリケーションでも気にかけないということです。DragSource 側は常に、 Java オブジェクトと DataFlavor をネイティブのドラッグとドロップ概念に変換し、DropTarget 側は常に、ネイティブのドラッグとドロップを Java に変換します。この分離によって、Java アプリケーション間のドラッグとドロップが、 Java とネイティブアプリケーション間のドラッグとドロップに同等となります。後者の点で、DragSource と DropTarget が同じ Java Virtual Machine 内にあるとき、直列化を短絡し、可能ならばオブジェクトを単にクローン化することによって、パフォーマンスを常に向上することができます。
例えば、ユーザが emacs 上のグラフィカルレイアウトエディタから矩形とテキストフィールドをドラッグする場合、emacs はフィードバックを返し、テキストフィールドのコンテンツは受け入れられるが矩形のコンテンツは受け入れられないので、ドロップを受け入れないということを示す必要があります。
[個別オブジェクトを今のようにさらしておくか、または列挙が渡される OLE に近いアプローチを使用するかは、いまだ明確ではありません。]
java.awt.dnd.DragContext
java.awt.dnd.DropContect
DragContext は、ドラッグが始まったとき DragSource の dragBegin() によって作成され、返されます。これには、このドラッグのドロップターゲットのオブジェクトと許されるアクションのリストが含まれます。さらにまた、DragSource の dragOver() メソッド内部で呼び出される次の関数をサポートします:
dragOver() が FALSE を返したとき使用されるデフォルトの AWT 実装は、getSuggestedAction() を呼び出し、結果を setCursor() に渡す。(次に説明する。)
DropContext はドラッグとドロップ実装によって作成され、すべての DropTarget メソッドに渡されます。これは、ドラッグについての情報を決定するための次の関数を供給します:
- ACTION_COPY -> ACTION_NONE
-ACTION_MOVE -> ACTION_COPY
DropTarget は、一群の完全なアクションをサポートしない場合、提示されたアクションと異なるアクションを返そうとする可能性がある。例えば、ファイルマネージャはボリューム間でコピーされたファイルだけを許可し、移動されたものは許可しない。
このソースには、ドロップが発生したときどのアクションを実行するかについて最終の決定権がある。それは、ソースが MOVE アクションを実行するために必要な追加処理を実行する責任を持つからである。
オブジェクトが GIF ファイルのように非 Java オブジェクトを表す場合、DropTarget は InputStream を受け取る。 DropTarget は次に InputStream を使用しすべてのオブジェクトデータを受け取ることができる。
AWT 実装が Java プログラマの生活を楽にさせたいという要望に基づいて、非 Java フォーマットを Java 構築物に変換することは可能である。例えば、Adobe Photoshop から Java アプリケーションにドラッグされた GIF ファイルは、基礎をなす AWT 実装によって Java ImageProducer に変換可能である。一方、Windows メールマージアプリケーションからドラッグされたストリートアドレスは、アドレスの直列化バージョンを含んだ単純な InputStream によって表すことが可能となる。
DragSources および DropTargets は自らを private DataFlavors に限定することで、その乱雑さを制限することができます。しかし、もっと率直な設計をして、 DragContext に現在の DropTarget を得るためのメソッドを追加したり、DropContext に現在の DragSource を取得するためのメソッドを追加したりできるかもしれません。これらのルーチンは、ドラッグが同じ Java VM 内にない場合、null を返します。これは、各項目がそのどちらかに存在するという、ある一対のテキストリストのようなユーザインタフェースを実装可能にします。