Programovanie

Java Tip 109: Zobrazujte obrázky pomocou JEditorPane

Môžete použiť prúd JEditorPane komponent na zobrazenie značiek HTML, ale na vykonávanie zložitejších úloh, JEditorPane potrebuje nejaké vylepšenie. Nedávno som musel zostaviť aplikáciu na vytváranie formulárov XML. Jedným z nevyhnutných komponentov bol editor WYSIWYG HTML, ktorý dokázal upravovať obsah značkovania HTML vo vnútri niektorých značiek XML. JEditorPane bola zrejmá voľba komponentu Java na zobrazovanie značiek HTML, pretože táto funkcia už bola v ňom zabudovaná. Po vložení do značky HTML, JEditorPane nemohol zobraziť obrázky s relatívnymi cestami. Ak by bol napríklad nasledujúci obrázok s relatívnou cestou obsiahnutý v značke XML, nezobrazil by sa správne:

Naopak, absolútna cesta by fungovala (za predpokladu, že daná cesta a obraz skutočne existovali):

V mojej aplikácii boli obrázky vždy uložené v podadresári relatívnom k ​​umiestneniu súboru XML. Preto som vždy chcel použiť relatívnu cestu. V tomto článku bude vysvetlené, prečo tento problém existuje a ako ho vyriešiť.

Prečo sa to stalo?

Keď sa pozrieme bližšie na konštruktérov pre JEditorPane pomôže nám pochopiť, prečo nemôže zobrazovať obrázky na relatívnych cestách.

  1. JEditorPane () vytvára nový JEditorPane.
  2. JEditorPane (adresa URL reťazca) vytvára a JEditorPane na základe reťazca obsahujúceho špecifikáciu adresy URL.
  3. JEditorPane (typ reťazca, text reťazca) vytvára a JEditorPane ktorý bol inicializovaný pre daný text.
  4. JEditorPane (URL initialPage) vytvára a JEditorPane na základe zadanej adresy URL pre vstup.

Druhý a štvrtý konštruktér inicializujú objekt s odkazom na vzdialený alebo lokálny súbor HTML. An HTMLDocument je vo vnútri každého JEditorPanea jeho základňa je nastavená na základňu parametra konštruktora adresy URL. JEditorPanes vytvorené pomocou týchto konštruktorov zvládne relatívne cesty, pretože základňa HTMLDocument kombinuje s relatívnou cestou a vytvára absolútnu cestu.

Ak sa použije prvý konštruktor, musí sa po vytvorení objektu vložiť zobrazený text. Tretí konštruktér prijíma a String ako obsah, ale základňa nie je inicializovaná. Pretože som chcel získať značky HTML zo značky XML a nie zo súboru, musel som použiť prvý alebo tretí konštruktor.

Ako odstránime problém?

Než budem pokračovať, poďme odhaliť a vyriešiť ďalší menší problém. Najzrejmejší spôsob vloženia značiek do súboru JEditorPane je použitie setText (text reťazca). Táto metóda však vyžaduje, aby ste pri každej zmene zadali celé zobrazené označenie. V ideálnom prípade by mali byť nové značky vložené do existujúceho textu. Na pridanie nového označenia môžete použiť nasledujúci kód:

