Programmering

Java Tip 109: Vis billeder ved hjælp af JEditorPane

Du kan bruge strømmen JEditorPane komponent til at vise HTML-markering, men for at udføre mere komplicerede opgaver, JEditorPane har brug for forbedring. For nylig var jeg nødt til at opbygge en XML-formbyggerapplikation. En nødvendig komponent var en WYSIWYG HTML-editor, der kunne redigere HTML-markupindholdet inde i nogle af XML-tags. JEditorPane var det åbenlyse valg af Java-komponent til visning af HTML-markering, fordi denne funktionalitet allerede var indbygget i den. Desværre, når de indsættes i HTML-markeringen, JEditorPane kunne ikke vise billeder med relative stier. For eksempel, hvis følgende billede med en relativ sti var indeholdt i et XML-tag, ville det ikke blive vist korrekt:

Omvendt ville en absolut sti fungere (forudsat at den givne sti og billedet virkelig eksisterede):

I min applikation blev billeder altid gemt i en underkatalog i forhold til XML-filens placering. Derfor har jeg altid ønsket at bruge en relativ sti. Denne artikel forklarer, hvorfor dette problem findes, og hvordan man løser det.

Hvorfor sker dette?

Ser vi nærmere på konstruktørerne til JEditorPane vil hjælpe os med at forstå, hvorfor det ikke kan vise billeder i relative stier.

  1. JEditorPane () skaber et nyt JEditorPane.
  2. JEditorPane (streng url) skaber en JEditorPane baseret på en streng, der indeholder en URL-specifikation.
  3. JEditorPane (streng type, streng tekst) skaber en JEditorPane der er initialiseret til den givne tekst.
  4. JEditorPane (URL initialPage) skaber en JEditorPane baseret på en specificeret URL til input.

Den anden og fjerde konstruktør initialiserer objektet med en henvisning til en ekstern eller lokal HTML-fil. En HTMLDocument er inde i hver JEditorPane, og dens base er indstillet til basen af ​​parameteren URL-konstruktør. JEditorPanes oprettet ved hjælp af disse konstruktører kan håndtere relative stier, fordi bunden af HTMLDocument kombineres med den relative sti for at skabe en absolut sti.

Hvis den første konstruktør bruges, skal den viste tekst indsættes, efter at objektet er oprettet. Den tredje konstruktør accepterer en Snor som indhold, men basen initialiseres ikke. Fordi jeg ønskede at få HTML-markeringen fra et XML-tag og ikke en fil, havde jeg brug for enten den første eller tredje konstruktør.

Hvordan løser vi problemet?

Lad os afsløre og løse et andet mindre problem, før jeg fortsætter. Den mest åbenlyse måde at indsætte markup i JEditorPane er at bruge setText (strengtekst). Denne metode kræver dog, at du indtaster hele den viste markering, hver gang du foretager en ændring. Ideelt set skal de nye tag (er) indsættes i den eksisterende tekst. Du kan bruge følgende kode til at tilføje den nye markering:

