Javaのヒント109:JEditorPaneを使用して画像を表示する

現在のJEditorPaneコンポーネントを使用してHTMLマークアップを表示できますが、より複雑なタスクを実行するにJEditorPaneは、いくつかの改善が必要です。最近、XMLフォームビルダーアプリケーションを作成する必要がありました。必要なコンポーネントの1つは、一部のXMLタグ内のHTMLマークアップコンテンツを編集できるWYSIWYGHTMLエディターでした。JEditorPaneHTMLマークアップはすでに組み込まれているため、HTMLマークアップを表示するためのJavaコンポーネントの選択肢は明らかでした。残念ながら、HTMLマークアップに挿入すると、JEditorPane相対パスを含む画像を表示できませんでした。たとえば、相対パスを持つ次の画像がXMLタグに含まれている場合、正しく表示されません。


  

逆に、絶対パスは機能します(指定されたパスとイメージが実際に存在すると仮定します)。


  

私のアプリケーションでは、画像は常にXMLファイルの場所に関連するサブディレクトリに保存されていました。したがって、私は常に相対パスを使用したかったのです。この記事では、この問題が存在する理由とその修正方法について説明します。

なぜこれが起こるのですか?

のコンストラクターを詳しく調べると、JEditorPane相対パスで画像を表示できない理由を理解するのに役立ちます。

  1. JEditorPane()新しいを作成しJEditorPaneます。
  2. JEditorPane(String url)JEditorPaneURL仕様を含む文字列に基づいてを作成します。
  3. JEditorPane(String type, String text)JEditorPane指定されたテキストに初期化されたを作成します。
  4. JEditorPane(URL initialPage)JEditorPane入力用に指定されたURLに基​​づいてを作成します。

2番目と4番目のコンストラクターは、リモートまたはローカルのHTMLファイルへの参照を使用してオブジェクトを初期化します。アンはHTMLDocument、すべての内部にありJEditorPane、そのベースは、URLのコンストラクタパラメータのベースに設定されています。JEditorPaneこれらのコンストラクタを使用して作成されたsは、相対パスを処理できます。これは、ベースがHTMLDocument相対パスと結合して絶対パスを作成するためです。

最初のコンストラクターを使用する場合は、オブジェクトの作成後に表示テキストを挿入する必要があります。3番目のコンストラクターはStringコンテンツとしてaを受け入れますが、ベースは初期化されていません。ファイルではなくXMLタグからHTMLマークアップを取得したかったので、最初または3番目のコンストラクターを使用する必要がありました。

どうすれば問題を解決できますか?

先に進む前に、別の小さな問題を明らかにして解決しましょう。にマークアップを挿入する最も明白な方法JEditorPaneは、を使用することsetText(String text)です。ただし、この方法では、変更を加えるたびに、表示されているマークアップ全体を入力する必要があります。理想的には、新しいタグを既存のテキストに挿入する必要があります。次のコードを使用して、新しいマークアップを追加できます。

