Programování

Tip Java 109: Zobrazujte obrázky pomocí JEditorPane

Můžete použít aktuální JEditorPane komponenta k zobrazení značek HTML, ale k provádění složitějších úkolů, JEditorPane potřebuje nějaké zlepšení. Nedávno jsem musel vytvořit aplikaci pro vytváření formulářů XML. Jednou nezbytnou součástí byl editor WYSIWYG HTML, který dokázal upravit obsah značek HTML uvnitř některých značek XML. JEditorPane byla zřejmá volba komponenty Java pro zobrazování značek HTML, protože tato funkce již byla v něm zabudována. Po vložení do značky HTML bohužel JEditorPane nelze zobrazit obrázky s relativními cestami. Například pokud byl následující obrázek s relativní cestou obsažen ve značce XML, nezobrazí se správně:

Naopak by fungovala absolutní cesta (za předpokladu, že daná cesta a obrázek skutečně existovaly):

V mé aplikaci byly obrázky vždy uloženy v podadresáři vzhledem k umístění souboru XML. Proto jsem vždycky chtěl použít relativní cestu. Tento článek vysvětlí, proč tento problém existuje a jak jej opravit.

Proč se to stalo?

Bližší pohled na konstruktory pro JEditorPane pomůže nám pochopit, proč nemůže zobrazit obrázky v relativních cestách.

  1. JEditorPane () vytváří nový JEditorPane.
  2. JEditorPane (adresa URL řetězce) vytváří JEditorPane na základě řetězce obsahujícího specifikaci URL.
  3. JEditorPane (typ řetězce, text řetězce) vytváří JEditorPane který byl inicializován na daný text.
  4. JEditorPane (URL initialPage) vytváří JEditorPane na základě zadané adresy URL pro vstup.

Druhý a čtvrtý konstruktor inicializují objekt s odkazem na vzdálený nebo místní soubor HTML. An HTMLDocument je uvnitř každého JEditorPanea jeho základna je nastavena na základ parametru konstruktoru URL. JEditorPanes vytvořen pomocí těchto konstruktorů zvládne relativní cesty, protože základna HTMLDocument kombinuje s relativní cestou k vytvoření absolutní cesty.

Pokud se použije první konstruktor, je třeba po vytvoření objektu vložit zobrazený text. Třetí konstruktor přijímá a Tětiva jako obsah, ale základna není inicializována. Protože jsem chtěl získat značku HTML ze značky XML a ne ze souboru, potřeboval jsem použít buď první, nebo třetí konstruktor.

Jak problém vyřešíme?

Než budu pokračovat, pojďme odhalit a vyřešit další menší problém. Nejviditelnější způsob vložení značek do JEditorPane je použít setText (text řetězce). Tato metoda však vyžaduje, abyste při každé změně zadali celé zobrazené označení. V ideálním případě by měla být nová značka vložena do stávajícího textu. K přidání nového označení můžete použít následující kód:

