5. Normas de codificación y desarrollo

Coding and development rules

Forewords

This document describes a list of coding conventions that are required for code submissions to the project. By default, the coding conventions for most Open Source Projects should follow the existing coding conventions in the code that you are working on. For example, if the bracket is on the same line as the if statement, then you should write all your code to have that convention.

If you commit code that does not follow these conventions and you are caught, you are responsible for also fixing your own code.

Below is a list of coding conventions that are specific to gvSIG, everything else not specificially mentioned here should follow the official Sun Java Coding Conventions

Why code conventions

As explained in the Sun Java Coding Conventions:

Code conventions are important to programmers for a number of reasons:

  • 80% of the lifetime cost of a piece of software goes to maintenance.
  • Hardly any software is maintained for its whole life by the original author.
  • Code conventions improve the readability of the software, allowing engineers to understand new code more quickly and thoroughly.
  • If you ship your source code as a product, you need to make sure it is as well packaged and clean as any other product you create.

gvSIG specific coding conventions

Mandatory conventions

  1. Headers

    Look at the Headers document for more information.

  2. Indentations

    4 spaces. NO tabs.

  3. Javadoc

    All API interfaces must be fully documented through javadocs comments at interface, method and package level.

    When you inherit or extend from another interface or class which is already documented, and implement or rewrite one of the parent methods, don't write any javadoc comments, as they are also inherited since java 1.4.

  4. Brackets

    All brackets should begin at the end of the line that begins the statement, and end on a new line indented to the beginning of the statement. Example:

    AVOID:

    public class MyClass
    {
    
        public void someMethod()
        {
            if (...) { // Do something }
    }
    }
    

    RIGHT:

    public class MyClass {
    
        public void someMethod() {
            if (...) {
              // Do something
            }
        }
    }
    

    Brackets are mandatory even for single line statements:

    if (expression)       // AVOID!
        // some code
    
    if (expression) {     // RIGHT
        // some code
    }
    
  5. Class variables should not have any prefix or suffix related to its data type or scope. Example:

    String nameString;   // AVOID!
    
    String name;         // RIGHT
    
  6. Avoid lines longer than 80 characters for Code, comments, ...

  7. Logging

    Do not use System.out to log. Instead, use the SLF4J logging API. For example:

    private static final Logger LOG = LoggerFactory.getLogger(MyClass.class);
    
    public void someMethod() {
        LOG.debug("some debug text");
    }
    

    For more information on SLF4J usage, you can read the Logging section in this document.

  8. Exception handling

    Managing exceptions correctly requires experience. This is not supposed to be a guide on managing exceptions, simply a few best practices.

    • Rule 1: Try to catch exceptions as much as possible and rethrow higher level exceptions (meaning hiding the low level detailed and putting a message that is more related to the function of your code).
    • Rule 2: It is important not to loose the stack trace which contains important information. Use chained exceptions for that.
    • Rule 3: Always log the exception at the higher level (ie. where it is handled and not rethrown).
    • Rule 4: Create a few parent Exception for each API library, so methods of the API that throw any exception use that parent exception or one of the child ones.
    • Rule 5: If you have an exception or an error which can't be handled or resolved by code, throw or rethrow a BaseRuntimeException subclass.
    • Rule 6: At user interface level, catch all exception, log it, and informto the user of error in a user friend

    An example:

    public void getTestClass() {
        try {
            Class responseClass =
                Class.forName("some.package.MyClass");
        } catch (ClassNotFoundException cnfe) {
            String message = "Cannot instantiate test class";
            LOG.warn(message, ex);
            throw new ChainedRuntimeException(message, e);
        }
    }
    
  9. Qualified imports

    All import statements should containing the full class name of classes to import and should not use the "*" notation: An example:

    // AVOID!
    import java.util.*;
    import java.net.*;
    
    // RIGHT
    import java.util.Date;
    import java.net.HttpURLConnection;
    
  10. Use interfaces in the declaration of methods and variables.

    By example, if you need a variable x that is a list, declare it as List instead an ArrayList:

    // AVOID!
    ArrayList x = new ArrayList();
    HashMap y = new HashMap();
    
    public HashMap create(ArrayList keys, ArrarList values) {
        ...
    }
    
    // RIGHT
    List x = new ArrayList();
    Map y = new HashMap();
    
    public Map create(List keys, List values) {
        ...
    }
    
  11. API packages

    Usually, API interfaces and classes will belong to the library's main root package. If you create subpackages, use them to group by functionality, not by type.

  12. How to name packages

    All packages must begin with org.gvsig.
    Normally, the package is named org.gvsig followed, a package name that identifies the logical or functional block will contain.

  13. Uso de listeners de swing.

    Cuando estamos creando interfaces de usuario de swing, a la hora de atrapar los eventos de los componentes, se usaran clases anónimas y no se enlazaran los listeners a la clase en la que estén los componentes de swing. Así mismo, cuando el código del evento exceda de dos o tres lineas, se crearan métodos privados en la clase para gestionar esa acción especifica.
    // AVOID !!!!!
    public static class MyPanel extends JPanel implements ActionListener {
    
        private void initComponents() {
            JButton btnOk = new JButton();
            btnOk.addActionListener(this);
        }
    
        @Override
        public void actionPerformed(ActionEvent e) {
            ...
        }
    }
    
    // RIGHT
    public static class MyPanel extends JPanel {
    
        private void initComponents() {
            JButton btnOk = new JButton();
            btnOk.addActionListener(new ActionListener() {
    
                @Override
                public void actionPerformed(ActionEvent e) {
                    btnOk_actionPerformed();
                }
            });
        }
    
        private void btnOk_actionPerformed() {
    
        }
    }
    


Nomenclatura para clases e interfaces

Uso de prefijos y sufijos en general

El uso de prefijos y/o sufijos en el nombre de clases e interfaces es normalmente una forma de aportar información sobre su naturaleza y/o cometido. Por ejemplo se pueden utilizar para denotar un patrón o un rol dentro de un patrón (por ejemplo el sufijo Factory). En otras ocasiones, se hace necesario su uso porque el nombre adecuado ya está asignado. Este es el caso de los prefijos y sufijos que se caracterizan a continuación.

Como regla general:

  • Sólo se utilizará un prefijo o sufijo cuando no se pueda utilizar el nombre simple (normalmente porque ya esté siendo usado por otra clase o interfaz).
Uso del prefijo "Abstract"
  • Sólo debe usarse en clases, no en interfaces.
  • La clase debe ser abstracta e implementar un interface.
  • Sólo se utilizara cuando la interfaz que implementa tenga el mismo nombre.

Ejemplo:

public interface List {}
public abstract class AbstractList extends AbstractCollection implements List {}
Uso del prefijo "I"
  • Se recomienda no utilizar.

    Normalmente, las interfaces son la parte visible del modelo de un componente. Suelen representar a las entidades y conceptos propios del negocio o dominio y por tanto se recomienda mantener los nombres tal cual se utilizan en el negocio.

Uso del prefijo "Default"
  • No puede ser una clase abstracta.
  • Es una implementación por defecto de una interfaz o una clase abstracta.
  • La implementación que aporta debe ser suficiente, pero no necesariamente completa.
  • Pueden existir implementaciones alternativas a esta clase.

Ejemplo:

public interface ListModel {}
public abstract class AbstractListModel implements ListModel, Serializable {}
public class DefaultListModel extends AbstractListModel {}
Uso del prefijo "Base"
  • No es una clase abstracta.
  • Proporciona una implementación "base" de un interface o una clase abstracta.
  • La clase puede ser instanciada para ser usada sin más, pero está pensada para que el usuario del componente la extienda.
  • Si no tiene un interface o clase abstracta como padre, no debe usarse este prefijo.

Ejemplo:

public abstract class StreamRequestHandler {}
public class BaseHTTPRequestHandler extends StreamRequestHandler {}
Uso del sufijo "Impl"
  • Es una clase instanciable, y por tanto no puede ser abstracta
  • Es una implementación completa de un interface o clase abstracta, que generalmente se llama igual que la clase sin el "impl".
  • No se debe extender otras clases a partir de ella.
  • Si el nombre de la clase no entra en conflicto con el nombre del interface o clase abstracta a implementar, no se usará este sufijo.
  • Si se trata de la implementación por defecto de una parte del API y no entra en conflicto con otras partes del proyecto se prefiere el uso del prefijo Default.

Ejemplo:

public interface Plane {}
public abstract class AbstractPlane implements Plane {}

// Ambos son correctos (pero no iguales)
public class PlaneImpl implements Plane {}
public class PlaneImpl extends AbstractPlane {}
Condiciones de aplicación

Este criterio se aplicará en los desarrollos que se realicen bajo el ambito de la Asociacion gvSIG o derivado de contratos realizados por esta.

Nombres de paquetes a usar en gvSIG

Consideraciones previas

Debido a la dispersión y falta de unidad en los nombres de paquetes que se han venido utilizando en gvSIG 1.X, se decidio uniformizarlos, dando una identidad de proyecto por encima del de la empresa que realiza el desarrollo.

Criterio a seguir

A la hora de crear paquetes java que deban formar parte de una distribución oficial de gvSIG o vayan a llevar el respaldo oficial del proyecto gvSIG, estos colgarán del paquete:

org.gvsig

No haciendo mención en el paquete a la empresa que realiza el desarrollo.

Normalmente, el paquete se nombrará org.gvsig seguido, de un nombre de paquete que identifique el bloque lógico o funcional que va a contener.

Condiciones de aplicación

Este criterio se aplicará en los desarrollos que se realicen bajo el ambito de la Asociacion gvSIG o derivado de contratos realizados por esta.

Logging

En gvSIG se emplea la librería SLF4J como API de logging a emplear.

SLF4J Simple Logging Facade o "Fachada de Registro Simple" es un framework que se ha creado para abstraer el sistema de registro que hay por debajo. Como el sistema de registro más popular usado es Log4j, el API del framework es muy similar para simplificar al máximo eliminar la dependencia directa de este sistema. Este framework por tanto es una capa de abstracción del sistema de registro, y nos va a permitir cambiar el componente de registro que lleve por debajo sin necesidad de un gran esfuerzo de recodificación de la aplicación.

Características de SLF4J
  • SLF4J es una capa de abstracción independiente cualquier sistema de registro "logging" concreto.
  • Permite al usuario final acoplar la implementación deseada en tiempo de despliegue.
  • Permite una migración gradual a partir de Jakarta Commons Logging ( JCL ), ya que dispone de un wrapper del API de JCL sobre su propio API.
  • Dispone de un sistema de registro parametrizado que reduce el overhead de evaluar los strings de los mensajes, sobre todo cuando no están habilitados los mensajes de debug.
// Those lines produce the same results. The second one has parameters that reduce the overhead when debuggin is disabled
LOG.debug("The new entry is "+entry+".");
LOG.debug("The new entry is {}.", entry);
  • Dispone de soporte para Mapped Diagnostic Context ( MDC ), si el sistema de registro que funciona bajo SLF4J lo soporta. Por el momento sólo lo hacen Log4j y logback .
  • Los sistemas de logging pueden escoger entre implementar el interfaz de SLF4J directamente como logback y SimpleLogger ó escribir adpatadores SLF4J para una implementación dada como en los casos de Log4jLoggerAdapter y JDK14LoggerAdapter .
  • No delega en un sistema de carga de clases específico (class loader) para delegar en un sistema de registro específico, es decir, se configura estáticamente en tiempo de compilación, sólo se permite usar uno y sólo un sistema de registro logging . Simplemente se añade al CLASSPATH el jar del API del sistema de logging junto con slf4j-api.jar. Esto evita los problemas de carga de clases y pérdidas de memoria sufridos por Jakarta JCL .
  • SLF4J tiene también una interfaz muy próxima a JCL y Log4j API actualmente en uso, por tanto el esfuerzo de implementación se reduce significativamente.
  • gvSIG continúa utilizando el mismo toolkit de logging ( Log4j ) por tanto, prácticamente no existen cambios en tiempo de ejecución.
  • Se gana en flexibilidad, robustez y eficiencia, mediante la posibilidad de cambiar el componente de logging, ya sea para todas las versiones de gvSIG o alternativamente para versiones concretas sin modificar el código fuente de gvSIG.
  • Es el standard que se ha recogido en los proyectos OSGI , debido a que puede adaptar proyectos que ya usan commons-logging con facilidad, debido a la popularidad adquirida de este framework estudiado anteriormente.
Cómo usar SLF4J en gvSIG

gvSIG 2.0 ya está preparado para trabajar con SLF4J, usando LOG4J como implementación, por lo que podemos usarlo directamente desde cualquier extensión de gvSIG.

Para usar SLF4J desde una clase Java basta con incluir las siguientes sentencias:

1.- Importar las clases necesarias

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

2.- Declaramos e inicializamos el Logger.

public class MyClass
{
    private static final Logger LOG = LoggerFactory.getLogger(MyClass.class);
    ...

3.- Usamos el Logger dentro del código de la clase, cuando queramos mostrar un mensaje de log, dependiendo del tipo: error, alerta, información, depuración o traza:

LOG.warn(String message);
LOG.warn(String message,Object arg1);
LOG.warn(String message,Object[] arg1);
LOG.warn(String message, Throwable arg1);
LOG.info(String message);
LOG.info(String message,Object arg1);
LOG.info(String message,Object[] arg1);
LOG.info(String message, Throwable arg1);
LOG.error(String message);
LOG.error(String message,Object arg1);
LOG.error(String message,Object[] arg1);
LOG.error(String message, Throwable arg1);
LOG.debug(String message);
LOG.debug(String message,Object arg1);
LOG.debug(String message,Object[] arg1);
LOG.debug(String message, Throwable arg1);
LOG.trace(String message);
LOG.trace(String message,Object arg1);
LOG.trace(String message,Object[] arg1);
LOG.trace(String message, Throwable arg1);

Los siguientes métodos se proporcionan para consultar la activación de los mensajes por nivel:

LOG.isErrorEnabled();
LOG.isWarnEnabled();
LOG.isInfoEnabled();
LOG.isDebugEnabled();
LOG.isTraceEnabled();

En SLF4J tenemos la capacidad adicional de parametrizar los mensajes, insertando variables dentro de la cadena String del mensaje de registro, como en el siguiente ejemplo:

private Integer temperature;

public void setTemperature(Integer temperature) {

    LOG.debug("Setting temperature to {}. Old temperature was {}.", temperature, this.temperature);

    this.temperature = temperature;

    if(temperature.intValue() > 50) {
      LOG.info("Temperature has risen above 50 degrees.");
    }
}

Esto evita que tengamos que ir concatenando Strings, lo cuál reduce el coste del uso de las instrucciones de logging. Esto es debido a que, aunque el nivel de log que estemos usando esté desactivado (por ejemplo, el de debug), la invocación al método se hará de todas formas, incluyendo la concatenación de Strings para construir el mensaje, si la hubiera.

De todas formas, si la obtención de alguno de los parámetros que vamos a pasar al mensaje de log fuera costosa, es conveniente emplear los métodos de consulta para evitar dicha ejecución. Por ejemplo:

private Integer temperature;

public void setTemperature(Integer temperature) {

    LOG.debug("Setting temperature to {}. Old temperature was {}.", temperature, this.temperature);

    this.temperature = temperature;
    addToTemperatureLog(temperature);

    if (LOG.isDebugEnabled()) {
        LOG.debug("The current average temperature is {} Celsius", calculateAverageTemperature());
    }
}

Interfaces de usuarios

Consideraciones generales
  1. Las herramientas deben denominarse con la misma cadena en cualquier lugar donde aparezcan, tanto en la barra de herramientas como en la barra de menús, etc. Además de dar soporte para ser traducida.
  2. Cada herramienta se debe identificar siempre con el mismo icono. No es posible que tenga un icono en el menú y otro distinto en la barra de herramientas, por ejemplo.
  3. Las ventanas deben incluir siempre un título, el cuál debe ser conciso y con soporte para ser traducido.
Cadenas de texto.
  1. Evitar el uso de abreviaturas cuando no existan problemas de espacio.
  2. Evitar textos muy largos que den información redundante, facilitando la reutilización de claves de traducción. Ejemplo: “Seleccione el tamaño que desea:” por “Tamaño”.
  3. Se debe consultar en "i18n/translations.all" en la instalación de gvSIG las traducciones ya existentes en gvSIG para comprobar si ya existe una cadena igual a la que queremos presentar y deberíamos reutilizar su clave. En este caso, deberemos seguir incluyendo en los properties de nuestro proyecto dicha entrada con la misma traducción en castellano e inglés a la que hay en "i18n/translations.all".
  4. Todos los textos de una ventana deben estar traducidos al castellano y al inglés como mínimo, evitando que aparezcan claves de traducción en vez del texto traducido, o que aparezcan textos en inglés en la interfaz en castellano.
  5. Prestar atención a fallos ortográficos, sobretodo en los acentos a la hora de crear las traducciones.
  6. En cualquier referencia a los documentos de gvSIG, como por ejemplo “Vista”, la primera letra debe ir en mayúsculas.
  7. Hay que seguir siempre la misma pauta en las mayúsculas y minúsculas cuando se presenta el caso de un desplegable con distintos elementos. Siempre deben empezar todos por el mismo tipo de letra.
  8. Usaremos "inglés" en la claves de traduccion. No las abreviaremos reflajando el mismo texto en la clave que en la traduccion al inglés. Reflejaran el texto que debe aparecer en la traduccion usando "_" en lugar de espacios en blanco.
  9. Todas las claves de traduccion que se creen nuevas deberan comenzar por "_".
  10. Cuando una cadena de traduccion precise parametros, en la clave se introduciran estos en la forma "XnumeroX" en el sitio correspondiente donde "numero" empieza en cero e identificara al parametro. En la traduccion, se sustituira "XnumeroX" por "{numero}" en el sitio que corresponda.
  11. Cuando en una cadena de traduccion aparezcan caracteres de puntuacion usar las siguientes reglas para la clave de traduccion:
XcolonX
":"
XsemicolonX
";"
XdotX
"."
XnlX
"\n"
XellipsisX
"..."
XquestionX
"?"
XexclamationX "!"
Dimensiones de cuadros de dialogo y paneles en general
  1. Los componentes de los paneles deberan ajustarse al tamaño del panel, presentando este un tamaño por defecto adecuado para mostrar los elementos que contiene.
  2. Evitar que aparezcan textos cortados e ilegibles obligando a que el usuario tenga que volver a dimensionar la ventana cada vez que acceda a ella. Hay que tener en cuenta que otras traducciones pueden ocupar mas que el texto en castellano o inglés.
  3. Prestar atención a los espacios sobrantes en las ventanas, sobretodo en la parte inferior.
  4. Intentar homogeneizar una ventana que incluya varios paneles, de manera que no existan espacios sobrantes exagerados.
  5. Dejar márgenes de 5 píxeles entre paneles interiores y con los bordes de la ventana.
Formularios
  1. Organizar los campos de un formulario en una sola columna de datos.
  2. Todas las opciones usadas para un grupo de componentes JRadioButton deben ser excluyentes.
  3. Los textos de los elementos JLabel que precedan a un JTextField, JComboBox, etc. en un formulario deben terminar con los dos puntos “:”.
  4. Cuando un componente con texto se encuentra en estado enabled=false, el texto debe aparecer en gris.
  5. La separación entre un componente JLabel y el componente de su derecha no debe ser inferior a 5 píxeles de manera que sea legible el texto. Además el tamaño máximo de separación no debe ser generalmente, mayor que el tamaño del propio texto del componente JLabel.
  6.  No se deben incluir botones sin funcionalidad.
  7.  El texto de los botones que aparecen en varias ventanas debe ser el mismo siempre. Por ejemplo, el botón con texto “Aceptar” a veces aparece con el texto “Ok”, cuando siempre debe poner “Aceptar”.
  8.  Los botones de Aplicar/Aceptar/Cancelar deben aparecer siempre en este orden y en la esquina inferior derecha de la ventana (en la misma alineación horizontal).
Creación de interfaces de usuario

A la hora de crear interfaces de usuario, siempre que sea posible, se utilizará la herramienta abeille. Esta herramienta es adecuada para la creación de interfaces de usuario de tipo formulario. Si el interface de usuario a crear no es un formulario, no es preciso usar esta herramienta.

[ http://devel.gvsig.org/download/runtimes/abeille/ ]

A la hora de crear los interfaces de usuario estos se guardarán en formato xml junto a los ficheros fuente del proyecto. Además se generará código java asociado al interface de usuario usando el sufijo "View" para nombrar la clase java a generar. Estos ficheros java generados nunca deberán ser editados. Para cada panel se crearán dos o tres clases java. Vamos a ver esto con un ejemplo. Supongamos que estamos creando el panel "general"  de propiedades de una capa. Tendremos:

  • GeneralLayerPropertiesView, generado a partir del abeille. Básicamente contiene los componentes basicos y su disposición y dimensiones.
  • GeneralLayerPropertiesControler, extiende a GeneralLayerPropertiesView, y aporta los métodos necesarios para asegurar el comportamiento de los componentes, además de la inclusión de imágenes y gestión de traducciones de estos.
  • GeneralLayerPropertiesModel, que contendrá el modelo asociado al componente. Es posible que muchas veces no sea necesario crear este componente ya que muchas veces existirá ya en las librerias de la lógica el componente al que se corresponde el interface de usuario que estamos creando. Por ejemplo, en este caso, el modelo asociado a nuestro panel sería un VectorialLayer de gvSIG.

A la hora de crear el interface de usuario deberemos de tener en cuenta:

  • Deberemos dar nombre a todos los componentes que aparezcan en nuestro interface de usuario.
  • Prefijaremos el nombre de los componentes siguiendo el siguiente criterio:
    • JLabel, lbl
    • JTextField, txt
    • JTextArea, txt
    • JEditorPane, txt
    • JComboBox, cbo
    • JButton, btn
    • JList, lst
    • JTree, tree
  • Cuando precisemos incrustar un componente que no este directamente soportado por la herramienta, insertaremos un JPanel dándole un nombre con el sufijo "Container".
  • Dejaremos un espacio de separación (una celda) entre los distintos componentes y entre estos y los bordes del panel.
  • Cuando debamos introducir un selector de fichero, introduciremos una caja de texto y a su derecha, sin espacio de separación un botón.
  • Los paneles que realicemos no deberán contener botones de Aplicar/Aceptar/Cancelar para facilitar su reutilización. Se utilizarán las utilidades del WindowManager de ToolsSwing para presentar ventanas y cuadros de diálogo.

A la hora de crear la clase "controller", tendremos en cuenta:

  • Se recomienda que nuestro panel implemente el interface "Component" de ToolsSwing.
  • Se recomienda la existencia de un metodo "initComponents" invocado en el contructor y en el que se inicializarán todos los componentes, añadiendo los "listeners" que precisemos y traduciendo los elementos que sea necesario.
  • Los paneles no deberán depender del estado de otros paneles, ventanas o herramientas de gvSIG. Si es preciso recibirían en su constructor la informacion pertinente para su funcionamiento.
  • Se evitará la codificación de acciones en clases anónimas asociadas a los listeners de los componentes, creándose métodos en la clase controller a los que se invocará desde las clases anónimas para que realicen las acciones pertinentes.
  • La clase controller no implementará ActionListener y no se redirijirán a ella todos los eventos de los componentes.
  • Usa el I18nManager para traducir las cadenas de texto que aparezcan en el interface de usuario. No guardes una instancia de él en la clase, créala local al método initComponents.
  • Cuando haya que incluir imágenes en los diálogos usa el "Tema de iconos" para acceder a ellas.

Estas consideraciones estan relacionadas con la implementación de paneles y cuadros de diálogo. Normalmente estos llevaran asociada en el API un interface definiendo el API del panel, que deberá estar implememtado con la clase "controller". Desde fuera de la implementación no se deberá acceder nunca a los componentes del interface de usuario, debiendo existir métodos para asignar los valores de entrada del formulario y recoger los de salida. Si estos métodos se limitan a hacer get y set sobre los componentes del formulario, revisa el análisis, es fácil que hayas trasladado al cliente del panel responsabilidades del "controller".

Acceso al documento vista activo

Siempre que precisemos acceder al documento vista y no al interface gráfico, en
lugar de recuperar el documento a partir de la ventana activa, usaremos el método
getActiveDocument del ApplicationManager. Haríamos algo como:

  ApplicationManager application = ApplicationLocator.getManager();
  ViewDocument doc = (ViewDocument) application.getActiveDocument(ViewManager.TYPENAME);
  if( doc == null ) {
      return false;
  }
  MapContext  mapContext = doc.getMapContext();

Asi mismo, si una vez recuperado el documento vista debemos determinar si contiene capas vectoriales, o contiene capas vectoriales activas usaremos los metodos del MapContext:

  • hasVectorLayers
  • hasActiveVectorLayers

Por ejemplo:

  return mapContext.hasActiveVectorLayers();
Resumiendo, siempre que sea posible:
  • No accedamos al interface de usuario si solo precisamos objetos de la lógica (getActiveDocument vs getActiveWindow).
  • Utilicemos los métodos "has...()" del MapContext en lugar de recorrernos las capas.



Tema anterior

4. Proyectos oficiales

Próximo tema

Talleres

Esta página