private void insertHTML(JEditorPane editor、String html、int location)throws IOException {//エディターがすでに「text / html」タイプに設定されていると想定HTMLEditorKitkit =(HTMLEditorKit)editor.getEditorKit(); ドキュメントdoc = editor.getDocument(); StringReaderリーダー=新しいStringReader(html); kit.read(reader、doc、location); }

ここで、問題の核心にJEditorPane迫ります。HTMLをどのようにレンダリングするのでしょうか。各タイプはJEditorPaneDocumentとの両方を参照しEditorKitます。JEditorPaneが「text / html」タイプに設定されている場合HTMLDocument、マークアップを含む、とHTMLEditorKit、マークアップに含まれる各タグをレンダリングするクラスを決定するが含まれます。具体的には、HTMLEditorKitクラスには、メソッドが実際に各個別のタグを検査するHTMLFactory内部クラスが含まれていますcreate(Element elem)。画像タグを処理するファクトリクラスのコードは次のとおりです。

 else if(kind == HTML.Tag.IMG)return new ImageView(elem); 

ご覧のとおり、ImageViewクラスは実際に画像を読み込みます。画像の場所を確立するために、getSourceURL()メソッドは次のように呼び出されます。

プライベートURLgetSourceURL(){String src =(String)fElement.getAttributes()。getAttribute(HTML.Attribute.SRC); if(src == null)はnullを返します。URL参照=((HTMLDocument)getDocument())。getBase(); {URL u = new URL(reference、src);を試してください。uを返します。} catch(MalformedURLException e){nullを返す; }}

ここで、このgetSourceURL()メソッドは、HTMLDocumentベースを使用して画像を参照するための新しいURLを作成しようとします。そのベースがnullの場合、nullが返され、画像の読み込み操作が中止されます。その動作をオーバーライドしたいとします。

理想的には、ImageViewクラスをサブクラス化しinitialize(Element elem)、画像の読み込みが行われるメソッドをオーバーライドします。残念ながら、そのクラスはパッケージで保護されているため、まったく新しいクラスを作成する必要があります。これを行う最も簡単な方法は、元のImageViewクラスからコードを借用してから変更することです。それを呼びましょうMyImageView

まず、画像を読み込んだコードを見てください。以下は、initialize(Element elem)メソッドから取得されます。

URL src = getSourceURL(); if(src!= null){ディクショナリキャッシュ=(ディクショナリ)getDocument()。getProperty(IMAGE_CACHE_PROPERTY); if(cache!= null)fImage =(Image)cache.get(src); else fImage = Toolkit.getDefaultToolkit()。getImage(src); }

ここで、URLを取得します。nullの場合、画像の読み込みをスキップします。ではMyImageView、画像参照がURLの場合にのみ、このコードを実行する必要があります。以下は、画像ソースをテストするために追加できるメソッドです。

private boolean isURL()String src =(String)fElement.getAttributes()。getAttribute(HTML.Attribute.SRC); src.toLowerCase()。startsWith( "file")を返します  

基本的に、aの形式で画像への参照を取得し、ローカル画像用のファイルとリモート画像用のhttpStringの2種類のURLのいずれかで始まるかどうかをテストします。元のクラスの作成者であるJensAlfkeは、クラスのグローバル変数を使用するため、パラメーターを関数に渡す必要はありません。ここで、グローバル変数はです。javax.swing.text.html.ImageViewfElement

と言うコードを書くことはできますが、相対パスのelseステートメントに何を入れますか?それは非常に簡単です-アプリケーションで通常行うように画像をロードするだけです:if (isURL()) { }

else {String src =(String)fElement.getAttributes()。getAttribute(HTML.Attribute.SRC); fImage = Toolkit.getDefaultToolkit()。createImage(src); }

ここには本当の魔法はありませんが、1つの落とし穴があります。このcreateImage(src)関数は、画像のすべてのピクセルが入力される前に戻ることができます。その場合、壊れた画像が表示されます。この問題を解決するには、画像のピクセルが完全に入力されるまで待つだけです。私の最初の傾向は、を使用しMediaTrackerて画像の準備ができたことを検出することでしたが、MediaTrackerのコンストラクターでは、画像をパラメーターとしてレンダリングするコンポーネントが必要です。そこでもう一度、Jim Grahamからコードを借りてjava.awt.MediaTracker、問題を回避するための独自のメソッドを作成しました。

private void waitForImage()throws InterruptedException {int w = fImage.getWidth(this); int h = fImage.getHeight(this); while(true)}

この方法は、基本的に同じ仕事ないMediaTrackerwaitForID(int id)方法を、しかし、親コンポーネントを必要としません。このメソッドの呼び出しは、イメージが作成された直後に行うことができます。

続行する前に言及しなければならない小さな問題があります。これは、サブクラスに不可能であったImageViewからjavax.swing.text.html、私はと呼ばれる自分のクラス、作成するために、ファイル全体をコピーして、パッケージMyImageView私はパッケージに入れていません、。元のImageViewコードでは、画像が存在しないか遅延しているために表示できない場合、javax.swing.text.html.iconsパッケージからデフォルトの壊れた画像が読み込まれます。壊れた画像をロードするために、クラスはクラスのgetResourceAsStream(String name)メソッドを使用しClassます。実際のコードは次のようになります。

 InputStreamリソース= HTMLEditorKit.class.getResourceAsStream(MISSING_IMAGE_SRC); 

ここで、MISSING_IMAGE_SRCパラメーターはStringコンテンツ付きです。

 MISSING_IMAGE_SRC = "icons" + System.getProperty( "file.separator"、 "/")+ "image-failed.gif"; 

次のImageViewソースコードからの抜粋はgetResourceAsStream(String name)、壊れたイメージをロードする方法を使用するSunの理由を説明しています。

/ *リソースをバイト配列にコピーします。これが必要なのは、いくつかのブラウザがClass.getResourceをセキュリティリスクと見なしているためです。これは、追加のクラスをロードするために使用できるためです。* Class.getResourceAsStreamは、画像に変換できる生の*バイトを返すだけです。* /

If you haven't skipped through this section yet (I know, it's pretty nitty-gritty!), let me explain why I mention it. If you aren't aware of this behavior, you won't understand why broken images are not displayed correctly, and won't be able to fix the problem in your own code. To fix the problem, you must load your own images. I chose to continue using the same method, but it's not really necessary. The above warning is for browsers containing applets, which have security considerations that limit disk access (unless signed, of course). In any case, this article was intended for use with an application, so using an alternate image-loading method should not be a concern.