private void insertHTML (editor JEditorPane, řetězec html, int umístění) vyvolá IOException {// předpokládá, že editor je již nastaven na „text / html“ typ HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit (); Dokument doc = editor.getDocument (); Čtečka StringReader = nový StringReader (html); kit.read (čtenář, dokument, umístění); } 

Nyní k jádru věci: Jak to jde JEditorPane vykreslit HTML? Každý typ JEditorPane odkazy jak a Dokument a EditorKit. Když JEditorPane je nastaven na typ "text / html", obsahuje HTMLDocument, který obsahuje označení a HTMLEditorKit který určuje, které třídy vykreslí každou značku obsaženou ve značce. Konkrétně HTMLEditorKit třída obsahuje HTMLFactory vnitřní třída, jejíž create (Element elem) metoda ve skutečnosti zkoumá každou samostatnou značku. Zde je kód z této tovární třídy, který zpracovává značky obrázků:

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

Jak nyní vidíte, ImageView třída ve skutečnosti načte obrázek. Chcete-li zjistit umístění obrázku, getSourceURL () metoda se nazývá:

 soukromá URL getSourceURL () {String src = (String) fElement.getAttributes (). getAttribute (HTML.Attribute.SRC); if (src == null) vrátit null; URL reference = ((HTMLDocument) getDocument ()). getBase (); zkuste {URL u = nová URL (reference, src); návrat u; } catch (MalformedURLException e) {return null; }} 

Tady getSourceURL () metoda se pokusí vytvořit novou URL odkazující na obrázek pomocí HTMLDocument základna. Pokud má tato základna hodnotu null, je vrácena hodnota null a operace načítání obrázků se přeruší. Chcete toto chování přepsat.

V ideálním případě byste podtřídu ImageView třídy a přepsat inicializovat (prvek elem) metoda, kde se provádí načítání obrazu. Bohužel tato třída je balíček chráněn, takže musíte vytvořit zcela novou třídu. Nejjednodušší způsob, jak to udělat, je půjčit si a poté upravit kód z originálu ImageView třída. Říkejme tomu MyImageView.

Nejprve se podívejte na kód, který načetl obrázek. Následující text je převzat z inicializovat (prvek elem) metoda:

 URL src = getSourceURL (); if (src! = null) {Dictionary cache = (Dictionary) getDocument (). getProperty (IMAGE_CACHE_PROPERTY); if (cache! = null) fImage = (Image) cache.get (src); else fImage = Toolkit.getDefaultToolkit (). getImage (src); } 

Zde získáte adresu URL; pokud je null, přeskočíte načítání obrázku. v MyImageView, měli byste tento kód spustit, pouze pokud je vaším obrázkovým odkazem adresa URL. Následuje metoda, kterou můžete přidat k otestování zdroje obrázku:

 private boolean isURL () String src = (String) fElement.getAttributes (). getAttribute (HTML.Attribute.SRC); vrátit src.toLowerCase (). startsWith ("soubor") 

V zásadě získáte odkaz na obrázek ve formě a Tětiva a vyzkoušejte, zda začíná jedním ze dvou typů adres URL: soubor pro místní obrázky a http pro vzdálené obrázky. Jens Alfke, autor originálu javax.swing.text.html.ImageView třída, používá globální proměnné třídy, takže předávání parametrů funkcím je zbytečné. Zde je globální proměnná prvek.

Můžete napsat kód, který říká if (isURL ()) {}, ale co vložíte do příkazu else pro relativní cestu? Je to docela jednoduché - stačí načíst obrázek obvyklým způsobem v aplikaci:

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

Neexistuje žádné skutečné kouzlo, ale je tu jeden háček. The createImage (src) funkce se může vrátit před naplněním všech obrazových pixelů. Pokud k tomu dojde, zobrazí se nefunkční obrázek. Chcete-li problém vyřešit, stačí počkat, až se pixely obrázku zcela naplní. Můj první sklon byl používat MediaTracker zjistit, kdy byl obraz připraven, ale MediaTrackerKonstruktor vyžaduje komponentu, která obrázek vykresluje jako parametr. Takže jsem si znovu vypůjčil nějaký kód od Jima Grahama java.awt.MediaTracker a napsal vlastní metodu, jak problém obejít:

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

Tato metoda v podstatě dělá stejnou práci jako MediaTrackerje waitForID (int id) metoda, ale nevyžaduje nadřazenou komponentu. Volání této metody lze provést hned po vytvoření obrázku.

Než budu pokračovat, měl bych zmínit malý problém. Bylo nemožné podtřídu ImageView z javax.swing.text.html balíček, tak jsem zkopíroval celý soubor a vytvořil svou vlastní třídu s názvem MyImageView, který jsem nedal do balíčku. V originále ImageView kód, pokud obrázek nelze zobrazit, protože neexistuje nebo je zpožděn, načte výchozí poškozený obrázek z javax.swing.text.html.icons balík. K načtení rozbitého obrázku používá třída getResourceAsStream (název řetězce) metoda z Třída třída. Skutečný kód vypadá takto:

 Zdroj InputStream = HTMLEditorKit.class.getResourceAsStream (MISSING_IMAGE_SRC); 

Kde MISSING_IMAGE_SRC parametr je a Tětiva s obsahem:

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

Následující výňatek z ImageView zdrojový kód vysvětluje důvody Sunu pro používání getResourceAsStream (název řetězce) způsob načítání rozbitých obrázků.

 / * Zkopírujte prostředek do bajtového pole. To je * nezbytné, protože několik prohlížečů považuje * Class.getResource za bezpečnostní riziko, protože * může být použito k načtení dalších tříd. * Class.getResourceAsStream právě vrací surové * bajty, které můžeme převést na obrázek. * / 

Pokud jste tuto část ještě nepřeskočili (vím, je to dost hloupé!), Dovolte mi vysvětlit, proč to zmiňuji. Pokud si toho chování nejste vědomi, nepochopíte, proč se poškozené obrázky nezobrazují správně, a nebudete moci problém vyřešit ve svém vlastním kódu. Chcete-li problém vyřešit, musíte načíst vlastní obrázky. Rozhodl jsem se pokračovat v používání stejné metody, ale není to opravdu nutné. Výše uvedené varování se týká prohlížečů obsahujících applety, jejichž bezpečnostní aspekty omezují přístup k disku (samozřejmě pokud nejsou podepsány). V každém případě byl tento článek určen pro použití s ​​aplikací, takže použití alternativní metody načítání obrázků by nemělo být problémem.

Při volání na getResourceAsStream (název řetězce) je vytvořena, můžete do obrázku zahrnout relativní cestu, jak je znázorněno výše. Ve výše uvedeném kódu bude rozbitý obrázek vždy načten ze zadané cesty vzhledem k HTMLEditorKit třída. Například, protože HTMLEditorKit třída se nachází v javax.swing.text.html, pokusí se načíst poškozený obrázek image-failed.gif z javax.swing.text.html.icons. To platí také pro jednoduché adresáře; třídy nemusí být v balíčcích. Nakonec od té doby HTMLEditorKit je chráněn balíčkem, nemáte k němu přístup getResourceAsStream (název řetězce) metoda. Místo toho můžete použít MyImageView třídy a vložte své rozbité obrázky do podadresáře ikon. Řádek kódu bude vypadat takto:

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

Pokud se rozhodnete použít implementaci podobnou té mé, budete si muset vytvořit vlastní ikony. Stále můžete používat ikony dodávané s JDK od Sunu, ale to vyžaduje změnu umístění zdroje, aby se místo relativní cesty používala absolutní cesta. Absolutní cesta je:

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

Chcete-li se dozvědět o používání getResourceStream (název řetězce), viz informace Javadoc pro Třída třída; odkaz je uveden ve zdrojích.

Tento článek je téměř úplně o přizpůsobení relativních cest - ale k čemu jsou relativní? Pokud zatím použijete kód, který jsem zadal, budete moci používat pouze cesty relativně k místu, kde jste aplikaci spustili. To je skvělé, pokud jsou všechny vaše obrázky vždy umístěny v těchto cestách, ale není tomu tak vždy. Nebudu zacházet do podrobností, jak tento problém vyřešit, protože to lze snadno opravit. Můžete buď nastavit globální proměnnou aplikace někde ve své aplikaci, nebo nastavit systémovou proměnnou. v MyImageView, před načtením obrázku spojíte relativní cestu k obrázku a absolutní cestu získanou z globální proměnné. Pokud to nedává smysl, hledejte processSrcPath () metoda v konečném zdrojovém kódu pro MyImageView.

Nakonec, MyImageView je kompletní. Musíte však přijít na to, jak to říct JEditorPane použít MyImageView namísto javax.swing.text.html.ImageView. The JEditorPane může podporovat tři textové formáty: prostý, RTF a HTML. Li JEditorPane zobrazuje HTML, BasicHTML - podtřída TextUI - slouží k vykreslení HTML. BasicHTML používá JEditorPaneje HTMLEditorKit vytvořit Pohled. The HTMLEditorKit obsahuje metodu nazvanou getViewFactory (), který vrací instanci vnitřní třídy s názvem HTMLFactory. The HTMLFactory obsahuje metodu nazvanou create (Element elem), který vrací a Pohled podle typu značky. Konkrétně, pokud je značka IMG tag, vrátí instanci ImageView. Chcete-li vrátit instanci MyImageView, můžete si vytvořit svůj vlastní EditorKit volala MyHTMLEditorKit, které podtřídy HTMLEditorKit. Uvnitř vašeho MyHTMLEditorKit, vytvoříte novou vnitřní třídu s názvem MyHTMLFactory, které podtřídy HTMLFactory. V této vnitřní třídě si můžete vytvořit svůj vlastní create (Element elem) metoda, která vypadá asi takto:

 veřejné zobrazení 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); } vrátit super.create (elem); } 

Jediné, co nyní zbývá udělat, je nastavit JEditorPane použít MyHTMLEditorKit. Kód je docela jednoduchý: