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.
JEditorPane ()
skaber et nytJEditorPane
.JEditorPane (streng url)
skaber enJEditorPane
baseret på en streng, der indeholder en URL-specifikation.JEditorPane (streng type, streng tekst)
skaber enJEditorPane
der er initialiseret til den givne tekst.JEditorPane (URL initialPage)
skaber enJEditorPane
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. JEditorPane
s 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 MediaTracker
konstruktø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 MyImageView
inden 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: