Programmering

Java Tips 109: Vis bilder ved hjelp av JEditorPane

Du kan bruke strømmen JEditorPane komponent for å vise HTML-markering, men for å utføre mer kompliserte oppgaver, JEditorPane trenger noe forbedring. Nylig måtte jeg bygge et program for XML-formbygger. En nødvendig komponent var en WYSIWYG HTML-editor som kunne redigere HTML-markeringsinnholdet i noen av XML-kodene. JEditorPane var det åpenbare Java-komponentvalget for å vise HTML-markeringen, fordi funksjonaliteten allerede var innebygd i den. Dessverre, når du settes inn i HTML-markeringen, JEditorPane kunne ikke vise bilder med relative stier. For eksempel, hvis følgende bilde med en relativ bane var inneholdt i en XML-tag, ville det ikke vises riktig:

Motsatt ville en absolutt vei fungere (forutsatt at den gitte banen og bildet virkelig eksisterte):

I applikasjonen min ble bilder alltid lagret i en underkatalog i forhold til XML-filens plassering. Derfor ønsket jeg alltid å bruke en relativ vei. Denne artikkelen vil forklare hvorfor dette problemet eksisterer, og hvordan du løser det.

Hvorfor skjer dette?

Ser nærmere på konstruktørene for JEditorPane vil hjelpe oss å forstå hvorfor det ikke kan vise bilder i relative baner.

  1. JEditorPane () skaper et nytt JEditorPane.
  2. JEditorPane (streng url) skaper en JEditorPane basert på en streng som inneholder en URL-spesifikasjon.
  3. JEditorPane (strengtype, strengtekst) skaper en JEditorPane som er initialisert til den gitte teksten.
  4. JEditorPane (URL initialPage) skaper en JEditorPane basert på en spesifisert URL for inndata.

Den andre og fjerde konstruktøren initialiserer objektet med en referanse til en ekstern eller lokal HTML-fil. An HTMLDocument er inne i hver JEditorPane, og basen er satt til basen til URL-konstruktørparameteren. JEditorPanes opprettet ved hjelp av disse konstruktørene kan håndtere relative stier, fordi basen til HTMLDocument kombineres med den relative stien for å skape en absolutt sti.

Hvis den første konstruktøren brukes, må den viste teksten settes inn etter at objektet er opprettet. Den tredje konstruktøren aksepterer a String som innhold, men basen initialiseres ikke. Fordi jeg ønsket å få HTML-markeringen fra en XML-tag og ikke en fil, trengte jeg å bruke enten den første eller tredje konstruktøren.

Hvordan løser vi problemet?

La oss avdekke og løse et annet mindre problem før jeg fortsetter. Den mest åpenbare måten å sette inn markering i JEditorPane er å bruke setText (strengtekst). Imidlertid krever den metoden at du skriver inn hele markeringen som vises hver gang du gjør en endring. Ideelt sett bør de nye taggene settes inn i den eksisterende teksten. Du kan bruke følgende kode for å legge til den nye markeringen:

