
A través de esta aplicación de ejemplo, aprenderás cómo usar la capacidades de los componentes de texto y cómo personalizarlos. Esta sección cubre los siguientes texto, que puesen ser aplicados a todas las subclases de JTextComponent: :
Nota: Es fichero fuente de esta aplicación es TextComponentDemo.java. También nevesitarás LimitedStyledDocument.java.
Al igual que muchos otros componentes Swing, un componente de texto separa su contenido de su vista. El contenido de un componente de este es manejado por su documento, el cual contiene el texto, soporte para edición, y notifica a los oyente los cambios en el texto. Un documento es un ejemplar de una clase que implementa el interface Document o su subinterface StyledDocument.
La aplicación de ejemplo mostrada anteriormente tiene un documento persoanlziado LimitedStyledDocument, que limita el número de caracteres que puede contener. LimitedStyledDocument es una subclase de DefaultStyledDocument, el documento por defecto para JTextPane.Aquí está el código del programa de ejemplo que crea un LimitedStyledDocument y lo designa como el documento para le text pane
...donde se declaren las variables miembro... JTextPane textPane; static final int MAX_CHARACTERS = 300; ...en el constructor del frame... //Crea el documento para el área de texto LimitedStyledDocument lpd = new LimitedStyledDocument(MAX_CHARACTERS); ... //Crea el text pane y lo configura textPane = new JTextPane(); textPane.setDocument(lpd); ...Para limitar los caracteres permitidos en el docuemnto, LimitedStyledDocument sobreescribe el método insertString de su superclase, que es llamado cada vez que se inserta texto en el documento.public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { if ((getLength() + str.length()) <= maxCharacters) super.insertString(offs, str, a); else Toolkit.getDefaultToolkit().beep(); }Además de insertString, los documentos personalizados también sobreescriben el método remove, que es llamado cada vez que se elimina texto de un documento.Un uso común para un documento personalizado es para crear un campo de texto validado. (un campo cuyo valor es chequeado cada vez que es editado). Para ver dos ejemplos de campos de texto validados pueder ir a Crear un Campo de Texto Validado.
Para más información puedes ver las tablas del API: Clases e Interfaces que Representan Documentos y Métodos útiles para trabajar con Documento.
Un documento notifica sus cambios a los oyentes interesados. Se utiliza un oyente de Document para reaccionar cuando se inserta o se elimina texto de un documento, o cuando cambia el estilo de alguna parte del texto.El programa TextComponentDemo usa un oyente de Document para actualizar el diario de cambios siempre que ocurra un cambio en el text pane. Esta línea de código registra un ejemplar de MyDocumentListener como oyente del LimitedStyledDocument usado en el ejemplo:
LimitedStyledDocument lpd = new LimitedStyledDocument(MAX_CHARACTERS); lpd.addDocumentListener(new MyDocumentListener());Aquí está la implementación de MyDocumentListener:protected class MyDocumentListener implements DocumentListener { public void insertUpdate(DocumentEvent e) { update(e); } public void removeUpdate(DocumentEvent e) { update(e); } public void changedUpdate(DocumentEvent e) { //Display the type of edit that occurred changeLog.append(e.getType().toString() + ": from " + e.getOffset() + " to " + (e.getOffset() + e.getLength() - 1) + newline); changeLog.setCaretPosition(changeLog.getDocument().getLength() - 1); } private void update(DocumentEvent e) { //Display the type of edit that occurred and //the resulting text length changeLog.append(e.getType().toString() + ": text length = " + e.getDocument().getLength() + newline); changeLog.setCaretPosition(changeLog.getDocument().getLength() - 1); } }El oyente de nuestro ejemplo mueatr el timpo de cambio que ha ocurrido y, si está afectada por el cambio, la longitud del texto. Para información general sobre los oyente de Docuement y eventos de Document, puedes ver Cómo escribir un Oyente de Document.Recuerda que el documento para este text pane limita el número de caracteres permitidos en el decumento. Si intentas añadir texto hasta exceder el límite máximo, el documento bloquea el cambio y no se llamará al método insertUpdate del oyente. Los oyentes de Document sólo son notificados si el cambio ha ocurrido realmente.
Algunas veces, podrías estar tentado de cambiar el texto del documento desde dentro de un oyente de Document. Por ejemplo, si tienes un campo de texto sólo debería contener enteros y el usuario introduce algún otro tipo de datos, podrías querer cambiar el texto a 0. Sin embargo, nunca se debe modificar el contenido de un componente de texto desde dentro de un oyente de Document. De hecho, si intentas hacerlo, tu programa se quedará bloqueado. En su lugar proporciona un documento personalizado y sobreescribe los métodos insert y remove. Crear un Campo de texto validado te enseña como hacerlo.
Todos los componentes de Texto Swing soportan comandos de edición estándard como cortar, copiar, pegar y la inserción de caracteres. Cada comando de edición está representada e implementado por un objeto action. Esto hace sencillo el asociar un comando con un componente GUI, como un ítem de menú o un botón, y construir un GUI alrededor de un componente de texto.Un componente de texto usa un objeto EditorKit para crear y manejar estas acciones. Además de manejar un conjunto de acciones para un componente de texto, un kit de editor también sabe leer y escribir documentos en un formato particular.
El paquete text de Swing proporciona estos tres kits de editor:
La mayoría de los programas no necesitan escribir código que interactúe directamente con los kits de editor porque JTextComponent proporciona el API necesario para invocar directamente a las capacidades del kit. Por ejemplo, JTextComponent proporicona métodos read y write, que llaman a los métodos read y write del kit de editor. JTextComponent también proporcionar un método, getActions, que devuelve todas las acciones soportadas por un componente. Este método obtiene una lista de acciones desde el kit de editor del componente. Sin embargo, las clases del kit de editor proporciona útiles clases internas y variables de clases que son muy útiles para crear un GUI alrededor de un componente de texto. Asociar Acciones con Ítems de Menú muestra como asociar una acción con un ítem de menú y Asociar Acciones con Pulsaciones de Teclas muestra como asociar una acción con una pulsación de teclas determinadas. Ambas secciones hacen uso de clases manejables o de variables definidas en los kits de editor estándars de Swing.
- DefaultEditorKit
- Lee y escribe texto sin estilo. Proporciona un conjunto básico de comandos de edición. Los demás kits de editor descienden de este.
- StyledEditorKit
- Lee y escribe texto con estilo y proporciona un conjunto de acciones mínimo para texto con estulo. Esta clase es una subclase de DefaultEditorKit y es el kit de editor usado por defecto por JTextPane.
- HTMLEditorKit
- Lee, escribe y edita HTML. Esta es una subclase de StyledEditorKit.
Como se mencionó anteriormente, podemos llamar al método getActions sobre cualquier componente para obtener un array que contenga todas las acciones soportadas por dicho componente. Es conveniente cargar el array de acciones en un Hashtable para que nuestro programa pueda recuperar una acción por su nombre. Aquí está el código de TextComponentDemo que obtiene las acciones del text pane y las carga dentro de un Hashtable:private void createActionTable(JTextComponent textComponent) { actions = new Hashtable(); Action[] actionsArray = textComponent.getActions(); for (int i = 0; i < actionsArray.length; i++) { Action a = actionsArray[i]; actions.put(a.getValue(Action.NAME), a); } }Y aquí hay un método de conveniencia para recuperar una acción por su nombre desde el hashtable:private Action getActionByName(String name) { return (Action)(actions.get(name)); }Puedes utilizar ambos métodos de forma casi literal en tus programas. Sólo tienes que cambiar actions por el nombre de tu hashtable.Ahora, vemaos cómo se crea el ítem de meú Cut y como se asocia a la acción de eliminar texto de un componente de texto:
protected JMenu createEditMenu() { JMenu menu = new JMenu("Edit"); ... menu.add(getActionByName(DefaultEditorKit.cutAction)); ...Este código obtiene la acción por su nombre usando el método descrito anteriormente y añade la acción al menú. Esto es todo lo que necesitas hacer. El menú y la acción tienen en cuenta todo lo demás. Observaras que el nombre de la acción viene de DefaultEditorKit. Este kit proporciona acciones para la edición básica de texto y es la superclase para todos los kits de editor proporcionados por Swing. Por eso sus capacidades están disponibles para todos los componentes de texto a menos que se hayan sobreescrito por una personalización.Por razones de rendimiento y eficiencia, los componentes de texto comparten acciones. El objeto Action devuelto por getActionByName(DefaultEditorKit.cutAction) es compartido por el JTextArea (no editable) de la parte inferior de la ventana. Esto tiene dos importantes ramificaciones:
Configurar el menú Style es similar. Aquí está el código que crea y pone el ítem de menú Bold en él:
- Generalmente hablando, no se deben modificar los objetos Action obtenidos de los kits de editor. Si lo hacemos, los cambios afectarán a todos los componentes de texto de nuestro programa.
- Los objetos Action pueden operar con otros componentes de texto del programa, quizás más de los esperados. En este ejemplo, incluso aunque no sea editable, el JTextArea comparte las acciones con el JTextPane. Si no queremos compartir, deberemos ejemplarizar el objeto Action nosotros mismos. DefaultEditorKit define varias sublcases de Action muy útiles.
protected JMenu createStyleMenu() { JMenu menu = new JMenu("Style"); Action action = new StyledEditorKit.BoldAction(); action.putValue(Action.NAME, "Bold"); menu.add(action); ...StyledEditorKit proporciona sublcases de Action para implementar comandos de edición para texto con estilo. Habrás notado que en lugar de obtener la accón del kit de editor, este código crea un ejemplar de la clase BoldAction. Asñi, esta acción no será compartida por ningún otro componente de texto, y cambiando su nombre no afectará a ningún otro componente de texto.Además de asociar una acción con componente GUI, también podemos asociar una acción con una pulsación de teclas Asociar Acciones con Pulsaciones de Teclas muestra como hacerlo.
Puedes ver la tabla de API relacionada con los Comandos de Edición de Texto.
Cada componente de texto tiene uno o más keymaps-- cada uno de los cuales es un ejemplar de la clase Keymap. Un keymap contiene una colección de parejas nombre-valor donde el nombre es un KeyStroke (pulsación de tecla) y el valor es una Action. Cada parej enlaza el keystroke con la acción por lo tanto cada vez que el usuario pulsa la tecla, la acción ocurrirá.Por defecto, un componente de texto tiene un keymap llamado JTextComponent.DEFAULT_KEYMAP. Este keymap contiene enlaces básicos estándars. Por ejemplo, las teclas de flechas están mapeadas para mover el cursor, etc. Se puede modificar o ampliar el kjeymap por defecto de las siguientes formas:
Cuando se resuelve una pulsación a su acción, el componente de texto chequea el keymap en el orden en que fueron añadidos al componente de texto. Así, el enlace para una pulsación específica en un keymap que hayamos añadido a un componente de texto sobreescribe cualquier enlace para la misma pulsación en el keymap por defecto.
- Añadiendo un keymao personalizado al componente de texto con del método addKeymap de JTextComponent.
- Añadiendo enlaces de teclas al keymap por defecto con el método addActionForKeyStroke de Keymap. El Keymap por defecto es compartido entre todos los componentes de texto, utilizalo con precaución.
- Eliminado enlaces de teclas del keymap por defecto con el método removeKeyStrokeBinding de Keymap. El Keymap por defecto es compartido entre todos los componentes de texto, utilizalo con precaución.
El text pane de TextComponentDemo añade cuatro enlaces de teclas al keymap por defecto.El siguiente código añade el enlace de tecla CTRL-B al keymap por defecto. El código para añadir las otras tres es similar.
- CTRL-B para mover el cursor un caracter hacia atrás
- CTRL-F para mover el cursor un caracter hacia adelante
- CTRL-P para mover el cursor una línea hacia arriba
- CTRL-N para mover el cursor una línea hacia abajo.
//Obtiene el mapa por defecto actual Keymap keymap = textPane.addKeymap("MyEmacsBindings", textPane.getKeymap()); //Ctrl-b para ir hacia atrás un caracter. Action action = getActionByName(StyledEditorKit.backwardAction); KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_B, Event.CTRL_MASK); keymap.addActionForKeyStroke(key, action);Este código primero añade el keymap al árbol de componentes. El método addKeymap crea el keymap por nosotros con el nombre y padre proprocionados en la llamada al método. En este ejemplo, el padre es el keymap por defecto del text pane. Luego, el código obtiene la acción de ir hacia atrás del kit de editor y obtiene un objeto KeyStroke que representa la secuencia de teclas CTRL-B. Finalmente, el código añade la pareja acción y puslación al keymap, y por lo tanto enlaza la tecla con la acción.Puedes ver el API relacionado en la tabla Enlazar Pulsaciones a Acciones.
Nota: La implementación de deshacer/repetir en TextComponentDemo fue copiada directamente del NotePad que viene con Swing. La mayoría de los programadores también podrán copiar esta implementación sin modificaciones.
Implementar Deshacer/repetir tiene dos partes:
- Recordar las ediciones "reversibles" que han ocurrido.
- Implementar los comandos deshacer y repetir y proporcionar un interface de usuari para ellos.
Parte 1: Recordar Ediciones "Reversibles"
Para soportar deshacer/repetir, un componente de texto debe recordar cada edición que ha ocurrido sobre él, el orden en que ocurren las ediciones en relación a otras, y que hacer para deshacerlas. El programa de ejemplo usa un manejar de deshacer, un ejemplar de la clase UndoManager del paquere undo de Swing, para menjar una lista de ediciones reversibles. El undomanager se crea donde se declaran las variables miembros:protected UndoManager undo = new UndoManager();Ahora veamos como el programa encuentra las ediciones reversibles y las añade al undomanager.Un documento noticia a los oyentes interesados si en su contenido ha ocurrido una edición reversible. Un paso importante en la implementación de deshacer/repetir es registrar un oynete de 'undoable edit' en el documento del componente de texto. Este código añade un ejemplar de MyUndoableEditListener al documento del text pane:
lpd.addUndoableEditListener(new MyUndoableEditListener());El oyente usado en nuestro ejemplo añade la edición a la lista del undomanager:protected class MyUndoableEditListener implements UndoableEditListener { public void undoableEditHappened(UndoableEditEvent e) { //Recuerda la edición y actualiza los menús undo.addEdit(e.getEdit()); undoAction.update(); redoAction.update(); } }Observa que este método actualizad so objetos: undoAction y redoAction. Estos dosn dos objetos actión añadidos a los ítems de menú Undo (Deshacer) y Redo (Repetir) menu items, respectivamente. El siguiente paso es ver como se crean los dos ítems de menú y la implementación de las dos acciones.Para información general sobte oyentes y eventos de 'undoable edit' puedes ver: Cómo escribir un oyente de Undoable Edit.
Parte 2: Implementar los Comandos Deshacer/Repetir
El primer paso de esta parte de implementación es crear las acciones y ponerlas en el menú Edit.JMenu menu = new JMenu("Edit"); //Deshacer y repetir son acciones de nuestra propia creacción undoAction = new UndoAction(); menu.add(undoAction); redoAction = new RedoAction(); menu.add(redoAction); ...Las acciones deshacer y repetir son implementadas por subclases personalizadas de AbstractAction: UndoAction y RedoAction respectivamente. Estas clases son clases internas de la clases primaria del ejemplo.Cuando el usuario llama al comando Undo, el método actionPerformed de UndoAction mostrado aquí, obtiene la llamada:
public void actionPerformed(ActionEvent e) { try { undo.undo(); } catch (CannotUndoException ex) { System.out.println("Unable to undo: " + ex); ex.printStackTrace(); } update(); redoAction.update(); }Este método llama al método undo del undomanager y actualiza los ítems de menú para reflejar el nuevo estado de deshacer/repetir.De forma similar, cuando el usuario llama al comando Redo, el método actionPerformed de RedoAction obtiene la llamada:
public void actionPerformed(ActionEvent e) { try { undo.redo(); } catch (CannotRedoException ex) { System.out.println("Unable to redo: " + ex); ex.printStackTrace(); } update(); undoAction.update(); }Este método es similar excepto en que llama al método redo de undomanager.La mayoría del código de las clases UndoAction y RedoAction está dedicada a habilitar o deshabilitar las acciones de forma apropiada al estado actual, y cmabiar los nombres de los ítems de menú para reflejar la edición a deshacer o repetir.
El programa TextComponentDemo usa un oyente de caret para mostrar la posición actual del cursor o, si hay texto selecciónado la extensión de la selección.El oyente de caret de este ejemplo es también una etiqueta. Aquí está el código que crea la etiqueta, la añade a la ventana, y la hace oyente de caret del text pane:
//Crea el área de estado JPanel statusPane = new JPanel(new GridLayout(1, 1)); CaretListenerLabel caretListenerLabel = new CaretListenerLabel( "Caret Status"); statusPane.add(caretListenerLabel); ... textPane.addCaretListener(caretListenerLabel);Un oyente de caret debe implemenar un método, caretUpdate, que es llamado cada vez que el cursor se mueva o cambie la selección. Aquí está la implementación que CaretListenerLabel hace de caretUpdate:public void caretUpdate(CaretEvent e) { //Obtiene la posición en el texto int dot = e.getDot(); int mark = e.getMark(); if (dot == mark) { // no hay selección try { Rectangle caretCoords = textPane.modelToView(dot); //Convierte las coordenadas setText("caret: text position: " + dot + ", view location = [" + caretCoords.x + ", " + caretCoords.y + "]" + newline); } catch (BadLocationException ble) { setText("caret: text position: " + dot + newline); } } else if (dot < mark) { setText("selection from: " + dot + " to " + mark + newline); } else { setText("selection from: " + mark + " to " + dot + newline); } }Cómo puedes ver, este oyenbte actualiza su etiqueta de texto para reflejar el estado actual del cursor o la selección. El oyente obtiene la información mostrada desde el objeto caret event. Para información general sobre los oyentes y eventos de cursor puedes ver Cómo escribir un oyente de Caret.Al igual que los oyenentes de document, un oyente de caret es pasivo. Reacciona a los cambios del cursor o de la selección, pero no cambia el cursor ni la selección. En vez de modidicar el cursor o la seleccíon desde un oyente de caret, deberemos usar un caret personalizado. Para crear un caret peresonalizado, debemos escribir una clase que implemente el interface Caret, luego proporcionar un ejemplar de nuestra clase como argumento a setCaret sobre un componente de texto.