private void insertHTML (editor JEditorPane, reťazec html, int umiestnenie) vyvolá IOException {// predpokladá, že editor je už nastavený na „text / html“ typ HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit (); Dokument doc = editor.getDocument (); Čítačka StringReader = nový StringReader (html); kit.read (čitateľ, dokument, umiestnenie); } 

Teraz sa dostaneme k jadru veci: Ako to ide JEditorPane vykresliť HTML? Každý typ JEditorPane referencie obidva a Dokument a an EditorKit. Kedy JEditorPane je nastavený na typ "text / html", obsahuje HTMLDocument, ktorý obsahuje označenie a HTMLEditorKit ktorá určuje, ktoré triedy vykresľujú každú značku obsiahnutú v značke. Konkrétne HTMLEditorKit trieda obsahuje HTMLFactory vnútorná trieda ktorého vytvoriť (prvok elem) metóda vlastne skúma každú samostatnú značku. Tu je kód z tejto továrenskej triedy, ktorý narába so značkami obrázkov:

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

Ako teraz vidíte, ImageView trieda skutočne načíta obrázok. Ak chcete zistiť polohu obrázka, kliknite na ikonu getSourceURL () metóda sa volá:

 súkromná URL getSourceURL () {String src = (String) fElement.getAttributes (). getAttribute (HTML.Attribute.SRC); if (src == null) return null; Odkaz na URL = ((HTMLDocument) getDocument ()). getBase (); skúsiť {URL u = nová URL (referencia, src); návrat u; } catch (MalformedURLException e) {return null; }} 

Tu je getSourceURL () metóda sa pokúsi vytvoriť novú URL na odkazovanie na obrázok pomocou HTMLDocument základňa. Ak má táto základňa hodnotu null, vráti sa hodnota null a operácia načítania obrazu sa preruší. Chcete prepísať toto správanie.

V ideálnom prípade by ste podtriedu ImageView triedy a prepísať inicializovať (prvok elem) metóda, pri ktorej sa načítanie obrazu vykonáva. Bohužiaľ, táto trieda je balík chránený, takže musíte vytvoriť úplne novú triedu. Najjednoduchší spôsob, ako to urobiť, je požičať si a potom upraviť kód z originálu ImageView trieda. Nazvime to MyImageView.

Najskôr sa pozrite na kód, ktorý načítal obrázok. Nasledujúce informácie sú prevzaté z inicializovať (prvok elem) metóda:

 URL src = getSourceURL (); if (src! = null) {cache slovníka = (slovník) getDocument (). getProperty (IMAGE_CACHE_PROPERTY); if (cache! = null) fImage = (Obrázok) cache.get (src); else fImage = Toolkit.getDefaultToolkit (). getImage (src); } 

Tu získate adresu URL; ak je nulový, preskočíte načítanie obrázka. V MyImageView, tento kód by ste mali vykonať, iba ak je vaším odkazom na obrázok adresa URL. Nasleduje metóda, ktorú môžete pridať na otestovanie zdroja obrázka:

 private boolean isURL () String src = (String) fElement.getAttributes (). getAttribute (HTML.Attribute.SRC); vrátiť src.toLowerCase (). startsWith ("súbor") 

V zásade získate odkaz na obrázok vo forme a String a otestujte, či sa začína jedným z dvoch typov adries URL: spis pre miestne obrázky a http pre vzdialené obrázky. Jens Alfke, autor originálu javax.swing.text.html.ImageView trieda, používa globálne premenné triedy, takže odovzdávanie parametrov funkciám je zbytočné. Tu je globálna premenná prvok.

Môžete napísať kód, ktorý hovorí if (isURL ()) {}, ale čo vložíte do výrazu else pre relatívnu cestu? Je to celkom jednoduché - stačí načítať obrázok obvyklým spôsobom v aplikácii:

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

Neexistuje tu žiadna skutočná mágia, ale má to jeden háčik. The createImage (src) funkcia sa môže vrátiť skôr, ako sa naplnia všetky pixely obrázka. Ak sa to stane, zobrazí sa nefunkčný obrázok. Na vyriešenie problému stačí počkať, kým sa pixely obrázka úplne vyplnia. Mojou prvou záľubou bolo použiť MediaTracker zistiť, kedy bol obrázok pripravený, ale MediaTrackerKonštruktor vyžaduje, aby komponent vykresľoval obrázok ako parameter. Opäť som si teda požičal nejaký kód od Jima Grahama java.awt.MediaTracker a napísal svoju vlastnú metódu na obídenie problému:

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

Táto metóda v zásade vykonáva rovnakú prácu ako MediaTrackerje waitForID (int id) metóda, ale nevyžaduje nadradený komponent. Túto metódu je možné zavolať ihneď po vytvorení obrázka.

Než budem pokračovať, mal by som spomenúť malý problém. Bolo nemožné podtriedu ImageView z javax.swing.text.html balíček, tak som skopíroval celý súbor, aby som vytvoril svoju vlastnú triedu, tzv MyImageView, ktoré som nevložil do obalu. V origináli ImageView kód, ak obrázok nie je možné zobraziť, pretože neexistuje alebo je oneskorený, načíta predvolený nefunkčný obrázok z priečinka javax.swing.text.html.icons balíček. Na načítanie rozbitého obrázka používa trieda getResourceAsStream (názov reťazca) metóda z Trieda trieda. Skutočný kód vyzerá takto:

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

kde MISSING_IMAGE_SRC parameter je a String s obsahom:

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

Nasledujúci výňatok z ImageView zdrojový kód vysvetľuje dôvody spoločnosti Sun na používanie súboru getResourceAsStream (názov reťazca) spôsob načítania rozbitých obrázkov.

 / * Skopírujte prostriedok do bajtového poľa. To je * nevyhnutné, pretože niekoľko prehľadávačov považuje * Class.getResource za bezpečnostné riziko, pretože * sa dá použiť na načítanie ďalších tried. * Class.getResourceAsStream vráti iba surové * bajty, ktoré môžeme previesť na obrázok. * / 

Ak ste túto časť ešte nepreskočili (viem, je to dosť drsné!), Dovoľte mi vysvetliť, prečo to spomínam. Ak si nie ste vedomí tohto správania, nebudete rozumieť tomu, prečo sa nefunkčné obrázky zobrazujú správne, a nebudete môcť problém vyriešiť vo svojom vlastnom kóde. Ak chcete problém vyriešiť, musíte načítať svoje vlastné obrázky. Rozhodol som sa pokračovať v používaní rovnakej metódy, ale nie je to skutočne nevyhnutné. Vyššie uvedené varovanie sa týka prehliadačov obsahujúcich applety, ktoré majú bezpečnostné hľadiská obmedzujúce prístup na disk (samozrejme, pokiaľ nie sú podpísané). V každom prípade bol tento článok určený na použitie s aplikáciou, takže použitie alternatívnej metódy načítania obrázkov by nemalo spôsobovať obavy.

Pri volaní na getResourceAsStream (názov reťazca) je vytvorená, môžete zahrnúť relatívnu cestu k obrázku, ako je znázornené vyššie. Vo vyššie uvedenom kóde sa rozbitý obrázok vždy načíta zo zadanej cesty vzhľadom na HTMLEditorKit trieda. Napríklad od HTMLEditorKit trieda sa nachádza v javax.swing.text.html, pokúsi sa načítať poškodený obrázok image-failed.gif od javax.swing.text.html.icons. To platí aj pre jednoduché adresáre; triedy nemusia byť v balíkoch. Na záver od HTMLEditorKit je chránený balíkom, nemáte k nemu prístup getResourceAsStream (názov reťazca) metóda. Namiesto toho môžete použiť MyImageView triedy a rozbité obrázky vložte do podadresára ikon. Riadok kódu bude vyzerať takto:

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

Ak sa rozhodnete použiť podobnú implementáciu ako ja, budete si musieť vytvoriť vlastné ikony. Stále môžete používať ikony dodávané s JDK spoločnosti Sun, ale to si vyžaduje zmenu umiestnenia zdroja, aby ste namiesto relatívnej cesty mohli používať absolútnu cestu. Absolútna cesta je:

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

Ak sa chcete dozvedieť viac o používaní getResourceStream (názov reťazca), pozrite si informácie o Javadoc pre Trieda trieda; odkaz je uvedený v zdrojoch.

Tento článok je takmer výlučne o prispôsobení sa relatívnym cestám - k čomu však patria? Pokiaľ zatiaľ použijete kód, ktorý som uviedol, budete môcť používať iba cesty k miestu, kde ste aplikáciu spustili. To je skvelé, ak sú všetky vaše obrázky vždy umiestnené v týchto cestách, ale nie je to tak vždy. Nebudem sa veľmi venovať tomu, ako tento problém vyriešiť, pretože sa dá ľahko vyriešiť. Globálnu premennú aplikácie môžete nastaviť niekde v aplikácii alebo môžete nastaviť systémovú premennú. V MyImageView, pred načítaním obrázka zreťazíte relatívnu cestu k obrázku a absolútnu cestu získanú z globálnej premennej. Ak to nemá zmysel, vyhľadajte processSrcPath () metóda v konečnom zdrojovom kóde pre MyImageView.

Nakoniec, MyImageView je kompletný. Musíte však prísť na to, ako to povedať JEditorPane použit MyImageView namiesto javax.swing.text.html.ImageView. The JEditorPane môže podporovať tri textové formáty: obyčajný, RTF a HTML. Ak JEditorPane zobrazuje HTML, BasicHTML - podtrieda TextUI - sa používa na vykreslenie HTML. BasicHTML používa JEditorPaneje HTMLEditorKit vytvoriť vyhliadka. The HTMLEditorKit obsahuje metódu nazvanú getViewFactory (), ktorá vracia inštanciu vnútornej triedy s názvom HTMLFactory. The HTMLFactory obsahuje metódu nazvanú vytvoriť (prvok elem), ktorá vracia a vyhliadka podľa typu štítku. Konkrétne, ak je značka znakom IMG značka, vráti inštanciu ImageView. Vrátiť inštanciu MyImageView, môžete si vytvoriť svoj vlastný EditorKit zavolal MyHTMLEditorKit, ktoré podtriedy HTMLEditorKit. Vo vnútri vášho MyHTMLEditorKit, vytvoríte novú vnútornú triedu s názvom MyHTMLFactory, ktoré podtriedy HTMLFactory. V tejto vnútornej triede si môžete vytvoriť svoj vlastný vytvoriť (prvok elem) metóda, ktorá vyzerá asi takto:

 verejné zobrazenie 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); } 

Jediné, čo teraz zostáva, je nastaviť JEditorPane použit MyHTMLEditorKit. Kód je celkom jednoduchý:

$config[zx-auto] not found$config[zx-overlay] not found