privat ugyldigt insertHTML (JEditorPane editor, String html, int placering) kaster IOException {// antager, at editor allerede er indstillet til "text / html" type HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit (); Dokument doc = editor.getDocument (); StringReader-læser = ny StringReader (html); kit.read (læser, doc, placering); } 

Nu når vi kernen i sagen: Hvordan gør det? JEditorPane gengive HTML? Hver type JEditorPane referencer både a Dokument og en EditorKit. Hvornår JEditorPane er indstillet til at skrive "tekst / html", den indeholder en HTMLDocument, som indeholder markeringen og en HTMLEditorKit der bestemmer, hvilke klasser der gengiver hvert tag indeholdt i markeringen. Specifikt den HTMLEditorKit klasse indeholder en HTMLFabrik indre klasse, hvis oprette (Element elem) metode undersøger faktisk hvert enkelt tag. Her er koden fra den fabriksklasse, der håndterer billedkoder:

 ellers hvis (kind == HTML.Tag.IMG) returnerer nyt ImageView (elem); 

Som du nu kan se, er ImageView klasse faktisk indlæser billedet. For at fastslå billedets placering skal getSourceURL () metoden kaldes:

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

Her, den getSourceURL () metoden forsøger at oprette en ny URL til at henvise til billedet ved hjælp af HTMLDocument grundlag. Hvis basen er null, returneres null, og billedindlæsningsoperationen afbrydes. Du vil tilsidesætte den adfærd.

Ideelt set ville du underklasse ImageView klasse og tilsidesætte initialisere (Elementelem) metode, hvor billedindlæsningen sker. Desværre er den klasse det pakkebeskyttet, så du skal oprette en helt ny klasse. Den nemmeste måde at gøre det på er at låne og derefter ændre koden fra originalen ImageView klasse. Lad os kalde det MyImageView.

Se først på koden, der indlæste billedet. Følgende er taget fra initialisere (Elementelem) metode:

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

Her får du URL'en; hvis det er null, springer du billedindlæsningen over. I MyImageView, skal du kun udføre denne kode, hvis din billedreference er en URL. Følgende er en metode, du kan tilføje for at teste billedkilden:

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

Dybest set får du henvisningen til billedet i form af en Snor og test for at se, om det begynder med en af ​​de to typer URL: fil til lokale billeder og http til fjernbilleder. Jens Alfke, forfatter af originalen javax.swing.text.html.ImageView klasse, bruger klasse globale variabler, så det er unødvendigt at overføre parametre til funktioner. Her er den globale variabel element.

Du kan skrive kode, der siger hvis (isURL ()) {}, men hvad lægger du i den ellers erklæring om en relativ sti? Det er ret simpelt - bare indlæse billedet som normalt i en applikation:

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

Der er ingen reel magi her, men der er en fangst. Det createImage (src) funktionen kan vende tilbage, før alle billedets pixels er udfyldt. Hvis det sker, vises et ødelagt billede. For at løse problemet kan du bare vente, indtil billedets pixels er fuldstændigt udfyldt. Min første tilbøjelighed var at bruge MediaTracker for at opdage, hvornår billedet var klar, men MediaTrackerkonstruktør kræver, at komponenten gengiver billedet som en parameter. Så endnu en gang lånte jeg noget kode fra Jim Grahams java.awt.MediaTracker og skrev min egen metode til at omgå problemet:

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

Denne metode gør stort set det samme job som MediaTracker's waitForID (int id) metode, men kræver ikke en overordnet komponent. Et opkald til denne metode kan foretages lige efter billedet er oprettet.

Der er et lille problem, som jeg skal nævne, før jeg fortsætter. Det var umuligt at underklasse ImageView fra javax.swing.text.html pakke, så jeg kopierede hele filen for at oprette min egen klasse, kaldet MyImageView, som jeg ikke har lagt i en pakke. I originalen ImageView kode, hvis et billede ikke kan vises, fordi det ikke findes eller er forsinket, indlæser det et standardbrudt billede fra javax.swing.text.html.icons pakke. For at indlæse det ødelagte billede bruger klassen getResourceAsStream (strengnavn) metode fra Klasse klasse. Den aktuelle kode ser sådan ud:

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

hvor er MISSING_IMAGE_SRC parameter er en Snor med indhold:

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

Følgende uddrag fra ImageView kildekode forklarer Suns begrundelse for at bruge getResourceAsStream (strengnavn) metode til indlæsning af det eller de ødelagte billeder.

 / * Kopier ressource til et byte-array. Dette er * nødvendigt, fordi flere browsere betragter * Class.getResource som en sikkerhedsrisiko, fordi den * kan bruges til at indlæse yderligere klasser. * Class.getResourceAsStream returnerer bare rå * byte, som vi kan konvertere til et billede. * / 

Hvis du ikke har sprunget igennem dette afsnit endnu (jeg ved, det er ret snavset!), Så lad mig forklare, hvorfor jeg nævner det. Hvis du ikke er opmærksom på denne adfærd, forstår du ikke, hvorfor ødelagte billeder ikke vises korrekt, og vil ikke være i stand til at løse problemet i din egen kode. For at løse problemet skal du indlæse dine egne billeder. Jeg valgte at fortsætte med at bruge den samme metode, men det er ikke rigtig nødvendigt. Ovenstående advarsel gælder browsere, der indeholder applets, som har sikkerhedsmæssige overvejelser, der begrænser diskadgang (medmindre de er underskrevet, selvfølgelig). Under alle omstændigheder var denne artikel beregnet til brug sammen med en applikation, så brug af en alternativ billedindlæsningsmetode bør ikke være et problem.

Når et opkald til getResourceAsStream (strengnavn) er lavet, kan du medtage en relativ sti til billedet som illustreret ovenfor. I ovenstående kode vil det ødelagte billede altid blive indlæst fra den angivne sti i forhold til HTMLEditorKit klasse. For eksempel siden HTMLEditorKit klasse er placeret i javax.swing.text.html, forsøger den at indlæse det ødelagte billede image-failed.gif fra javax.swing.text.html.icons. Dette gælder også for enkle mapper; klasserne behøver ikke at være i pakker. Endelig siden HTMLEditorKit er pakkebeskyttet, har du ikke adgang til dens getResourceAsStream (strengnavn) metode. I stedet kan du bruge MyImageView klasse og læg dine ødelagte billeder i en underkatalog med ikoner. Kodelinjen ser sådan ud:

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

Hvis du vælger at bruge en implementering svarende til min, bliver du nødt til at oprette dine egne ikoner. Du kan stadig bruge ikonerne, der følger med Suns JDK, men det kræver ændring af ressourcens placering for at bruge en absolut sti i stedet for en relativ sti. Den absolutte vej er:

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

At lære om brugen getResourceStream (strengnavn), se Javadoc-oplysningerne for Klasse klasse; et link findes i Ressourcer.

Denne artikel handler næsten udelukkende om at imødekomme relative stier - men hvad er de i forhold til? Indtil videre, hvis du bruger den kode, jeg har leveret, vil du kun kunne bruge stier i forhold til det sted, hvor du startede applikationen. Dette er fantastisk, hvis alle dine billeder altid er placeret på disse stier, men det er ikke altid tilfældet. Jeg vil ikke gå i detaljer om, hvordan man løser dette problem, fordi det let kan løses. Du kan enten indstille en applikations global variabel et eller andet sted i din applikation eller indstille en systemvariabel. I MyImageViewinden du indlæser billedet, sammenkæder du den relative sti til billedet og den absolutte sti opnået fra den globale variabel. Hvis det ikke giver mening, skal du kigge efter processSrcPath () metode i den endelige kildekode til MyImageView.

Endelig, MyImageView er komplet. Du skal dog finde ud af, hvordan du fortæller det JEditorPane at bruge MyImageView i stedet for javax.swing.text.html.ImageView. Det JEditorPane kan understøtte tre tekstformater: almindelig, RTF og HTML. Hvis JEditorPane viser HTML, BasicHTML - en underklasse af TextUI - bruges til at gengive HTML. BasicHTML anvendelser JEditorPane's HTMLEditorKit at oprette Udsigt. Det HTMLEditorKit indeholder en metode kaldet getViewFactory (), som returnerer en forekomst af en indre klasse kaldet HTMLFabrik. Det HTMLFabrik indeholder en metode kaldet Opret (Element elem), som returnerer en Udsigt i henhold til mærketypen. Specifikt hvis mærket er et IMG tag, det returnerer en forekomst af ImageView. For at returnere en forekomst af MyImageView, kan du oprette din egen EditorKit hedder MyHTMLEditorKit, som underklasser HTMLEditorKit. Inde i din MyHTMLEditorKit, opretter du en ny indre klasse kaldet MyHTMLFactory, som underklasser HTMLFabrik. I den indre klasse kan du lave din egen Opret (Element elem) metode, der ser sådan ud:

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

Det eneste, der er tilbage at gøre nu, er at indstille JEditorPane at bruge MyHTMLEditorKit. Koden er ret enkel: