Προγραμματισμός

Συμβουλή Java 109: Εμφάνιση εικόνων χρησιμοποιώντας το JEditorPane

Μπορείτε να χρησιμοποιήσετε το τρέχον JEditorPane στοιχείο για την εμφάνιση σήμανσης HTML, αλλά για την εκτέλεση πιο περίπλοκων εργασιών, JEditorPane χρειάζεται κάποια βελτίωση. Πρόσφατα, έπρεπε να δημιουργήσω μια εφαρμογή δημιουργίας φορμών XML. Ένα απαραίτητο στοιχείο ήταν ένα πρόγραμμα επεξεργασίας HTML WYSIWYG που θα μπορούσε να επεξεργαστεί το περιεχόμενο σήμανσης HTML σε ορισμένες από τις ετικέτες XML. JEditorPane ήταν η προφανής επιλογή στοιχείου Java για την εμφάνιση της σήμανσης HTML, επειδή αυτή η λειτουργικότητα είχε ήδη ενσωματωθεί σε αυτήν. Δυστυχώς, όταν εισάγεται στη σήμανση HTML, JEditorPane δεν ήταν δυνατή η εμφάνιση εικόνων με σχετικές διαδρομές. Για παράδειγμα, εάν η ακόλουθη εικόνα με σχετική διαδρομή περιέχεται σε ετικέτα XML, δεν θα εμφανίζεται σωστά:

Αντίθετα, μια απόλυτη διαδρομή θα λειτουργούσε (υποθέτοντας ότι η δεδομένη διαδρομή και η εικόνα υπήρχε πραγματικά):

Στην εφαρμογή μου, οι εικόνες αποθηκεύονταν πάντα σε έναν υποκατάλογο σε σχέση με την τοποθεσία του αρχείου XML. Ως εκ τούτου, πάντα ήθελα να χρησιμοποιήσω μια σχετική διαδρομή. Αυτό το άρθρο θα εξηγήσει γιατί υπάρχει αυτό το πρόβλημα και πώς να το διορθώσετε.

Γιατί συμβαίνει αυτό;

Ρίξτε μια πιο προσεκτική ματιά στους κατασκευαστές για JEditorPane θα μας βοηθήσει να καταλάβουμε γιατί δεν μπορεί να εμφανίσει εικόνες σε σχετικές διαδρομές.

  1. JEditorPane () δημιουργεί ένα νέο JEditorPane.
  2. JEditorPane (διεύθυνση συμβολοσειράς) δημιουργεί ένα JEditorPane με βάση μια συμβολοσειρά που περιέχει μια προδιαγραφή URL.
  3. JEditorPane (Τύπος συμβολοσειράς, κείμενο συμβολοσειράς) δημιουργεί ένα JEditorPane που έχει αρχικοποιηθεί στο δεδομένο κείμενο.
  4. JEditorPane (αρχική διεύθυνση URL) δημιουργεί ένα JEditorPane με βάση μια καθορισμένη διεύθυνση URL για εισαγωγή.

Ο δεύτερος και ο τέταρτος κατασκευαστής προετοιμάζουν το αντικείμενο με αναφορά σε ένα απομακρυσμένο ή τοπικό αρχείο HTML. Ενα Έγγραφο HTML είναι μέσα σε κάθε JEditorPane, και η βάση του έχει οριστεί στη βάση της παραμέτρου του κατασκευαστή διευθύνσεων URL. JEditorPanes που δημιουργούνται χρησιμοποιώντας αυτούς τους κατασκευαστές μπορούν να χειριστούν σχετικές διαδρομές, επειδή η βάση του Έγγραφο HTML συνδυάζεται με τη σχετική διαδρομή για να δημιουργήσει μια απόλυτη διαδρομή.

Εάν χρησιμοποιείται ο πρώτος κατασκευαστής, το εμφανιζόμενο κείμενο πρέπει να εισαχθεί μετά τη δημιουργία του αντικειμένου. Ο τρίτος κατασκευαστής δέχεται ένα Σειρά ως περιεχόμενο, αλλά η βάση δεν έχει αρχικοποιηθεί. Επειδή ήθελα να αποκτήσω τη σήμανση HTML από μια ετικέτα XML και όχι από ένα αρχείο, χρειάστηκε να χρησιμοποιήσω τον πρώτο ή τον τρίτο κατασκευαστή.

Πώς επιλύουμε το πρόβλημα;

Πριν συνεχίσω, ας αποκαλύψουμε και λύσουμε ένα άλλο μικρότερο πρόβλημα. Ο πιο προφανής τρόπος εισαγωγής σήμανσης στο JEditorPane είναι να χρησιμοποιήσετε το setText (κείμενο συμβολοσειράς). Ωστόσο, αυτή η μέθοδος απαιτεί να εισαγάγετε ολόκληρη τη σήμανση που εμφανίζεται κάθε φορά που κάνετε μια αλλαγή. Στην ιδανική περίπτωση, οι νέες ετικέτες πρέπει να εισαχθούν στο υπάρχον κείμενο. Μπορείτε να χρησιμοποιήσετε τον ακόλουθο κώδικα για να προσθέσετε τη νέα σήμανση:

private void insertHTML (JEditorPane editor, String html, int location) ρίχνει το IOException {// υποθέτει ότι το πρόγραμμα επεξεργασίας έχει ήδη ρυθμιστεί σε "text / html" type HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit (); Έγγραφο doc = editor.getDocument (); StringReader reader = νέο StringReader (html); kit.read (αναγνώστης, έγγραφο, τοποθεσία); } 

Τώρα, φτάνοντας στην καρδιά του θέματος: Πώς γίνεται JEditorPane απόδοση HTML; Κάθε τύπος JEditorPane αναφέρει και τα δύο α Εγγραφο και ένα Πρόγραμμα επεξεργασίας. Πότε JEditorPane έχει οριστεί να πληκτρολογεί "text / html", περιέχει ένα Έγγραφο HTML, το οποίο περιέχει τη σήμανση και ένα HTMLEditorKit που καθορίζει ποιες κλάσεις αποδίδουν κάθε ετικέτα που περιέχεται στη σήμανση. Συγκεκριμένα, το HTMLEditorKit τάξη περιέχει ένα Εργοστάσιο HTML εσωτερική τάξη του οποίου δημιουργία (Element elem) Η μέθοδος εξετάζει πραγματικά κάθε ξεχωριστή ετικέτα. Εδώ είναι ο κωδικός από αυτήν την εργοστασιακή κλάση, η οποία χειρίζεται τις ετικέτες εικόνας:

 αλλιώς εάν (kind == HTML.Tag.IMG) επιστρέψετε νέο ImageView (elem); 

Όπως μπορείτε τώρα να δείτε, το Προβολή εικόνας η τάξη φορτώνει πραγματικά την εικόνα. Για να προσδιορίσετε την τοποθεσία της εικόνας, το getSourceURL () η μέθοδος καλείται:

 ιδιωτικό URL getSourceURL () {String src = (String) fElement.getAttributes (). getAttribute (HTML.Attribute.SRC); εάν (src == null) επιστρέψτε null; Αναφορά διεύθυνσης URL = ((HTMLDocument) getDocument ()). getBase (); δοκιμάστε το {URL u = νέο URL (αναφορά, src); επιστρέφω } catch (MalformedURLException e) {επιστροφή null; }} 

Εδώ, το getSourceURL () Η μέθοδος προσπαθεί να δημιουργήσει μια νέα διεύθυνση URL για την αναφορά της εικόνας χρησιμοποιώντας το Έγγραφο HTML βάση. Εάν αυτή η βάση είναι μηδενική, επιστρέφεται η τιμή null και η λειτουργία φόρτωσης εικόνας ακυρώνεται. Θέλετε να παρακάμψετε αυτήν τη συμπεριφορά.

Στην ιδανική περίπτωση, θα κάνατε την υποκατηγορία Προβολή εικόνας τάξη και παράκαμψη του αρχικοποίηση (Element elem) μέθοδος, όπου γίνεται η φόρτωση της εικόνας. Δυστυχώς, αυτή η τάξη είναι προστατευμένο πακέτο, οπότε πρέπει να δημιουργήσετε μια εντελώς νέα τάξη. Ο ευκολότερος τρόπος για να γίνει αυτό είναι να δανειστείτε, μετά να τροποποιήσετε, τον κωδικό από το πρωτότυπο Προβολή εικόνας τάξη. Ας το ονομάσουμε MyImageView.

Αρχικά, κοιτάξτε τον κώδικα που φορτώνει την εικόνα. Τα ακόλουθα λαμβάνονται από το αρχικοποίηση (Element elem) μέθοδος:

 URL src = getSourceURL (); if (src! = null) {Λεξικό cache = (Λεξικό) getDocument (). getProperty (IMAGE_CACHE_PROPERTY); if (cache! = null) fImage = (Εικόνα) cache.get (src); αλλιώς fImage = Toolkit.getDefaultToolkit (). getImage (src); } 

Εδώ, λαμβάνετε τη διεύθυνση URL. αν είναι άκυρη, παραλείπετε τη φόρτωση της εικόνας. Σε MyImageView, θα πρέπει να εκτελέσετε αυτόν τον κώδικα μόνο εάν η αναφορά της εικόνας σας είναι μια διεύθυνση URL. Το παρακάτω είναι μια μέθοδος που μπορείτε να προσθέσετε για να ελέγξετε την πηγή εικόνας:

 private boolean isURL () String src = (String) fElement.getAttributes (). getAttribute (HTML.Attribute.SRC); return src.toLowerCase (). beginWith ("αρχείο") 

Βασικά, λαμβάνετε την αναφορά στην εικόνα με τη μορφή a Σειρά και ελέγξτε αν ξεκινά με έναν από τους δύο τύπους URL: αρχείο για τοπικές εικόνες και http για απομακρυσμένες εικόνες. Jens Alfke, συγγραφέας του πρωτότυπου javax.swing.text.html.ImageView class, χρησιμοποιεί καθολικές μεταβλητές κλάσης, επομένως δεν είναι απαραίτητη η παράδοση παραμέτρων σε συναρτήσεις. Εδώ, η καθολική μεταβλητή είναι στοιχείο.

Μπορείτε να γράψετε κώδικα που λέει εάν (isURL ()) {}, αλλά τι βάζετε στην άλλη δήλωση για μια σχετική διαδρομή; Είναι αρκετά απλό - απλώς φορτώστε την εικόνα όπως θα κάνατε συνήθως σε μια εφαρμογή:

 αλλιώς {String src = (String) fElement.getAttributes (). getAttribute (HTML.Attribute.SRC); fImage = Toolkit.getDefaultToolkit (). createImage (src); } 

Δεν υπάρχει πραγματική μαγεία εδώ, αλλά υπάρχει ένα catch. ο createImage (src) Η λειτουργία μπορεί να επιστρέψει προτού συμπληρωθούν όλα τα pixel της εικόνας. Εάν συμβεί αυτό, θα εμφανιστεί μια κατεστραμμένη εικόνα. Για να διορθώσετε το πρόβλημα, μπορείτε απλώς να περιμένετε μέχρι να συμπληρωθούν πλήρως τα pixel της εικόνας. Η πρώτη μου τάση ήταν να χρησιμοποιήσω το MediaTracker για να ανιχνεύσει πότε η εικόνα ήταν έτοιμη, αλλά το MediaTrackerΟ κατασκευαστής απαιτεί το στοιχείο που καθιστά την εικόνα ως παράμετρο. Έτσι, για άλλη μια φορά, δανείστηκα κάποιο κωδικό από τον Jim Graham's java.awt.MediaTracker και έγραψα τη δική μου μέθοδο για να παρακάμψω το πρόβλημα:

 private void waitForImage () ρίχνει το InterruptException {int w = fImage.getWidth (αυτό); int h = fImage.getHeight (αυτό); ενώ (αληθινό)} 

Αυτή η μέθοδος κάνει βασικά την ίδια δουλειά με το MediaTracker'μικρό waitForID (int id) μέθοδο, αλλά δεν απαιτεί γονικό στοιχείο. Μια κλήση σε αυτήν τη μέθοδο μπορεί να πραγματοποιηθεί αμέσως μετά τη δημιουργία της εικόνας.

Υπάρχει ένα μικρό πρόβλημα που πρέπει να αναφέρω πριν συνεχίσω. Ήταν αδύνατο να υποκλάσεις Προβολή εικόνας από το javax.swing.text.html πακέτο, έτσι αντιγράψαμε ολόκληρο το αρχείο για να δημιουργήσω τη δική μου τάξη, που ονομάζεται MyImageView, το οποίο δεν έχω βάλει σε ένα πακέτο. Στο πρωτότυπο Προβολή εικόνας κωδικός, εάν μια εικόνα δεν μπορεί να εμφανιστεί επειδή δεν υπάρχει ή καθυστερεί, φορτώνει μια προεπιλεγμένη σπασμένη εικόνα από το javax.swing.text.html.icons πακέτο. Για τη φόρτωση της σπασμένης εικόνας, η κλάση χρησιμοποιεί το getResourceAsStream (όνομα συμβολοσειράς) μέθοδο από το Τάξη τάξη. Ο πραγματικός κώδικας μοιάζει με αυτόν:

 Πόρος InputStream = HTMLEditorKit.class.getResourceAsStream (MISSING_IMAGE_SRC); 

όπου το MISSING_IMAGE_SRC η παράμετρος είναι α Σειρά με περιεχόμενο:

 MISSING_IMAGE_SRC = "εικονίδια" + System.getProperty ("file.separator", "/") + "image-fail.gif"; 

Το ακόλουθο απόσπασμα από το Προβολή εικόνας Ο πηγαίος κώδικας εξηγεί το σκεπτικό της Sun για τη χρήση του getResourceAsStream (όνομα συμβολοσειράς) μέθοδος φόρτωσης των σπασμένων εικόνων.

 / * Αντιγραφή πόρου σε έναν πίνακα byte. Αυτό είναι * απαραίτητο, επειδή πολλά προγράμματα περιήγησης θεωρούν ότι το * Class.getResource αποτελεί κίνδυνο ασφαλείας, επειδή * μπορεί να χρησιμοποιηθεί για τη φόρτωση επιπλέον κατηγοριών. * Το Class.getResourceAsStream επιστρέφει απλώς * bytes, τα οποία μπορούμε να μετατρέψουμε σε εικόνα. * / 

Εάν δεν έχετε παραλείψει αυτήν την ενότητα ακόμα (ξέρω, είναι αρκετά γλυκό!), Επιτρέψτε μου να εξηγήσω γιατί το αναφέρω. Εάν δεν γνωρίζετε αυτήν τη συμπεριφορά, δεν θα καταλάβετε γιατί οι σπασμένες εικόνες δεν εμφανίζονται σωστά και δεν θα μπορείτε να διορθώσετε το πρόβλημα στον δικό σας κώδικα. Για να επιλύσετε το πρόβλημα, πρέπει να φορτώσετε τις δικές σας εικόνες. Επέλεξα να συνεχίσω να χρησιμοποιώ την ίδια μέθοδο, αλλά δεν είναι πραγματικά απαραίτητο. Η παραπάνω προειδοποίηση αφορά προγράμματα περιήγησης που περιέχουν μικροεφαρμογές, τα οποία έχουν ζητήματα ασφάλειας που περιορίζουν την πρόσβαση στο δίσκο (εκτός αν υπογραφεί, φυσικά) Σε κάθε περίπτωση, αυτό το άρθρο προοριζόταν για χρήση με μια εφαρμογή, οπότε η χρήση μιας εναλλακτικής μεθόδου φόρτωσης εικόνας δεν πρέπει να προκαλεί ανησυχία.

Όταν μια κλήση προς getResourceAsStream (όνομα συμβολοσειράς) έχει δημιουργηθεί, μπορείτε να συμπεριλάβετε μια σχετική διαδρομή προς την εικόνα, όπως φαίνεται παραπάνω. Στον παραπάνω κώδικα, η σπασμένη εικόνα θα φορτώνεται πάντα από την καθορισμένη διαδρομή σε σχέση με το HTMLEditorKit τάξη. Για παράδειγμα, δεδομένου ότι το HTMLEditorKit η τάξη βρίσκεται στο javax.swing.text.html, θα προσπαθήσει να φορτώσει την σπασμένη εικόνα image-fail.gif από javax.swing.text.html.icons. Αυτό ισχύει επίσης για απλούς καταλόγους. τα μαθήματα δεν χρειάζεται να είναι σε πακέτα. Τέλος, από τότε HTMLEditorKit προστατεύεται από το πακέτο, δεν έχετε πρόσβαση σε αυτό getResourceAsStream (όνομα συμβολοσειράς) μέθοδος. Αντ 'αυτού, μπορείτε να χρησιμοποιήσετε το MyImageView τάξη και βάλτε τις σπασμένες εικόνες σας σε έναν υποκατάλογο εικονιδίων. Η γραμμή κώδικα θα έχει την εξής μορφή:

 Πόρος InputStream = MyImageView.class.getResourceAsStream (MISSING_IMAGE_SRC); 

Εάν επιλέξετε να χρησιμοποιήσετε μια εφαρμογή παρόμοια με τη δική μου, θα πρέπει να δημιουργήσετε τα δικά σας εικονίδια. Μπορείτε ακόμα να χρησιμοποιήσετε τα εικονίδια που συνδυάζονται με το JDK της Sun, αλλά αυτό απαιτεί την αλλαγή της θέσης του πόρου για να χρησιμοποιήσετε μια απόλυτη διαδρομή αντί για μια σχετική διαδρομή. Η απόλυτη διαδρομή είναι:

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

Για να μάθετε σχετικά με τη χρήση getResourceStream (όνομα συμβολοσειράς), δείτε τις πληροφορίες Javadoc για το Τάξη τάξη; ένας σύνδεσμος παρέχεται στους πόρους.

Αυτό το άρθρο αφορά σχεδόν πλήρως την προσαρμογή σχετικών διαδρομών - αλλά σε τι σχετίζονται; Μέχρι στιγμής, εάν χρησιμοποιείτε τον κωδικό που έχω δώσει, θα μπορείτε να χρησιμοποιείτε μόνο διαδρομές σε σχέση με το σημείο όπου ξεκινήσατε την εφαρμογή. Αυτό είναι υπέροχο εάν όλες οι εικόνες σας βρίσκονται πάντα σε αυτές τις διαδρομές, αλλά αυτό δεν ισχύει πάντα. Δεν θα αναφερθώ σε λεπτομέρειες σχετικά με τον τρόπο επίλυσης αυτού του προβλήματος, επειδή μπορεί να διορθωθεί εύκολα. Μπορείτε είτε να ορίσετε μια καθολική μεταβλητή εφαρμογής κάπου στην εφαρμογή σας είτε να ορίσετε μια μεταβλητή συστήματος. Σε MyImageView, πριν από τη φόρτωση της εικόνας, συνδυάζετε τη σχετική διαδρομή προς την εικόνα και την απόλυτη διαδρομή που λαμβάνεται από την καθολική μεταβλητή. Εάν αυτό δεν έχει νόημα, αναζητήστε το processSrcPath () μέθοδο στον τελικό πηγαίο κώδικα για MyImageView.

Επιτέλους, MyImageView ειναι ΟΛΟΚΛΗΡΩΜΕΝΟ. Ωστόσο, πρέπει να καταλάβετε πώς να το πείτε JEditorPane χρησιμοποιώ MyImageView αντί javax.swing.text.html.ImageView. ο JEditorPane μπορεί να υποστηρίξει τρεις μορφές κειμένου: απλό, RTF και HTML. Αν JEditorPane εμφανίζει HTML, Βασική HTML - μια υποκατηγορία του TextUI - χρησιμοποιείται για την απόδοση του HTML. Βασική HTML χρήσεις JEditorPane'μικρό HTMLEditorKit για να δημιουργήσετε το Θέα. ο HTMLEditorKit περιέχει μια μέθοδο που ονομάζεται getViewFactory (), η οποία επιστρέφει μια παρουσία μιας εσωτερικής τάξης που ονομάζεται Εργοστάσιο HTML. ο Εργοστάσιο HTML περιέχει μια μέθοδο που ονομάζεται δημιουργία (Element elem), που επιστρέφει a Θέα σύμφωνα με τον τύπο ετικέτας. Συγκεκριμένα, εάν η ετικέτα είναι IMG επιστρέφει μια παρουσία του Προβολή εικόνας. Για να επιστρέψετε μια παρουσία του MyImageView, μπορείτε να δημιουργήσετε το δικό σας Πρόγραμμα επεξεργασίας που ονομάζεται MyHTMLEditorKit, που υποκατηγορίες HTMLEditorKit. Μέσα στο δικό σας MyHTMLEditorKit, δημιουργείτε μια νέα εσωτερική κλάση που ονομάζεται MyHTMLFactory, που υποκατηγορίες Εργοστάσιο HTML. Σε αυτήν την εσωτερική τάξη, μπορείτε να φτιάξετε τη δική σας δημιουργία (Element elem) μέθοδος, η οποία μοιάζει κάπως έτσι:

 public View create (Element elem) {Object o = elem.getAttributes (). getAttribute (StyleConstants.NameAttribute); if (o instanceof HTML.Tag) {HTML.Tag kind = (HTML.Tag) o; εάν (kind == HTML.Tag.IMG) επιστρέψτε το νέο MyImageView (elem); } επιστροφή super.create (elem); } 

Το μόνο που μένει να κάνουμε τώρα είναι να ορίσετε το JEditorPane χρησιμοποιώ MyHTMLEditorKit. Ο κωδικός είναι αρκετά απλός:

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