privat tomrom insertHTML (JEditorPane editor, String html, int location) kaster IOException {// antar at editor allerede er satt til "text / html" type HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit (); Dokument doc = editor.getDocument (); StringReader-leser = ny StringReader (html); kit.read (leser, doc, plassering); } 

Nå når vi kjernen i saken: Hvordan gjør det JEditorPane gjengi HTML? Hver type JEditorPane referanser både a Dokument og en EditorKit. Når JEditorPane er satt til å skrive "text / html", den inneholder en HTMLDocument, som inneholder markeringen og et HTMLEditorKit som bestemmer hvilke klasser som gjengir hver tag som er merket. Spesielt, den HTMLEditorKit klasse inneholder en HTMLFactory indre klasse hvis lage (Element elem) metoden faktisk undersøker hver enkelt tag. Her er koden fra den fabrikklassen, som håndterer bildekoder:

 annet hvis (kind == HTML.Tag.IMG) returnerer nytt ImageView (elem); 

Som du nå kan se, er ImageView klasse laster faktisk bildet. For å fastslå bildets beliggenhet, getSourceURL () metoden kalles:

 privat URL getSourceURL () {String src = (String) fElement.getAttributes (). getAttribute (HTML.Attribute.SRC); hvis (src == null) returnerer null; URL referanse = ((HTMLDocument) getDocument ()). getBase (); prøv {URL u = ny URL (referanse, src); returner deg; } catch (MalformedURLException e) {return null; }} 

Her, den getSourceURL () metoden prøver å opprette en ny URL for å referere til bildet ved hjelp av HTMLDocument utgangspunkt. Hvis basen er null, returneres null og bildelastingsoperasjonen avbrytes. Du vil overstyre den oppførselen.

Ideelt sett vil du underklasse ImageView klasse og overstyre initialisere (Elementelem) metode, hvor bildelastingen gjøres. Dessverre er den klassen det pakkebeskyttet, så du må opprette en helt ny klasse. Den enkleste måten å gjøre det på er å låne og endre koden fra originalen ImageView klasse. La oss kalle det MyImageView.

Se først på koden som lastet inn bildet. Følgende er hentet fra initialisere (Elementelem) metode:

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

Her får du nettadressen; hvis den er null, hopper du over bildelastingen. I MyImageView, bør du bare utføre denne koden hvis bildereferansen din er en URL. Følgende er en metode du kan legge til for å teste bildekilden:

 privat boolsk isURL () String src = (String) fElement.getAttributes (). getAttribute (HTML.Attribute.SRC); returner src.toLowerCase (). startsWith ("fil") 

I utgangspunktet får du referansen til bildet i form av en String og test for å se om det begynner med en av de to typene URL: fil for lokale bilder og http for eksterne bilder. Jens Alfke, forfatter av originalen javax.swing.text.html.ImageView klasse, bruker klasse globale variabler, så overføring av parametere til funksjoner er unødvendig. Her er den globale variabelen FELEMENT.

Du kan skrive kode som sier hvis (isURL ()) {}, men hva legger du i den andre uttalelsen for en relativ vei? Det er ganske enkelt - bare last inn bildet som du normalt ville gjort i et program:

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

Det er ingen virkelig magi her, men det er en fangst. De createImage (src) funksjonen kan returnere før alle bildepikslene er fylt ut. Hvis det skjer, vises et ødelagt bilde. For å løse problemet kan du bare vente til bildets piksler er fullstendig fylt ut. Min første tilbøyelighet var å bruke MediaTracker for å oppdage når bildet var klart, men MediaTrackersin konstruktør krever at komponenten gjengir bildet som en parameter. Så igjen, jeg lånte litt kode fra Jim Graham java.awt.MediaTracker og skrev min egen metode for å omgå problemet:

 privat tomrom waitForImage () kaster InterruptedException {int w = fImage.getWidth (dette); int h = fImage.getHeight (dette); mens (sant)} 

Denne metoden gjør i utgangspunktet den samme jobben som MediaTrackers waitForID (int id) metode, men krever ikke en overordnet komponent. Et kall til denne metoden kan ringes like etter at bildet er opprettet.

Det er et lite problem som jeg bør nevne før jeg fortsetter. Det var umulig å underklasse ImageView fra javax.swing.text.html pakke, så jeg kopierte hele filen for å lage min egen klasse, kalt MyImageView, som jeg ikke har lagt i en pakke. I originalen ImageView kode, hvis et bilde ikke kan vises fordi det ikke eksisterer eller er forsinket, lastes det et standard ødelagt bilde fra javax.swing.text.html.icons pakke. For å laste det ødelagte bildet bruker klassen getResourceAsStream (strengnavn) metoden fra Klasse klasse. Den faktiske koden ser slik ut:

 InputStream-ressurs = HTMLEditorKit.class.getResourceAsStream (MISSING_IMAGE_SRC); 

hvor i MISSING_IMAGE_SRC parameter er en String med innhold:

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

Følgende utdrag fra ImageView kildekoden forklarer Suns begrunnelse for å bruke getResourceAsStream (strengnavn) metode for å laste det ødelagte bildet / bildene.

 / * Kopier ressurs til et byte-utvalg. Dette er * nødvendig fordi flere nettlesere anser * Class.getResource som en sikkerhetsrisiko fordi den * kan brukes til å laste inn flere klasser. * Class.getResourceAsStream returnerer bare rå * byte, som vi kan konvertere til et bilde. * / 

Hvis du ikke har gått igjennom denne delen ennå (jeg vet, den er ganske snuskig!), La meg forklare hvorfor jeg nevner det. Hvis du ikke er klar over denne oppførselen, vil du ikke forstå hvorfor ødelagte bilder ikke vises riktig, og du vil ikke kunne løse problemet i din egen kode. For å løse problemet må du laste inn dine egne bilder. Jeg valgte å fortsette å bruke den samme metoden, men det er egentlig ikke nødvendig. Ovennevnte advarsel er for nettlesere som inneholder applets, som har sikkerhetshensyn som begrenser disktilgang (med mindre det er selvfølgelig signert). I alle fall var denne artikkelen ment for bruk sammen med et program, så bruk av en alternativ bildelastingsmetode burde ikke være en bekymring.

Når en samtale til getResourceAsStream (strengnavn) er laget, kan du inkludere en relativ bane til bildet, som illustrert ovenfor. I koden ovenfor vil det ødelagte bildet alltid lastes fra den angitte banen i forhold til HTMLEditorKit klasse. For eksempel siden HTMLEditorKit klasse ligger i javax.swing.text.html, vil den prøve å laste det ødelagte bildet image-failed.gif fra javax.swing.text.html.icons. Dette gjelder også enkle kataloger; klassene trenger ikke å være i pakker. Til slutt, siden HTMLEditorKit er pakkebeskyttet, har du ikke tilgang til den getResourceAsStream (strengnavn) metode. I stedet kan du bruke MyImageView klasse og legg de ødelagte bildene dine i en underkatalog for ikoner. Kodelinjen vil se slik ut:

 InputStream-ressurs = MyImageView.class.getResourceAsStream (MISSING_IMAGE_SRC); 

Hvis du velger å bruke en implementering som ligner på min, må du lage dine egne ikoner. Du kan fortsatt bruke ikonene som følger med Suns JDK, men det krever at du lokerer ressursen for å bruke en absolutt bane i stedet for en relativ bane. Den absolutte banen er:

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

For å lære om bruk getResourceStream (strengnavn), se Javadoc-informasjonen for Klasse klasse; en lenke er gitt i Ressurser.

Denne artikkelen handler nesten utelukkende om å imøtekomme relative stier - men hva er de i forhold til? Så langt, hvis du bruker koden jeg har oppgitt, vil du bare kunne bruke stier i forhold til der du startet applikasjonen. Dette er flott hvis alle bildene dine alltid ligger i disse banene, men det er ikke alltid tilfelle. Jeg vil ikke gå i detalj om hvordan jeg kan løse dette problemet, fordi det enkelt kan løses. Du kan enten angi en applikasjons global variabel et sted i applikasjonen eller angi en systemvariabel. I MyImageView, før du laster inn bildet, sammenkobler du den relative banen til bildet og den absolutte banen du får fra den globale variabelen. Hvis det ikke gir mening, se etter processSrcPath () metode i den endelige kildekoden for MyImageView.

Endelig, MyImageView er ferdig. Du må imidlertid finne ut hvordan du skal fortelle det JEditorPane å bruke MyImageView i stedet for javax.swing.text.html.ImageView. De JEditorPane kan støtte tre tekstformater: vanlig, RTF og HTML. Hvis JEditorPane viser HTML, BasicHTML - en underklasse av TextUI - brukes til å gjengi HTML. BasicHTML bruker JEditorPanes HTMLEditorKit å lage Utsikt. De HTMLEditorKit inneholder en metode som heter getViewFactory (), som returnerer en forekomst av en indre klasse kalt HTMLFactory. De HTMLFactory inneholder en metode som heter lage (Element elem), som returnerer a Utsikt i henhold til merketypen. Spesielt hvis koden er en IMG koden, returnerer den en forekomst av ImageView. Å returnere en forekomst av MyImageView, kan du lage dine egne EditorKit kalt MyHTMLEditorKit, som underklasser HTMLEditorKit. Inne i MyHTMLEditorKit, oppretter du en ny indre klasse kalt MyHTMLFactory, som underklasser HTMLFactory. I den indre klassen kan du lage din egen lage (Element elem) metoden, som ser omtrent slik ut:

 public View create (Element elem) {Object o = elem.getAttributes (). getAttribute (StyleConstants.NameAttribute); hvis (o forekomst av HTML.Tag) {HTML.Tag type = (HTML.Tag) o; hvis (kind == HTML.Tag.IMG) returnerer ny MyImageView (elem); } returner super.create (elem); } 

Det eneste som er igjen å gjøre er å stille inn JEditorPane å bruke MyHTMLEditorKit. Koden er ganske enkel:

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