When a call to getResourceAsStream(String name) is made, you can include a relative path to the image, as illustrated above. In the above code, the broken image will always be loaded from the specified path relative to the HTMLEditorKit class. For example, since the HTMLEditorKit class is located in javax.swing.text.html, it will attempt to load the broken image image-failed.gif from javax.swing.text.html.icons. This also applies to simple directories; the classes do not have to be in packages. Lastly, since HTMLEditorKit is package protected, you do not have access to its getResourceAsStream(String name) method. Instead, you can use the MyImageView class and put your broken images in an icons subdirectory. The code line will look like this:

 InputStream resource = MyImageView.class.getResourceAsStream(MISSING_IMAGE_SRC); 

If you choose to use an implementation similar to mine, you will have to create your own icons. You can still use the icons bundled with Sun's JDK, but that requires changing the location of the resource to use an absolute path instead of a relative path. The absolute path is:

javax.swing.text.html.icons.imagename.gif 

To learn about using getResourceStream(String name), see the Javadoc information for the Class class; a link is provided in Resources.

This article is almost entirely about accommodating relative paths -- but what are they relative to? So far, if you use the code I have supplied, you will only be able to use paths relative to where you started the application. This is great if all your images are always located in those paths, but that is not always the case. I won't go into great detail on how to fix this problem, because it can be fixed easily. You can either set an application global variable somewhere in your application or set a system variable. In MyImageView, before loading the image, you concatenate the relative path to the image and the absolute path obtained from the global variable. If that doesn't make sense, look for the processSrcPath() method in the final source code for MyImageView.

At last, MyImageView is complete. However, you must figure out how to tell JEditorPane to use MyImageView instead of javax.swing.text.html.ImageView. The JEditorPane can support three text formats: plain, RTF, and HTML. If JEditorPane is displaying HTML, BasicHTML -- a subclass of TextUI -- is used to render the HTML. BasicHTML uses JEditorPane's HTMLEditorKit to create the View. The HTMLEditorKit contains a method called getViewFactory(), which returns an instance of an inner class called HTMLFactory. The HTMLFactory contains a method called create(Element elem), which returns a View according to the tag type. Specifically, if the tag is an IMG tag, it returns an instance of ImageView. To return an instance of MyImageView, you can create your own EditorKit called MyHTMLEditorKit、サブクラスHTMLEditorKit。の内部に、をサブクラス化MyHTMLEditorKitする、という新しい内部クラスを作成します。その内部クラスでは、次のような独自のメソッドを作成できます。MyHTMLFactoryHTMLFactorycreate(Element elem)

public View create(Element elem){Object o = elem.getAttributes()。getAttribute(StyleConstants.NameAttribute); if(o instanceof HTML.Tag){HTML.Tag kind =(HTML.Tag)o; if(kind == HTML.Tag.IMG)return new MyImageView(elem); } return super.create(elem); }

あとは、JEditorPaneを使用するように設定するだけですMyHTMLEditorKit。コードは非常に単純です。