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.
JEditorPane ()
skaper et nyttJEditorPane
.JEditorPane (streng url)
skaper enJEditorPane
basert på en streng som inneholder en URL-spesifikasjon.JEditorPane (strengtype, strengtekst)
skaper enJEditorPane
som er initialisert til den gitte teksten.JEditorPane (URL initialPage)
skaper enJEditorPane
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. JEditorPane
s 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 MediaTracker
sin 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 MediaTracker
s 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 JEditorPane
s 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: