25 de abril de 2017

Patrones de diseño - La estrategia

Artículo original: Design Patterns - The Strategy
http://weblogs.foxite.com/andykramek/archive/2006/12/18/3044.aspx
Autor: Andy Kramek
Traducido por: Ana María Bisbé York


¿Qué es una estrategia y cómo la utilizo?

La estrategia describe una solución al problema principal inherente a escribir el código principal – qué hacer con demandas inesperadas para cambios de implementación.

¿Cómo reconozco dónde necesito una estrategia?

La definición formal de estrategia, dada en "Design Patterns, Elements of Reusable Object-Oriented Software" por Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides es:

Define una familia de algoritmos, encapsula cada uno, y los hace intercambiables. La estrategia permite al algoritmo variar en dependencia del cliente que la utilice.

Esto resulta algo oscuro en una primera lectura, vamos a verlo en un ejemplo donde el patrón de estrategia puede ayudar.

Considere el problema de calcular la tasa de impuestos debido por una compra. Podemos asumir que nuestra aplicación conoce los elementos adquiridos, y la cantidad y entonces (a partir de su información de precios) puede determinar el valor de la venta. Ahora tenemos que aplicar la tarifa de impuesto.

Esto no es un problema, si tenemos un entorno donde el impuesto de venta se aplica a una única tarifa independientemente de la localidad ( como con el VAT en el Reino Unido, por ejemplo). El único problema aquí es saber si el elemento tiene impuesto, y si lo tiene cuál es la tarifa actual. Sin embargo, en EEUU, ¡ los impuestos sobre las ventas son locales ! La tarifa aplicada depende incluso de dónde compramos algo (o incluso, dónde reside el vendedor.)

Por ejemplo, en nuestra localidad, la tasa de ventas en ropa es 5.75%; pero si viajamos 20 millas al sur, la tasa base es sólo del 5.25%, mientras, que si viajamos dentro del mismo estado (sólo a 50 millas) no existen tasas para vestuario de ningún tipo. El mismo elemento de vestuario etiquetado como $29.95 puede, sin embargo, costarnos $31.67, $31.52 o $29.95 según donde compremos. Teniendo esto en cuenta para cualquier otro elemento, la tasa aplicable a una transacción depende del contexto (la combinación específica de tipo de elemento y localización), en el cual tiene lugar la transacción. Si hubiéramos escrito el código en la aplicación para controlarlo podríamos comenzar con algo como esto:

DO CASE
  CASE lclocale = "Akron"
    IF lcItemType = "Ropa"
      lnTaxRate = 5.75
    ELSE
      *** Otros elementos aquí
    ENDIF
  CASE lclocale = "Canton"
    IF lcItemType = "Ropa"
      lnTaxRate = 5.25
    ELSE
      *** Otros elementos aquí
    ENDIF
  CASE lclocale = "Grove City"
    IF lcItemType = "Ropa"
      lnTaxRate = 0.00
    ELSE
      *** Otros elementos aquí
    ENDIF
  OTHERWISE
    *** Aplicar un valor predeterminado
    lnTaxRate = 5.50
ENDCASE

Podemos ver inmediatamente que surgirá un problema. Ocurrirá, cuando necesitemos agregar "Cleveland" a nuestra lista de localidades, o cuando Akron, desalentado por las pérdidas en ventas de Canton, recorte sus tasas para vestuario. Necesitaremos cambiar nuestro código con todos los riesgos que conlleva. Más adelante, como vamos a aumentar la cantidad de localidades, este código será inmanejable. Por supuesto, podríamos guardar esta información en una tabla (una columna para localidad, y quizás, una columna para cada elemento) y revisarlo cada vez que sea necesario. De esta forma, lo único que tenemos que hacer es, modificar registros cuando cambie el dato; pero la tabla podría crecer rápidamente y puede haber muchos datos duplicados y redundancia de los datos, lo cual no es tampoco la solución ideal. Lo que queremos realmente que haga nuestra aplicación es que sea capaz de pasar el precio del ticket, y la información de la localidad, a algo que nos diga qué tasa debemos aplicar. En otras palabras deseamos separar la abstracción (calcular el impuesto debido) de la implementación (basada en la localidad).

Sin embargo, sin cambiar el código, lo cual es re-localizar; pero no cambiar el problema, un puente no es la solución porque solo permite una única implementación y nosotros necesitamos múltiples implementaciones. Necesitamos ser capaces de decidir, en tiempo de ejecución, qué implementación necesita. El patrón de estrategia nos permite definir clases para cada situación que debemos tratar y luego instanciar la adecuada en tiempo de ejecución.

¿Cuáles son los componentes de una estrategia?

Una estrategia tiene tres componentes esenciales: "la estrategia abstracta", que es una clase que define la interfaz, y funcionalidad genérica para las "estrategias concretas", que son subclases que definen varias implementaciones posibles. El tercer componente, el "Contexto", es responsable de controlar la referencia a la implementación actual. La estrategia es inicializada por una petición de acción del cliente.

Puede pensar, que una estrategia es como un "puente dinámico" en el cual un final (el contexto) es estático; pero el otro (la estrategia) está creado por varias variantes con el propósito de entregar una implementación específica en un momento específico. La esencia del patrón es tal que la decisión como establecer qué subclase debe ser instanciada en cualquier momento dependa de la información derivada del cliente (por ejemplo, la aplicación).

Este patrón generalmente basa la responsabilidad de instanciar lel objeto estrategia concreto en el cliente, que pasa una referencia al contexto. Para implementar un patrón de estrategia, usted necesita definir la clase de estrategia abstracta y cuántas subclases especificas necesita. El objeto, que va a jugar el rol de contexto (en VFP es típicamente el formulario, o contenedor padre), necesita exponer una interfaz apropiada a sus clientes potenciales y además, requiere una propiedad que es utilizada para guardar la referencia actualmente activa de implementación de objeto.

¿Cómo implemento una estrategia?

El siguiente ejemplo ilustra cómo se puede utilizar un patrón de estrategia para implementar una solución a los impuestos de venta que hemos discutido en el preámbulo de este tópico.

Lo primero que necesitamos hacer es definir una clase abstracta que tenga una propiedad para el impuesto aplicable. Tiene además, métodos para calcular el impuesto utilizando el valor pasado a la propiedad. Luego creamos tantas subclases de esta definición como tarifas de impuestos trabajamos - una subclase para cada tarifa.

Ahora necesitamos considerar cómo decidimos cuál de varias subclases necesitamos en cada momento dado. Para determinar esto, necesitamos relacionar las diferentes localidades y las tarifas aplicables (la "localidad") a su subclase apropiada. Existen, por supuesto, muchas formas en que podemos hacer esto y la más sencilla es definir la lista de localidades (por ejemplo, ciudades) y la tarifa de impuesto que se aplica en cada una. Una vez que tenemos una subclase específica para cada tarifa de impuesto, podemos derivar la subclase necesaria para cada localidad.

Aunque aun necesitamos una tabla, un registro para cada ciudad, podemos reducir la duplicación ya que nombramos las clases por la tarifa que aplica. El resultado es algo como esto:

CiudadTarifaSub-clase
Akron5.75Tax575
Canton5.25Tax525
Grove City0.00Tax000
Kent5.75Tax575

En otras palabras, utilizamos la tarifa aplicable para identificar las subclases que necesitamos con la consecuencia de que no necesitamos la tercera columna de la tabla - podemos inferirlo directamente de la Tarifa.

Entonces, ¿cómo podemos programar esto en un formulario? Bien, es muy sencillo. Todo lo que necesitamos es una lista desplegable de las localidades basada en nuestra tabla Ciudad/Tarifa (City/Rate). Cuando se selecciona la ciudad podemos comprobar la imposición fiscal aplicable. Teniendo esto, podemos derivar el nombre de la subclase a instanciar, y debido a que todas las subclases derivan de un padre común sabemos que todo lo que necesita es llamar al método adecuado del objeto, pasando los parámetros esperados - en este caso, dos parámetros - la clave contextual (la que define la tarifa aplicable) y el "precio" al que se calcula el impuesto. La clave contextual es usada para generar el nombre de la subclase requerida. Si la clase instanciada actualmente no es la correcta, simplemente instanciamos la correcta, como sigue.

LPARAMETERS tcContext, tnPrice
WITH ThisForm
  *** Define el nombre del objeto de la etrategia
  lcStrategy = "CNTTAXSTRAT" + PADL( tcContext, 2, '0' )
  *** Ya está ahi
  IF ISNULL( .oStrategy ) OR NOT UPPER( .oStrategy.Name ) == lcStrategy
    *** Necesitamos crear este
    .oStrategy = NEWOBJECT( lcStrategy, 'Ch15.vcx' )
  ENDIF
  *** Ahora, es sólo llamar al método CalcTax() method y pasar el precio
  lnTax = .oStrategy.CalcTax( tnPrice )
  RETURN lnTax
ENDWITH

Aunque esta es una forma en que puede ser implementada una estrategia y tiene sentido en este escenario, otros escenarios pueden requerir diferentes implementaciones. Por ejemplo, considere la aplicación de descuentos en una orden. La orden de entrada del formulario (el contexto) puede necesitar la aplicación de diferentes tipos de descuentos a una orden:

  • Descuento al valor del elemento, aplicado a una línea de un elemento cada vez.
  • Descuento del valor de la orden, aplicado al valor total de la orden.
  • Elemento de descuento especial o promoción.
  • Descuento a clientes, aplicado al valor de la orden basado en el usuario

Una posible interfaz utilizará botones de comandos (los clientes), de tal forma que el operador pueda determinar cuándo aplicar un tipo de descuento específico. En este escenario puede ser enteramente apropiado, y mucho más simple, que cada botón sea el responsable de instanciar la subclase de estrategia del descuento apropiado y almacenar su referencia a la propiedad del formulario. (Recuerde que mientras podemos hacer este tipo de cosas fácilmente en Visual FoxPro, otros lenguajes tendrían que pasar una referencia explícitamente) El formulario puede manipular el cálculo actual y ocuparse de que el resultado sea tal que el código no tenga que ser duplicado en cada botón.

Resumen de patrones de estrategia.

Hemos mostrado una implementación posible del patrón de estrategia y esbozado otro. La ventaja de la estrategia es tal que evita la necesidad de tener que incrustar implementaciones alternativas en el código permitiéndonos crear objetos separados, que van a compartir una interfaz común, para cada opción y solamente instanciar el objeto necesario en tiempo de ejecución. Como otros patrones los detalles de la implementación pueden variar con las circunstancias pero el patrón como tal no cambia.

22 de abril de 2017

Patrones de diseño - El puente

Artículo original: Design Patterns - The BRIDGE
http://weblogs.foxite.com/andykramek/archive/2006/12/10/3009.aspx
Autor: Andy Kramek
Traducido por: Ana María Bisbé York


Como he prometido, comienzo una pequeña serie sobre Patrones de diseño, el primero a mirar es el "Puente" - frecuentemente referenciado como la "madre de los patrones de diseño", porque es el patrón de diseño más básico y usted va a descubrir que es más familiar con los patrones en general, cuando encuentre el puente (de alguna forma) como base de casi todos los otros patrones.

OK, entonces, ¿Qué es un puente?

La definición formal de un puente, dada en "Design Patterns, Elements of Reusable Object-Oriented Software" por Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides es que un Puente es:

"Desacoplar una abstracción de su implementación de tal forma que las dos puedan variar independientemente."

Impresionante, ¿eh? ¿Hacemos esto realmente en nuestro código? La respuesta corta es que probablemente nosotros debemos hacerlo más de lo que nos damos cuenta. Vamos a tomar un ejemplo común.

Al escribir código en nuestros formularios y clases queremos naturalmente atrapar las cosas que pueden salir mal y usualmente (considerándonos desarrolladores) queremos decir a los usuarios cuándo van a ocurrir. La solución más obvia y sencilla, es incluir un par de líneas de código en el método apropiado. El resultado puede verse así:

IF NOT <Una función que devuelve True/False>
  lcText = "Chequeo fallido para devolver el valor correcto" + CHR(13)
  lcText = lcText + "Presione cualquier tecla para re-entrar el valor"
  WAIT lcText WINDOW
  RETURN .F. 
ENDIF

En toda nuestra aplicación pueden existir docenas de situaciones donde mostramos una ventana del tipo Wait, para mantener al usuario al corriente de lo que está ocurriendo, y esto funciona perfectamente bien hasta que ocurre una de estas dos cosas. Cualquiera de nuestros usuarios decide que realmente odia estas molestas ventanas de ligera espera "wait windows" y preferirá una ventana estilo "message box", o peor, tendremos que implantar el código en un entorno que no soporta una ventana tipo "wait windows". Es posible lograrlo con un componente COM, o en un formulario Web. Ahora podemos ir y buscar cada ocurrencia de este código por nuestra aplicación, para cambiarla para que garantice el nuevo requerimiento. No se usted; pero en mi experiencia, las posibilidades de hacer esta tarea correcta la primera vez (no olvide ninguna ocurrencia y re-codifique cada una perfectamente) están tan cerca de cero que no se pueden distinguir del mismo cero.  Incluso, si confiamos en que nuestros resultados son correctos, tendremos que hacer un grupo de verificaciones para asegurarnos.

Otro caso es cuando estamos trabajando con colegas. Suponga que en uno de mis formularios llamo a una función del sistema y, siendo un desarrollador cuidadoso, verifico que el resultado es el esperado, y en caso de error, el código muestra un mensaje del tipo:

lcText = "El valor devuelto por la función es incorrecto"

Mientras tanto, mi compañero, escribiendo un pedazo de código similar, en otra parte de la aplicación, escribe sus validaciones de la llamada a la misma función de esta forma:

lcText = "No se ha completado la tarea requerida"

Entonces, incluso si ambos mostramos el texto de igual forma, el contenido es diferente, incluso pensando en el usuario final, está ocurriendo lo mismo ! ¿Es confuso? Puede aportar. Peor, no se ve pulido ante el usuario final.

Entonces, ¿qué tiene que ver esto, con el patrón Puente? Bueno, la razón de que tenemos ese problema es porque hemos fallado al reconocer que estábamos acoplando una abstracción (mostrando un mensaje al usuario) y su implementación (la ventana "Wait window" o "Messagebox"). Si hubiéramos hecho un puente en su lugar, hubiéramos evitado el problema. Así es como se muestra el mismo código si lo hubiéramos implementado, utilizando un patrón de puente.

IF NOT <Una función que devuelve True/False>
  This.loMsgHandler.ShowMessage( 9011)
  RETURN .F.
ENDIF

¿Ve la diferencia? No sabemos nada más, ni nos preocuparemos, por saber qué mensaje se mostrará en realidad o cómo se manipulará. Todo lo que necesita conocer es dónde tomar una referencia al objeto que va a manipular el mensaje por nosotros, y el identificador que dice al controlador qué mensaje deseamos mostrar. Por supuesto,es un requerimiento fundamental que todos los posibles controladores implanten la interfaz adecuada, en este caso el método ShowMessage().

Es este el origen de la referencia, de lo que es un "puente". En este ejemplo la propiedad "oMsgHandler" proporciona el puente entre el código que requiere un mensaje y el mecanismo para manipularlo. Ahora, todo lo que necesitamos es cambiar la forma en que nuestro mensaje es manipulado, para cambiar el objeto de referencia guardado en esta propiedad. Esto es algo que pudo haberse hecho incluso en tiempo de ejecución, dependientemente del entorno en el que el objeto padre se haya instanciado( y el mecanismo para hacerlo es un ejemplo de otro patrón llamado "Estrategia", que vamos a ver después. Esta estrategia desacopla exitosamente la abstracción de la implementación y, como resultado, nuestro código, es mucho más reutilizable.

¿Cuáles son los componentes de un puente?

Un puente tiene dos componentes esenciales, la "abstracción", que es el objeto responsable de inicializar una operación, y la "implementación", que es el objeto que la lleva a cabo. La abstracción conoce su implementación porque guarda una referencia a la misma, o porque lo posee (por ejemplo, lo contiene). Observe que frecuentemente ocurre que la abstracción crea la implementación, que no es un requerimiento absoluto del patrón. Un puente también podría hacer uso de una referencia ya existente para una misma implementación de objeto. De hecho, diseñar puentes de esta forma, puede ser muy eficiente porque diferentes objetos abstractos pueden utilizar el mismo objeto implementación. Debido a que Visual FoxPro tiene un modelo de contenedores muy bueno, la mayoría de los desarrolladores notarán que lo han utilizado (no intencionalmente) para implementar Puentes con más frecuencia de lo que imaginan. La mayoría de los objetos "administradores" ( por ejemplo: El Administrador de Formularios, El Administrador de archivos, el Administrador de mensajes), son en realidad ejemplos de patrón de Puente.

Sin embargo, no confunda el envío de mensajes con el puente. Si un método de un botón de comandos llama a un método en su formulario padre, esto implementa directamente la acción necesaria, que es enviar un mensaje, no es un puente. Sin embargo, si el método del formulario va a llamar a otro objeto para que lleve a cabo la acción, entonces tenemos un puente.

Es interesante, otra posibilidad que ocurre cuando un formulario (u otro contenedor) tiene una propiedad que guarda una referencia a un objeto de implementación. Un objeto contenedor (por ejemplo un botón de comandos en un formulario) puede acceder a esta propiedad directamente y tomar una referencia local al objeto de implementación. Puede entonces, llamar a los métodos del objeto de implementación directamente. Ahora, ¿este es un puente, o un envío de mensajes? De hecho, probablemente podría argumentar ambos lados del caso con igual justificación. Sin embargo, la respuesta en realidad no importa, este asunto surge sólo porque Visual FoxPro expone las propiedades como "Públicas" de forma predeterminada, y también implementa punteros hacia atrás, los que permiten a los objetos dirigirse a sus padres directamente. En la mayoría de los entornos orientados a objeto, es sencillamente imposible para los objetos comportarse tan toscamente entre ellos.

Entonces, para implementar un puente necesita identificar qué objeto va a realizar la función de abstracción y cuál la implementación. Una vez que lo haya definido, todo lo que queda por decidir es cómo el puente entre los dos será codificado y esto dependerá enteramente del escenario.

19 de abril de 2017

Análisis y Diseño Orientado a Objetos. Enfoque iterativo

Autor:
Jim Booth

Texto original:
When Does It Happen?

Traducido por:
Ana María Bisbé York


Introducción

El desarrollo de sistemas orientados a objeto es nuevo para muchos de nosotros. Ser nuevo en esto supone muchos cambios. Entre ellos está el proceso de planificación y dirección del proyecto. La mayoría de nosotros ha desarrollado proyectos antes. En aquellos proyectos trabajábamos con los usuarios para saber cuál era el sistema y cómo era que debían hacerse las cosas. Nosotros diseñábamos los programas y escribíamos las aplicaciones. Entonces, ¿qué tiene de especial la orientación a objetos?

Uno de los aspectos de la orientación a objetos es el hecho de que es relativamente nuevo de usar. Debido a esta novedad estamos casi constantemente encontrando barreras de ideas a superar. Este escrito está diseñado para exponer uno de los muchos aspectos del desarrollo orientado a objetos, el análisis y diseño y para ayudarnos a encontrar el “sendero en la jungla”. El método de análisis y diseño que vamos a examinar es llamado Enfoque iterativo, toma este nombre porque todo el proceso de desarrollo es logrado a través de una serie de iteraciones donde cada una abarca el proceso entero para el análisis a través de pruebas. Durante cada una de estas iteraciones somos capaces de retroalimentar la información de las primeras etapas del proyecto.

¿Cuándo debemos usar un proceso interactivo?

Martin Fowlr, en su libro “UML Distilled" (disponible en www.amazon.com), dice”Debe utilizar un desarrollo iterativo solo en los casos en que desee obtener éxito” El no está exagerando. Nadie puede asegurar completamente ninguna fase del ciclo de desarrollo en un simple paso. Estos son simplemente muchos detalles a los que dirigirse en cada etapa de desarrollo. La metodología puede ser usada tal que no solo permita revisar las etapas anteriores sino que también las complemente. La metodología iterativa es justo eso. Requiere que grandes proyectos sean partidos en pequeñas piezas y que cada pieza sea desarrollada mediante un proceso iterativo de análisis, diseño, implementación y pruebas.

¿Por qué hacer iteraciones?

Las iteraciones nos permiten enfocar un subconjunto del proyecto completo de tal forma que lo podemos terminar en detalle. Frecuentemente vamos a descubrir nuevos problemas y requerimientos durante el proceso de creación de uno de sus subsistemas. Estos nuevos descubrimientos pueden ser fácilmente incorporados en una iteración posterior sin desechar lo que se ha avanzado hasta entonces.

Este proceso nos permite probar cada subsistema independientemente y asegurar su propia funcionalidad. Esto significa que cuando alcancemos la etapa final del desarrollo - la integración entre los subsistemas como un todo - podremos concentrarnos en la integración sabiendo que cada subsistema está ya completamente probado.

Trabajando lo más temprano posible con los aspectos de alto riesgo para el proyecto, seremos capaces de reducir la influencia de estos riesgos en el cronograma completo del proyecto.

Durante la implementación de los subsistemas nuevos casos de uso pueden ser descubiertos. Estas situaciones nuevas pueden ser planificadas para la siguiente iteración.

¿Qué es el enfoque iterativo?

El enfoque iterativo no es nada nuevo ni revolucionario. Muchos de nosotros hemos creado sistemas por esta vía hace mucho tiempo. Martin Fowler clasifica las fases de un proyecto iterativo como Iniciación, Elaboración, Construcción y Transición. Cada una de estas fases constituye un punto diferente en la continuidad del proyecto hasta el final del mismo.

El proyecto comienza con la fase de Iniciación. Durante esta fase, la que discutiremos con más detalle más adelante, vamos a invertir un tiempo explicándonos qué es el proyecto y la mejor idea de cómo hacerlo. Al final de la fase de iniciación debemos tener una idea bastante acertada del alcance del proyecto. Los detalles no serán obtenidos; pero la visión general del sistema está aquí. En este punto podemos hacer nuestro primer corte del proyecto en piezas. Estas piezas deben estar suficientemente encapsuladas que permitan crearlas independientemente. Cada una de estas piezas satisface un subconjunto de requerimiento del sistema completo.

La mayor ventaja de este método es que puede identificar el riesgo involucrado con el proyecto y tenerlo en cuenta en la dirección del mismo. Esto nos ayuda a evitar, que conociendo todas las cosas que podrían ir mal haya que esperar a que empiecen a fallar.

Estos riesgos pueden ser esparcidos por todo el proyecto y continuar teniéndolos en cuenta.

Categorías de riesgo

Los riesgos que enfrentamos en el desarrollo del proyecto los podemos dividir en cuatro categorías. Estas categorías son: riesgos de requerimiento, riesgos tecnológicos, riesgos de habilidades y riesgos políticos. Cualquier proyecto con un alcance relativo tendrá algunos riesgos asociados con cada una de estas características. Ignorar o negar la presencia de estos riesgos significaría matar el proyecto. Estos riesgos pueden ser solo superados si no son bien manejados. Manejar los riesgos requiere de conocer cual es el riesgo y tener un plan para lidiar con ellos.

Miremos las categorías de riesgo

Riesgos de Requerimientos

La categoría de riesgos de requerimientos incluye aquellas cosas que ponen en riesgo el proyecto desde la temprana etapa de descubrimiento de requerimientos del sistema. Podemos crear el mejor sistema de software que nadie ha visto jamás para manipular la logística del papel baño de las fuerzas armadas de los EUA; pero si la necesidad fue administrar la afiliación del club de oficiales su proyecto será un fracaso.

Estar seguro de que estamos creando el proyecto correcto, este es el logro principal de administración de requerimiento de riesgos. Se generarán menores costos en la medida que se reconozcan estos riegos lo antes posible en el proyecto. Solo imagine la cantidad de trabajo (y costo) perdido si nosotros descubrimos sobre el club de oficiales en el día que vamos a instalar la aplicación logística.

El uso de casos es imprescindible en la administración de riesgos de requerimiento. El uso de casos predecirá el comportamiento que será alcanzado por el sistema. Esto le va a dar una clara visión de cómo el sistema y los usuarios interactúan.

Desarrollar un dominio conceptual de los negocios. El dominio conceptual debe darle una clara comprensión de qué cosas y en qué negocios interactúan. Un concepto general de cómo los negocios trabajan.

Luego, puede combinar el uso de casos con el modelo de dominio para crear el modelo de diseño. Tomar tiempo para identificar la información necesaria para crear estos modelos le dará una detallada información de los usuarios y los expertos de negocios sobre el proyecto. Sin dudas, usted reducirá los riesgos de requerimiento considerablemente.

Riesgos Tecnológicos

El primer riesgo tecnológico viene al olvidar la relación con la orientación a objetos. ¿Es la Orientación a Objetos un nuevo paradigma para usted o su equipo? Si es así, entonces el riego tecnológico aquí es bastante grande. ¿Enfrentará ese riesgo? ¿Va a disponer de presupuesto para entrenarse en estas técnicas, usted y su equipo? ¿Se puede permitir tener un consultante que esté disponible para dudas, reuniones, y revisiones periódicas del sistema?

¿Qué hay del problema con las nuevas tecnologías? Este nuevo proyecto requiere que los informes sean publicados en un sitio web. El sitio web debe permitir criterios dinámicos en los informes. Hey, esto de la web debe ser simpático, ¿no?

Si, mucha diversión en lo que usted comienza tratando de seleccionar el software y hardware verá y encontrará los requerimientos. Así podrá calcular la calidad de varias tecnologías que necesita incorporar para el proyecto rápidamente encontrará que este es el próximo mayor riesgo.

¿Qué ocurre si selecciona el mejor software de servicio web y el mejor software de base de datos remota solo para encontrar que no son compatibles y no trabajan bien juntos? Puede asegurar que cualquier nueva tecnología podría causarle problemas. Aquellos problemas tomarán tiempo de su proyecto.

La solución de estos riesgos tecnológicos es similar a los riesgos de requerimientos, consultores, entrenadores o tutores con habilidades específicas en esas tecnologías.

Otro método para minimizar los riesgos tecnológicos en el proyecto es crear prototipos. Crear muchos prototipos, especialmente para aquellos aspectos del sistema que usan las tecnologías más avanzadas y menos entendidas.

Riesgos de Habilidades

¿Tiene su equipo las habilidades para cumplimentar este proyecto? ¿Qué haría si a la única persona que sabe de aspectos de web le ofrecen un trabajo dónde le pagan 10 veces más justo dos semanas antes de que comience el proyecto?

¿Qué recursos existen en su área inmediata para recibir ayuda exterior? Puede necesitarla en distintos momentos de su desarrollo.

Usar un tutor puede colisionar con estos problemas de experiencias. Entrenamientos es otro factor a considerar. Nuevamente, periódicas revisiones del sistema hechos por un reconocido experto son invaluables. Finalmente lea tanto como pueda sobre las nuevas tecnologías y provea a su equipo de abundante material también en esas áreas.

Riesgos políticos

Aunque quiera creerlo o no cada proyecto está cargado de riesgos políticos. Si no tiene experiencia para lidiar con este tipo de problemas, pues tenga pronto a bordo alguien que sepa. Los riesgos políticos tienen un potencial completamente destructivo para el proyecto. Una compañía está llena de gente que tiene sus propias agendas y trabajan muy duro para cumplirlas. Los riesgos políticos se pueden mostrar como simples batallas alrededor del presupuesto en un sabotaje abierto.

Debe saber el entorno político que rodea al proyecto que va a llevar a cabo. Conocer sus amigos y enemigos. Sepa de esta gente de la cual usted puede depender y los que van a luchar contra sus esfuerzos.

Un buen libro para aprender más sobre el manejo de riesgos políticos es “Death March” escrito por Edward Yourdan. El libro es publicado por Prentice-Hall y su ISBN es 0-13-748310-4 (disponible enwww.amazon.com)

Las fases

Ahora vamos a trabajar sobre las fases del ciclo de un desarrollo de proyecto interactivo. Llamará a estas fases: iniciación, elaboración, construcción y transición. Vamos a discutir cada una de estas fases, que serán revisadas como cada punto en el proyecto de desarrollo.

La primera fase de iniciación va a identificar un conjunto de sub-proyectos a construir. Cada uno de estos sub-proyectos va a constar de sus propias fases de elaboración y construcción. Finalmente la fase de transición es donde todos los sub-proyectos se recuperan juntos.

Iniciación

La iniciación es el inicio del proyecto. Esta fase puede manifestarse en una o diferentes formas. Puede abarcar desde una conversación informal tomando un café hasta una reunión bien estructurada con una gran cantidad de personas.

El propósito de esta fase es trabajar en un resumen global del proyecto. Martin Fowler dice: “La iniciación deben ser días de trabajo en los que se debe considerar si vale la pena trabajar durante meses de desarrollo de una mayor investigación durante la elaboración.

El objetivo de la iniciación es tener una buena idea de los casos de negocios para el proyecto. ¿Cuánto puede este proyecto superar la línea principal? Otro aspecto es obtener una idea del alcance del proyecto. ¿Cuánto va a costar este proyecto?

Al final de la fase de iniciación el patrocinador del proyecto está comprometido solo a dar una mirada seria al proyecto.

Dependiendo del tamaño del proyecto puede incluir en si mismo algún grado de análisis con el objetivo de tener la idea del alcance del proyecto en esta etapa. La iniciación puede ser desde una corta conversación hasta un completo análisis de factibilidad que tome muchos meses de trabajo.

Elaboración

Ya cuando está en el punto de comenzar el proyecto, empieza la etapa de elaboración. Este es el punto donde tiene ya una idea muy general de lo que será el proyecto. Quizás usted pueda decir “Vamos a crear una aplicación que va a controlar las actividades de una bolera incluyendo la planificación de las carrileras y las ligas, imprimir los horarios de las ligas los horarios de los trabajadores, guardar la información de la recolección en caja y el control del mantenimiento de los equipos.”

La información requerida puede ser un conjunto de cosas diferente que requerirían mucho texto; pero no nos vamos a detener en este punto.

Las preguntas a responder en este punto son: ¿Qué es realmente lo que va a construir? ¿Cómo lo va a construir? ¿Qué tecnologías estará utilizando para ello?

El mayor enfoque para esta fase debe estar en los riesgos con los que se va a enfrentar. ¿Cuáles son las cosas que pueden descarrilar su proyecto y cómo debe manipularlas? Debe identificar estos riesgos en dependencia de cuánto hay en ellos de problema potencial. El mayor riesgo debe tener la mayor atención.
Estos riesgos deben ser catalogados en los grupos descritos en la sección anterior. Hay que encontrar los riesgos que necesita para comenzar a hacer el análisis y diseño detallado un sistema completo.

En primer punto para comenzar es el modelo de dominio del sistema. Una vez que tenga el modelo de dominio, es necesario moverse entre los casos de uso del sistema y finalmente combinar el modelo de dominio y los casos de estudio en un modelo de diseño.

El Modelo de dominio

El modelo de dominio es un esquema bastante general de cómo opera el negocio. Este modelo describe el mundo en el cual este sistema existirá. Necesitamos una imagen conceptual del negocio de conjunto, como accionar con el, qué cosas haces, cómo hace esas cosas y como encajan todas juntas. El modelo de dominio nos mostrará esto.

El modelo de dominio contiene una mínima cantidad de detalles. Pueden ser diagramas desconectados y estos diagramas pueden ser combinados con libertad con notas y comentarios. El modelo de dominio va a ser la base para un modelo más detallado. La figura1 es un ejemplo de modelo de dominio para un sistema ficticio.


Figura 1 – El modelo de dominio para un sistema de administración de una bolera

Note en la figura 1, no existen detalles dentro de los objetos identificados. Estos objetos son simplemente llamados partes y sus relaciones entre ellos son descritas. El modelo nos muestra una visión general del negocio y cómo funciona.

El éxito de este enfoque es que vamos a lograr una gran imagen del sistema. Para tener la concepción correcta del paquete total, desde el comienzo podemos planificar el impacto de cada pieza del sistema en las otras piezas.

Cuando el modelo de dominio está creado podemos proceder a la identificación de los casos de uso.

Casos de Uso

Martin Fowler describe los casos de uso como “Una interacción típica que el usuario tiene con el sistema para alcanzar resultados. Los casos de estudios proporcionan muchos beneficios al desarrollador. Los casos de estudio son interacciones que el usuario tiene con el sistema, así como es fácilmente comprensible por los usuarios y además provee de una retroalimentación efectiva para este grupo. Los casos de estudio son una especificación funcional. Ya que describen las cosas como se hace de la perspectiva del usuario. Casos de uso para un procesador de texto pueden ser: “hacer texto en negrita”, “hacer texto en cursiva”, “copiar un texto de un lado a otro”, o “crear un índice a un documento”. Estos ejemplos muestran que un caso de uso puede ser una pequeña acción o puede abarcar un proceso largo y complejo.

Ejemplos de casos de uso para nuestra bolera pudieran ser: “El usuario necesita ser capaz de planificar las carrileras para las ligas. Puede existir solo una liga para cada carretera en un momento dado. El espacio de tiempo para cada liga es de 2 horas. Cada liga va a tener la cantidad de carrileras que requiera para un conjunto de juegos de ligas, y “El usuario necesita imprimir la planificación para cada liga” En contra de lo que usted piensa el rango de complejidad para estos casos de uso varían mucho. El texto para el caso de uso debe ser suficientemente específico para los usuarios para comprender la idea del caso y para los desarrolladores a tener una idea de que afecta la implementación y la funcionalidad.

Los casos de uso han sido dibujados como se ve en la figura 2


Figura 2 – Un diagrama de caso de uso.

En la figura 2 puede ver que los casos de uso se representan por un óvalo con el nombre del caso de uso dentro. La “figurita” es llamada el actor y representa la persona o cosa que usa la funcionalidad. El actor puede ser un usuario o puede ser otro sistema de computadoras o incluso una máquina en la empresa.

El diagrama de caso de uso le permite ver una idea general de cómo encajan juntos.

Una vez que el modelo de dominio se ha creado y se han identificado los casos de uso se puede proceder con el modelo de diseño.

El Modelo de Diseño

El modelo de diseño identifica los objetos que el sistema contendrá y los casos de estudios las actividades que el sistema va a automatizar. El modelo de diseño es la combinación de estos dos aspectos. El modelo de diseño es también un modelo abstracto en el que no se incluye un alto nivel de detalle. Los diagramas detallados se crearán más tarde en el ciclo de desarrollo.

El propósito de este modelo de diseño es describir la combinación de la información en el modelo de dominio y el comportamiento de los casos de uso en el estilo que nos muestra cómo estas cosas encajan juntas. El modelo de diseño también provee una arquitectura reutilizable que permite para futuras extensiones del sistema.

La figura 3 es un ejemplo de un modelo de diseño para nuestro sistema de la bolera. El modelo de la figura 3 está muy lejos de ser completo, tiene solo la intención de demostrar la idea de la combinación del modelo de dominio con los casos de uso


Fig3 – Modelo de diseño

Note que la figure 3 añade el comportamiento de los casos de uso a los objetos del modelo de dominios. Los diagramas serán expandidos en el actual diagrama en un momento posterior. Aquí estamos tratando de tener una visión más completa del sistema entero.

¿Cuán lejos iremos con los diagramas?

Cuando leemos acerca de análisis y diseño de sistemas estamos viendo constantemente diferentes diagramas. Un diagrama para esto y diagramas para lo otro. ¿Cuántos diagramas son suficientes y cuántos son demasiado? ¿Qué debemos usar además de estos diagramas para la comunicación?

Los diagramas pueden ser usados cuando aportan al entendimiento del sistema y deben ser evitados cuando causan confusión. Ward Cunningham dijo: “Seleccionar cuidadosamente los memos escritos puede fácilmente sustituirse por la documentación de diseño comprensible y tradicional. Excepto en puntos aislados. Eleve esos puntos y olvídese de lo demás”

¿Qué significa esta cita? ¿Significa que debemos o no dibujar los diagramas? No, significa que los diagramas deben ser hechos cuando ayuden al entendimiento de los sistemas. Si hay diagramas que no enriquecen el entendimiento, entonces debemos olvidarnos de ellos. Muchos de los memos cortos bien escritos pueden mejor y más claramente describir nuestro punto que un complejo y confuso diagrama. En estas situaciones deje el diagrama y escriba el memo.

¿Cuándo está completa la fase de elaboración?

La fase de elaboración de un proyecto está completa cuando todos los riesgos relacionados con el proyecto están bien definidos y existen los planes para manipularlos. Además todas las tecnologías han sido identificadas y los modelos existentes para el dominio, caso de uso y diseño. Además, los desarrolladores están provistos de los estimados para la creación de cada caso de uso.

Cada caso de uso se convertirá en uno de los sub-proyectos en la fase de construcción. Esto es donde la iteración juega su papel. Durante la construcción vamos a crear cada caso de uso por separado y permitir que la experiencia de crear cada uno influya en el diseño en las otras.

Construcción

Una vez que la elaboración está completa entramos en la fase de construcción. En la construcción de cada caso de uso, los manipulamos como un proyecto ya que será construido a través del análisis, diseño, codificación, pruebas y proceso de iteración. Una iteración termina con un demo a los usuarios del subsistema completado.

Durante la construcción de un caso de uso, frecuentemente vamos a descubrir cambios que van a repercutir en nuestro diseño preliminar para otros casos de uso. Estos descubrimientos van a retroalimentar los diseños de casos de uso. También vamos a descubrir nuevos casos de uso que fueron realizados durante la fase de elaboración. Estos pueden adicionarse al proyecto y ser planificados para una construcción posterior.

Cuando completamos la construcción de cada caso de uso vamos a integrarlos con la construcción previa de casos de uso para trabajar a través de sistemas completamente integrados. Podemos reingresar la construcción de los casos de uso previamente completados y las necesidades originadas. Este proceso que da el enfoque del nombre de desarrollo iterativo, como un sistema completo en la construcción del cual hay una serie de iteraciones en cada subsistema.

Transición

La transición es la fase final del enfoque de desarrollo iterativo. La transición manipula esos aspectos que no fueron referenciados durante la construcción. Es posible que no haya alguna integración final a hacer después que todos los subsistemas hayan sido creados. Un buen ejemplo del problema que puede ser referenciado durante la transición es la optimización del rendimiento.

La optimización usualmente sacrifica claramente y facilita la integración a favor del mejoramiento del rendimiento. Esto no es siempre así, ya que nosotros queremos hacer pronto en el proyecto de desarrollo tanto como que el va a incrementar las dificultades en la creación del proyecto. En su lugar dejaremos la optimización para la fase de transición cuando todo el subsistema esté creado y probado.

La optimización es además un logro fugaz. Frecuentemente, nosotros, como desarrolladores, percibimos un problema de rendimiento cuando los usuarios no lo notarían nunca. Nosotros vamos también a sobre ver el problema del rendimiento cuando los usuarios difícilmente lo reconozcan. Si empezamos a optimizar el sistema antes que los usuarios puedan decir dónde son los puntos que ven lentos, gastaremos mucho tiempo y esfuerzo en lugares erróneos.

La transición puede ser pensada como el período de tiempo entre liberar una versión beta y la versión final del proyecto. Será como un bug fijo, en los enganches funcionales. Optimización de rendimiento, y otras cosas hechas durante esta fase. A veces podemos descubrir un caso de uso enteramente nuevo que necesitamos para no ser creado. El enfoque de desarrollo iterativo nos permite facilitar el proceso de este nuevo caso de uso y luego re-entrar en la fase de transición.

Resumen

El análisis y diseño orientado a objetos es frecuentemente visto como un enorme paquete de complejos diagramas que pueden significar poco o nada para los usuarios del sistema. Es frecuentemente visto como un proceso que envuelve una gran cantidad de conceptos abstractos y teóricos modelos que realmente aportan muy poco al proyecto.

No tiene que ser de esta forma. El análisis y diseño orientado a objetos puede ser un enfoque muy pragmático del desarrollo de un sistema. No tienen que ser frases distintas que nunca sean revisadas. El uso de un enfoque iterativo, ya que este proceso puede ser un aspecto que vive y respira en el desarrollo del sistema. Puede enriquecer nuestra habilidad de encontrar la necesidad de los usuarios y el control de los riesgos asociados con el proyecto. Cuando se usan efectivamente, el enfoque iterativo puede realmente reducir el tiempo requerido para la creación de un proyecto.

31 de marzo de 2017

Evitar salir de los TextBox al borrar con BackSpace

Mucha gente desea saber si es un bug de los controles TextBox, y cómo evitarlo. Aquí veremos la explicación sobre el tema.

No es un error, es el comportamiento normal de VFP, y tanto no es un error que puedes cambiarlo, para esto, puedes codificar lo siguiente en el evento KeyPress de tu clase TextBox personalizada (que así lo deberíamos tener todos):

IF nKeyCode = 127 AND (LEN(ALLTRIM(this.Value))-1 < 0)
  NODEFAULT
ENDIF

Un pequeño código para probarlo:

oForm = CREATEOBJECT("MyForm")
oForm.Show(1)
DEFINE CLASS myForm AS Form
   ADD OBJECT TextBox1 AS MyTextBox WITH Top=20, Width=150, Height= 25
   ADD OBJECT TextBox2 AS MyTextBox WITH Top=50, Width=150, Height= 25
   PROCEDURE Init
      SET CONFIRM ON 
      lcMessage = "Escriba un texto en las cajas, borre el contenido con BackSpace"+;
                  CHR(13)+"Verá el comportamiento cambiado"
       MESSAGEBOX(lcMessage,64,"Aviso")
   ENDPROC
ENDDEFINE
DEFINE CLASS myTextBox AS TextBox
   PROCEDURE KeyPress
   LPARAMETERS nKeyCode, nShiftAltCtrl
     IF nKeyCode = 127 AND (LEN(ALLTRIM(this.Value))-1 < 0)
        NODEFAULT
      ENDIF
ENDDEFINE

El mismo ejemplo, pero usando BindEvents ( a partir de VFP8):

oForm = CREATEOBJECT("MyForm")
oForm.Show(1)
DEFINE CLASS myForm AS Form
   ADD OBJECT TextBox1 AS TextBox WITH Top=20, Width=150, Height= 25
   ADD OBJECT TextBox2 AS TextBox WITH Top=50, Width=150, Height= 25
   PROCEDURE Init
      SET CONFIRM ON 
      lcMessage = "Escriba un texto en las cajas, borre el contenido con BackSpace"+;
                  CHR(13)+"Verá el comportamiento cambiado"
       MESSAGEBOX(lcMessage,64,"Aviso")
       FOR EACH oControl IN This.Controls 
         IF oControl.BaseClass = 'Textbox'
            BINDEVENT(oControl, 'KeyPress',This,'MyKeyPress')
         ENDIF
       ENDFOR
   ENDPROC
   PROCEDURE myKeyPress
   LPARAMETERS nKeyCode, nShiftAltCtrl
     IF AEVENTS(laControl,0) > 0
       IF nKeyCode = 127 AND (LEN(ALLTRIM(laControl[1].Value))-1 < 0)
          NODEFAULT
       ENDIF
     ENDIF
   ENDPROC
ENDDEFINE

Nota: Para ejecutar los códigos anteriores basta con copiar y pegarlos en tu command window, seleccionar lo y presionar ENTER. Verás como el comportamiento ha cambiado.

Espero les sea de utilidad.

Esparta Palma

26 de marzo de 2017

Simular CkeckBoxes en un control ListBox

Para mostrar los CheckBoxes en un ListBox y enganchar todo el asunto hasta una tabla, se debe utilizar la propiedades Picture (entre otras cosas) del ListBox. En realidad no son CheckBoxes, son iconos. De hecho, los que estoy usando son más como luces (yo quería dar un ejemplo que se pueda ejecutar desde un PRG). Se puede usar cualquier imagen que desee ... encontrar un par de imágenes realmente buenas de casillas de verificación que están marcadas y desmarcadas y simplemente reemplazar las correspondientes propiedades del Formulario.

El ListBox tiene un Cursor como RowSource y uno de los campos "Checked" se utiliza para realizar un seguimiento de qué registros están marcados o desmarcados. Incluso podría dar un paso más y cambiar el campo marcado a numérico y tener una casilla de verificación MultiEstado con los valores 0, 1 y 2 y utilizar tres imágenes. (Copie y peque el siguiente código en un archivo PRG y ejecútelo desde VFP)

PUBLIC oForm
oForm = CREATEOBJECT("clsListCheckBox")
oForm.VISIBLE = .T.
READ EVENTS

DEFINE CLASS clsListCheckBox AS FORM

    TOP = 1
    LEFT = 0
    HEIGHT = 473
    WIDTH = 287
    DOCREATE = .T.
    CAPTION = "Listbox With Checkboxes"
    WINDOWSTATE = 0
    NAME = "clsListCheckBox"
    AlwaysOnTop = .T.
    CheckIcon = HOME() + "Graphics\Icons\Misc\MISC15.ICO"
    Uncheckicon = HOME() + "Graphics\Icons\Misc\MISC13.ICO"
    SHOWWINDOW = 2

    ADD OBJECT list1 AS LISTBOX WITH ;
        HEIGHT = 408, ;
        LEFT = 12, ;
        SORTED = .T., ;
        TOP = 48, ;
        WIDTH = 264, ;
        NAME = "List1", ;
        ROWSOURCETYPE = 2, ;
        ROWSOURCE = "ListCheck"
        
    PROCEDURE LOAD
        LOCAL nCount, nCount2, nWordLength, sItem, nUpper, nLower
        nUpper = 90 &&ASCII
        nLower = 65 &&ASCII
        CREATE CURSOR ListCheck (MyEntry c(35), Checked L)
        FOR nCount = 1 TO 250
            sItem = ""
            nWordLength = INT((35) * RAND( ) + 1)
            FOR nCount2 = 1 TO nWordLength
                sItem = sItem + CHR(INT((nUpper - nLower + 1) * RAND( ) + nLower))
            ENDFOR
            INSERT INTO ListCheck (MyEntry, Checked) VALUES(sItem, .F.)
        NEXT
    ENDPROC
        
    PROCEDURE Unload
        USE IN SELECT("ListCheck")
        CLEAR EVENTS
    ENDPROC

    PROCEDURE ListSetup
        THISFORM.LOCKSCREEN = .T.
        LOCAL nListCount
        nListCount = 1
        SELECT ListCheck
        SCAN ALL
            IF ListCheck.Checked
                THIS.list1.PICTURE(nListCount) = THISFORM.CheckIcon
            ELSE
                THIS.list1.PICTURE(nListCount) = THISFORM.Uncheckicon
            ENDIF
            nListCount = nListCount + 1
        ENDSCAN
        THISFORM.LOCKSCREEN = .F.
    ENDPROC

    PROCEDURE SetCheck
        LOCAL nListIndex
        nListIndex = THIS.list1.LISTINDEX
        IF nListIndex > 0
            GO nListIndex IN "ListCheck"
            IF ListCheck.Checked
                THIS.list1.PICTURE(nListIndex) = THISFORM.Uncheckicon
            ELSE
                THIS.list1.PICTURE(nListIndex) = THISFORM.CheckIcon
            ENDIF
            REPLACE ListCheck.Checked WITH !ListCheck.Checked
        ENDIF
    ENDPROC

    PROCEDURE list1.GOTFOCUS()
        IF DODEFAULT()
            THISFORM.ListSetup()
        ENDIF
    ENDPROC
    
    PROCEDURE list1.CLICK()
        IF LASTKEY() = 13
            THISFORM.SetCheck()
        ENDIF
    ENDPROC

    PROCEDURE list1.KEYPRESS(nKeyCode, nShiftAltCtrl)
        IF nKeyCode = 13 OR nKeyCode = 32
            THISFORM.SetCheck()
        ENDIF
    ENDPROC

ENDDEFINE

Craig Boyd

21 de marzo de 2017

Mostrar los archivos que contiene un proyecto

Este codigo nos muestra cada uno de los archivos que componen un proyecto. para saber que tipo es el archivo, basta con consultar la tabla que esta al final.

_SCREEN.ADDPROPERTY('NUMERO',1)
LOCAL NUMERO, CADENA, CICLO, SELECCION
CLEAR
CADENA = ''
NUMERO = APPLICATION.PROJECTS.COUNT
SELECCION = 0
IF NUMERO = 0
 WAIT WINDOW "NO HAY NINGUN PROJECTO ABIERTO"
 RETURN
ENDIF
IF NUMERO > 1
 OBJETO = CREATEOBJECT('FORM')
 OBJETO.WINDOWTYPE = 1
 OBJETO.AUTOCENTER = .T.
 OBJETO.HEIGHT  = 135
 OBJETO.WIDTH = 380
 OBJETO.CAPTION = 'Proyectos Activos'
 OBJETO.ADDOBJECT('ETIQUETA','LABEL')
 OBJETO.ADDOBJECT('COMBO1','COMBOBOX')
 OBJETO.ADDOBJECT('aceptar','aceptar')
 OBJETO.ETIQUETA.CAPTION = 'Por Favor Seleccione el Proyecto que desea mostrar'
 OBJETO.ETIQUETA.AUTOSIZE = .T.
 OBJETO.COMBO1.WIDTH = OBJETO.WIDTH-15
 OBJETO.COMBO1.TOP = 50
 FOR CICLO = 1 TO NUMERO
  OBJETO.COMBO1.ADDITEM(ALLTRIM(APPLICATION.PROJECTS(CICLO).NAME),CICLO)
 ENDFOR
 OBJETO.SETALL('visible',.T.)
 OBJETO.COMBO1.VALUE = 1
 OBJETO.SHOW
 SELECCION = _SCREEN.NUMERO 
ENDIF

WITH APPLICATION.PROJECTS(SELECCION)
 ? "NOMBRE DEL PROYECTO: " + .NAME
 CANTIDAD = .FILES.COUNT
 FOR CICLO = 1 TO CANTIDAD
  ? 'Tipo: ' + ALLTRIM(.FILES(CICLO).TYPE) + ' Nombre: ' + ALLTRIM(.FILES(CICLO).NAME)
  IF INT(ciclo/30) =(ciclo/30)
   WAIT WINDOW 'PRESIONE UNA TECLA PARA CONTINUAR'
  ENDIF
 ENDFOR
ENDWITH

DEFINE CLASS ACEPTAR AS COMMANDBUTTON
 CAPTION = 'ACEPTAR'
 TOP    = 80
 LEFT   = 30
 HEIGHT = 35

 PROCEDURE CLICK
  _SCREEN.NUMERO =THIS.PARENT.COMBO1.VALUE
  THISFORM.RELEASE
 ENDPROC
ENDDEFINE
ValorConstante FoxPro.HTipo de Archivo Y Extension
dFILETYPE_DATABASEBase de datos, .dbc
DFILETYPE_FREETABLETabla libre, .dbf
QFILETYPE_QUERYConsulta, .qpr
KFILETYPE_FORMFormulario, .scx
RFILETYPE_REPORTInforme, .frx
BFILETYPE_LABELEtiqueta, .lbx
VFILETYPE_CLASSLIBBiblioteca de clases visuales, .vcx
PFILETYPE_PROGRAMPrograma, .prg
LFILETYPE_APILIBBiblioteca de vínculos dinámicos de Visual FoxPro, .fll
ZFILETYPE_APPLICATIONAplicación, .app
MFILETYPE_MENUMenú, .mnx
TFILETYPE_TEXTArchivo de texto, varias extensiones
xFILETYPE_OTHEROtros, varias extensiones

Jorge Mota

16 de marzo de 2017

El registro está fuera de rango (#5)

Artículo original: Record is out of range (#5)
http://www.foxpert.com/KnowlBits_200609_3.htm
Autor: Christof Wollenhaupt
Traducido por: Ana María Bisbé York


A veces trata de acceder a un registro que no existe en la tabla seleccionada.

Causa: Corrupción de índice

Cuando se corrompe el índice de una tabla, pudiera contener números de registros que ya no existan en la tabla. Cuando usted intenta acceder a este registro, recibe el mensaje de error que típicamente se refiere a la función o comando como tal. Si el error aparece en un comando que no accede a registros, puede ser una condición de filtro que es evaluada por detrás, por ejemplo, en un grid. REINDEX normalmente se ocupa de este problema durante el desarrollo. En tiempo de ejecución se debe utilizar un código para reordenar índices.

Causa: Listbox y Requery

Un control listbox está enlazado a un cursor utilizando RecordSourceTypes #2 (Alias) o #6 (Campos) y su valor apunta a un registro. Ahora reduce la cantidad de registros en su RecordSource. Típicamente el error aparece cuando emplea ZAP; pero ocasionalmente aparece cuando se re ejecuta una instrucción SELECT o al emplear REQUERY().

Una vez que esto ocurra, el error puede aparecer en varias situaciones. Ocurre cuando accede a una propiedad en el listbox. Pero también ocurre cuando Visual FoxPro refresca internamente el listbox. LA razón es simple, Visual FoxPro almacena el número de registro actual y navega hasta ese registro cuando usted intenta acceder al listbox, o cuando necesita repintarlo. Cuando elimina registros y no hay registros con el viejo número, entonces usted recibe un error. Lo mismo se aplica también a controles Combobox.

Persiste la pregunta de porqué no ocurre siempre un error. Parece que Visual FoxPro contiene mucho código que notifica a los controles de un cambio en la tabla. Esto explica, por qué el grid se queda completamente en blanco, incluso cuando no es necesario repintarlo. Estas notificaciones de eventos son necesarias para evadir los errores de este tipo. Cuando cambia la cantidad de registrosdel Recordsource desde un método Requery(), no tendrá ningún problema. Esto es obvio, debido a que VFP conoce que el RecordSource tiene que cambiar. Pero, incluso cuando ejecute SELECT ...FROM en alguna parte del programa, parece que se le notifica al listbox y no dispara ningún error. Al parecer ZAP es el comando menos cooperativo, y causa la mayoría de los problemas.

Causa: GOTO o LOCATE RECORD

La razón más obvia para este error es la cantidad no válida de registros utilizada por el GOTO o el comando LOCATE RECORD. Los números de registros válidos van desde 1 a RECCOUNT() y los números negativos para los registros agregados. LOCATE RECORD puede también obtener RECORD()+1 como un parámetro y posicionar la tabla en este caso en EOF()

11 de marzo de 2017

Técnicas de macrosustitución en VFP

Artículo original: VFP Macro Substitution Techniques
http://rickschummer.com/blog/2006/06/vfp-macro-substitution-techniques.html
Autor: Rick Schummer
Traducido por: Ana María Bisbé York


Hace un par de semanas programé un generador que se encarga de manipular propiedades. Estuve tratando de volver a utilizar una técnica con macro-sustitución que utilizaba hace un par de años para hacerlo todo.

Abrí la herramienta Referencias de código (Code Reference) y comencé a buscar por "..&" (punto, punto, ampersand) porque pensé que era eso lo que necesitaba. No obtuve resultados. Me sorprendió. Podría jurar que necesitaba los dos puntos.

Obtuve resultados cuando busqué ".&" (punto, ampersand). Lo que yo intentaba hacer era concatenar el nombre de una propiedad macro sustituida al final de una referencia a un objeto en el diseñador. El código final es muy sencillo una vez que se conoce lo que se necesita. En este caso, tenía un combobox con una lista de objetos en el diseñador de clase o formulario. El combo tiene una matriz con un contenedor al objeto del contenedor superior. Para obtener una referencia de objeto he ejecutado el siguiente código.

lnComboRowSelected = this.cboObjectsToPickFrom.ListIndex
lcAddPath = this.cboObjectsToPickFrom.aItems[lnComboRowSelected, 2]
loPickedControl = this.oControlList.oObject.&lcAddPath

Las propiedades en que estaba trabajando eran en control listbox con selección múltiple. Para obtener el valor de la propiedad en el control yo utilicé el siguiente código:

lcProperty = ALLTRIM(this.lstCommonProperties.aItems[lnI, 1])
lcPropertyValue = loPickedControl
&lcProperty

En este generador en particular (que se mostrará en el próximo número de Advisor's Guide to Microsoft Visual FoxPro) básicamente permito seleccionar los controles, seleccionar las propiedades que desea que sean idénticas y el generador va a migrar las propiedades. Esto es utilizando el framework BuilderControls que también se describe en el artículo. He empleado la misma técnica dos veces en el mismo método que mueve el valor de la propiedad entre los controles.

Me preocupó que no podía saber por qué originalmente pensé que necesitaba los dos puntos. Entonces, la semana pasadaTracy Pearson escrbió en ProFox algo que me hizo recordar en lo que yo estaba pensando y me di cuenta que yo estaba empleando la segunda técnica de macrosustitución.

Trabajo frecuentemente con tablas o cursores para guardar datos y conozco frecuentemente la estructura utilizando AFIELDS() o algunas otras técnicas. Controlo el alias del cursor por programación y lo guardo en una variable de memoria. Si deseo un valor específico de una columna y necesito incluir el alias ( a veces deseo siempre eliminar el factor ambiguedad) en la referencia a la columna. Si el código es genérico al cursor puedo utilizar macro sustitución para manipular el dato:

lcAlias = ALIAS()
ldInvoice = &lcAlias..dInvoice
lcCustomerName = &lcAlias..cCustName 

Como escribió Tracy en su post: "un punto para el final de la macrosustitución y otro punto para señalar tabla.campo"

Esto me hizo sentir mejor ya que existe la técnica de macrosustitución con dos puntos, espero que usted no pierda media hora buscando los dos puntos de forma incorrecta, o que sirvan estas líneas como recuerdo de que existen y son completamente diferentes.

28 de febrero de 2017

GETPICT() con miniaturas

Artículo original: GETPICT() with thumbnails
http://weblogs.foxite.com/vfpimaging/archive/2007/06/27/4199.aspx
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


Esto va por Koen Piller.

He visto mucha gente en los foros preguntando sobre la posibilidad de abrir el diálogo GETPICT() mostrando las miniaturas en lugar de los nombres de archivos de imágenes.

Sólo por diversión, hice algunas pruebas simulando algunas teclas, y ¡ funciona ! Ejecute las 3 líneas siguientes en WinXP

oShell = CREATEOBJECT("Wscript.Shell")
oShell.SendKeys("{TAB}{TAB}{TAB}{TAB}{TAB}{DOWN}{DOWN}{DOWN}{DOWN}{ENTER}")
GETPICT()

Como puede ver, he utilizado la función SendKeys de WSH. En este enlace, puede encontrar todos los códigos, si desea utilizar esta técnica para cualquier otro propósito: http://msdn2.microsoft.com/en-us/library/system.windows.forms.sendkeys(vs.71).aspx

Esto fue probado solamente en mi PC de trabajo, con WinXP Profesional. probablemente no trabaje de esta forma en otros Sistemas Operativos, así que vaya con cuidado, y si desea utilizar este código, verifíquelo primero.

Estoy casi seguro de que debe existir otra forma, probablemente más segura de hacerlo. ¿Usted sabe?

26 de febrero de 2017

El usuario siempre tiene la razón

Artículo original: The User is Always Right
http://doughennig.blogspot.com/2006/03/user-is-always-right.html
Autor: Doug Hennig
Traducido por: Ana María Bisbé York


A veces, nosotros, los desarrolladores de aplicaciones nos tornamos un tanto arrogantes. Creo que todos diseñamos las aplicaciones para que sea lo más fácil de usar posible; pero no siempre lo logramos. Y cuando los usuarios se meten en problemas por algo que han hecho, nuestra primera reacción es culparles:

  • Eso estaba escrito en el archivo de ayuda, ¿No lo ha leído?
  • No oprima ese botón a no ser que sepa lo que está haciendo.
  • ¿Por qué hizo eso?

Me he encontrado culpable de esto mismo recientemente. En Stonefield Query (http://www.stonefieldquery.com) comienza un proyecto nuevo oprimiendo el botón New Project en la barra de herramientas y seleccionando el directorio donde debe ir el archivo proyecto. Los archivos del proyecto son especificados en un archivo llamado SFQuery.INI, el que es creado después de oprimir el botón OK en el cuadro File. Trabaja genial.

Bueno, casi. Un desarrollador reportó un error en su proceso. El dice que no sólo ha seleccionado la carpeta deseada, también ha cambiado el nombre de un archivo a algo como MyProject.INI. Por supuesto, la aplicación está buscando SFQuery.INI, no lo encuentra, y recibe el mensaje de advertencia. Mi reacción (no ante el desarrollador, no soy tan descortés) fue de pensar ¿Por qué el usuario esperaba que trabajara? Yo no podría cambiar el nombre de una DLL de Microsoft World y luego sorprenderme de que dejara de funcionar.

Sin embargo, agregamos algún texto a nuestro archivo de ayuda documentando que nombre del archivo no podía ser cambiado, porque de lo contrario, nadie podría ejecutarlo. De esta forma llegamos a los Axiomas del desarrollo de aplicaciones de Doug.

Axioma # 1: La gente no lee

Otro desarrollador llegó al mismo problema. ¿Será que eran compañeros de habitación en el instituto? Cuando decimos que esto está documentado, replican que no han leído la documentación. ¿Por qué debía? Cuán complejo es oprimir en el botón New, seleccionar una carpeta y oprimir OK?

Entonces, arreglamos el problema al ignorar que el nombre de archivo entrado por el usuario y utilizando solamente la carpeta al crear SFQuery.INI. Fin del asunto.

Tenemos una llamada de un desarrollador, durante la cual apareció el Axioma # 2 de Doug.

Axioma # 2 La gente no es buena para comunicarse

El dijo que la función New nunca trabajó. Me encanta ese nivel de detalles. Veamos, "no trabaja". ¿Significa que ha recibido un mensaje de error? ¿Tengo que usar el Administrador de tarea de Windows para determinar un lazo interminable? ¿El monitor se hizo pedazos y cayeron trozos de vidrio en su cara? No, el solamente no creó el proyecto. Cuando le preguntamos un poco más, entonces se dio cuenta de que había escrito un nombre de archivo nuevo (el tristemente famoso MyProject.INI); pero cuando verificó en la carpeta, no existía el archivo, sino otro llamado SFQuery.INI

Entonces se hizo la luz. No era culpa del usuario, era culpa mía. ¿Por qué cuando lo que queremos es que el usuario seleccione una carpeta, mostramos el diálogo Archivo. La razón es que queremos que el usuario vea si el proyecto Stonefield Query ya existe en esa carpeta o no. Si ve el SFQuery.INI en la lista de archivos, no lo podría saber que ya lo había hecho. Esta no es una buena razón, sin embargo, hicimos finalmente lo correcto: cambiamos el diálogo por el de Seleccionar carpeta. Ahora no hay confusión en lo que necesita hacer el usuario.

Un segundo ejemplo: un usuario se quejó de que se le habían perdido unos informes. Luego de un trabajo propio de detectives, se dio cuenta de que los había borrado accidentalmente: el pensó modificar el informe; pero oprimió Eliminar, por error. Pero... ¿cómo pudo haber un accidente? Preguntamos al usuario con la típica pregunta ¿Está seguro?, entonces debe seleccionar "Yes" para confirmar la eliminación. Ah; pero es que habíamos olvidado el Axioma # 1. El problema es que Windows tiene tan acostumbrados a los usuarios ha seleccionar "Yes" sin leer los mensajes cada vez que aparece un diálogo, que ya es una costumbre. Existen varias soluciones para esto:

  • Reescribir el texto para que "Yes" signifique no eliminar el archivo. Probablemente no es la mejor elección.
  • Brindar una función Deshacer para el usuario pueda recuperar el informe eliminado. Nos gustó esta idea; pero sólo funciona si el usuario se da cuenta inmediatamente de lo que ha hecho. No ayudaría al usuario.
  • Complicar la selección de la función Eliminar. Justo ahora, el botón Eliminar está detrás del botón Editar. Haciendo una retrospectiva es evidente que se podía pensar que ocurriría un desastre. Puede ser que no deba estar en la barra de herramienta y solamente en el menú, ya que es muy raro decidir eliminar un informe comparado con la creación o modificación.

Conclusión: Necesito dedicar más tiempo a pensar algunas cosas desde la perspectiva de usuarios inexpertos. Y recordar el Axioma # 3:

Axioma # 3 El usuario siempre tiene la razón

No necesariamente en cómo comunican sus ideas (vea Axioma # 2) pero al menos en su intento.

Para ver más ideas sobre este escrito, vea: http://www.codinghorror.com/blog/archives/000550.html.

23 de febrero de 2017

Utilizar # DEFINE para navegar por el código

Artículo original: Use # DEFINE to navigate your code
http://www.ml-consult.co.uk/foxst-33.htm
Autor: Mike Lewis
Traducido por: Ana María Bisbé York


Un consejo sencillo que le permitirá recorrer grandes bloques de código mucho más fácil.

Recientemente un usuario nos pidió que le realizáramos un trabajo de mantenimiento en una aplicación Visual FoxPro. El programa incluía un único archivo PRG con más de 2,500 líneas de código. Nos quisimos desmayar al ver que el programador original no intentó siquiera dividir la lógica en pequeños módulos; lo había escrito como una única rutina monolítica.

Actualmente el estilo de programación no se presta para el mantenimiento. Lo ideal hubiera sido tomar tiempo para reestructurar el programa en procedimientos y funciones separadas; pero desafortunadamente, esto no fue posible.

Nosotros, sin embargo, dimos con una vía sencilla para navegar por esa monstruosa pieza de código. Nos dimos cuenta que el programa estaba hecho aproximadamente de 25 secciones lógicas. Cada uno de estos trozos de código realizaba una tarea particular. Al inicio de cada una de estas secciones, colocamos una directiva #DEFINE. Esto incluyó un identificador significativo que describe el propósito del código que le continúa como estos ejemplos:

#DEFINE Validate_Input@@
...@@ 
#DEFINE Calculate_Residue@@
...@@ 
#DEFINE Cleanup@@
...

La cuestión sobre estas directivas # DEFINE está en que ellas se muestran en la ventana Vista del documento (Document view) del menú Herramientas (Tools) (vea Figura1). Por consiguiente, tenemos una vía muy sencilla para acceder a cualquier sección de código, solamente con un clic del ratón.

Figura 1: Ventana Vista del documento. Las entradas con la marca # roja y grande son las directivas #DEFINE que se colocaron en el programa.

Si no ha utilizado la ventana de Vista de documento con anterioridad, definitivamente vale la pena tomar un momento para conocerla (está disponible en VFP7.0 y superior). Puede abrirla desde la Barra de herramientas Estándar (Standard toolbar) o desde el menú Herramientas (Tools). Su trabajo principal es mostrar los nombres de todos los procedimientos, funciones y métodos definidos en un archivo de programa, formulario o clase visual. Al hacer clic en uno de estos nombres, Visual FoxPro lo lleva directamente al código de la rutina en cuestión. (En el caso de los formularios y clases, debe hacer clic en el área de diseño para llenar inicialmente la ventana Vista del documento).

Opcionalmente, la ventana puede mostrar también las directivas #DEFINE y otras directivas de compilación. Estas opciones (que están activas de forma predeterminada) pueden encontrarse en el menú contextual que aparece al hacer clic derecho en la ventana. Esta característica nos facilitó la tarea de recorrer nuestro enorme archivo PRG.

Algo especialmente agradable de esta técnica es que una vez que se completó el trabajo de mantenimiento, no tuvimos que quitar las directivas #DEFINE. Como el resto de directivas del compilador, estas no impactan en tiempo de ejecución, y no afectan el rendimiento al dejarlas en el código.

Mike Lewis Consultants Ltd.

9 de febrero de 2017

CMD VFP - Mantenimiento de Tablas y Comandos Externos

Hemos visto muchas veces la necesidad de realizar un Mantenimiento de nuestra Tabla en un Sistema ya finalizado y suele suceder que no tenemos instalado el Visual FoxPro en esa PC, y tenemos que recurrir a muchas alternativas y muchas veces no es un buen modo de trabajar. Debido a muchas consultas que veía en la red me anime en aportar este pequeño modulo que se puede incorporar a sus propios sistemas y usar cuando sea necesario, solo en nivel de Mantenimiento.

Que hace este Pequeño Modulo trabaja con Tablas libres, simplemente usted busca la tabla a trabajar y si dicha tabla a sido dañada por falla de fluido eléctrico, la puede reparar sin problemas, o quizás hacer un pequeño mantenimiento de la misma tabla, ya sea reindexar, o modificar la estructura del mismo.

Además si desea realizar algún comando adicional lo puede hacer desde la Ventana Comandos sin necesidad de tener instalado el VFP, es decir lo puede hacer desde su sistema.

Descarga

Pueden descargar la clase v.1.70 desde el siguiente enlace: cmdvfp_1.70.rar

Jean Pierre Adonis De la Cruz Garcia
Pisco, Perú

25 de enero de 2017

Control Grid de Visual FoxPro - un truco y una clase calendario

Control Grid de Visual FoxPro - un truco y una clase calendario

Artículo original: Visual FoxPro Grid – A Tip and a Calendar Class
http://www.sweetpotatosoftware.com/blog/index.php/2005/08/20/visual-foxpro-grid-a-tip-and-a-calendar-class
Autor: Craig Boyd
Traducido por: Ana María Bisbé York


En mi opinión, el control grid de Visual FoxPro, por momentos ha sido poco valorado. Pienso que es probablemente uno de los controles más poderosos y útiles con los que he tenido el placer de trabajar. Las cosas que son capaces de hacer son sencillamente increíbles. Puede tener varios controles en una columna o incluso colocar un grid dentro de otro grid, de tal forma tiene filas y columnas que se interceptan en celdas representando muchos registros.

En esa entrada, deseo mostrarle una característica conocida del control grid. Si sabe de esta característica, entonces es uno entre un puñado. Cuando se refrescan los grids o si se repintan va a acceder al fondo (backstyle) de cada control que contiene en las columnas, y cualquier cosa que haga en los controles individuales va a ser mostrado en el grid.

Ayuda del FoxTeam de Microsoft

Permítame aclarar... digamos que tenemos un grid con una única columna y que esa columna contiene Textbox1. Si establece el backcolor del Textbox1 a través del código, digamos, el color Rojo, luego cada celda mostrada en la columna1 será roja. Entonces, cómo obtener colores dinámicos (celdas individuales coloreadas de forma diferente dentro de la misma columna)? Bien, el Fox Team de Microsoft nos brinda algunas propiedades dinámicas del objeto columna que va a actuar sobre celdas individuales ((DynamicAlignment, DynamicBackColor, DynamicCurrentControl, DynamicFontBold, DynamicFontItalic, DynamicFontName, DynamicFontOutline, DynamicFontShadow, DynamicFontSize, DynamicFontStrikeThru, DynamicFontUnderline, DynamicForeColor, y DynamicInputMask). Estas son muy utilizadas y en el escenario que brindo DynamicBackColor trabajará muy bien para cambiar el backcolor de una celda individual dentro de una columna del grid. Pero ¿Y si desea hacer algo más complejo? ¿Y si tiene un contenedor en la columna y el contenedor contiene múltiples objetos y desea establecer sus propiedades forecolor y backcolor a colores completamente diferentes dinámicamente o si desea mostrar diferentes imágenes dentro de las celdas del grid?

Soluciones ingeniosas y frecuentes de los desarrolladores

Un enfoque a estos problemas es utilizar múltiples controles dentro de las columnas del grid y luego, utilizar DynamicCurrentControl para decidir cuál mostrar. Algunos controles textbox que tienen fondo rojo y otros que tienen fondo blanco, o diferentes controles image configurados con figuras diferentes.

Otro método ingenioso de algunos desarrolladores Visual FoxPro al solucionar este problema es subclasear el objeto columna y luego enganchar en una propiedad dinámica de la columna que no se utilice para otros propósitos (por ejemplo, DynamicForeColor) Si el ForeColor es igual a 1, entonces hace esto, si es 2 entonces se hace otra cosa y si es 3 .... y etcétera. Aún cuando este enfoque y los dos precedentes son válidos, hay otra vía.

Otra vía

Como se ha visto antes, a la propiedad BackStyle se accede desde el CurrentControl en una columna, y no se accede sólo una vez, se accede por cada celda visible del grid. Entonces, utilizando esto podemos hacer la cercarnos a lo que queremos: un formateo dinámico y mostrar. ¿Desea mostrar imágenes diferentes? sólo hay que crear una subclase del contenedor y colocamos un control image dentro y lo colocamos en una columna. Luego, el método backstyle_access (necesitará añadir este método access al contenedor subclaseado), vea el valor de la propiedad Picture de la imagen en un campo en el RecordSource que guarda todos los caminos diferentes en el método backstyle_access de la imagen.

this.picture = crsImages.Paths

He aquí un ejemplo incluido en el archivo de descarga download. Cuando ejecute el ejemplo se le pedirá una carpeta que contiene las imágenes. Si no tiene archivo disponible, puede seleccionar la carpeta del proyecto ya que he incluido la imagen que se ve en la figura. Esta solución es más eficiente que tener un control image en la columna por cada imagen que desea mostrar en el control grid... 100 imagenes = 100 controles imagen? Lo acabo de solucionar con solo uno..

Clase calendario y un ejemplo del mundo real

Bueno, ¿qué tal si hacemos algo de la vida real?¿Qué tipo de cosas se pueden hacer al alterar dinámicamente elementos dentro de un contenedor utilizando para ello el backstyle_access? Todo tipo de cosas. Para demostrar un ejemplo útil, he colocado juntos algunos ejemplos de calendarios. He creado una clase calendar empleando el control grid. Esto debe demostrar el poder del empleo de backstyle_access en un grid, por no mencionar el poder de Visual FoxPro. Debajo está el enlace para la descarga del código fuente del ejemplo y algunas capturas de pantalla (para que sepa lo que se va a encontrar). Hay también un ejecutable en la carpeta fuente para la ejecución de los ejemplos, o si prefiere, puede ejecutar los formularios individualmente. Los ejemplos fueron creados en VFP 9.0, así que si desea utilizarlo en una versión anterior debe modificar el código, o lo que es aún mejor, actualizar su versión. Hay muy buenas cosas que ver. ¡Me encanta Visual FoxPro!

Descargar ejemplo y código de VFP Calendar (91 KB)
http://www.sweetpotatosoftware.com/files/vfpcalendar.zip

Un calendario sencillo

Demuestra algunos de las características avanzadas de la clase calendar

Controles Date y DateTime mejorados, creados empleando la clase calendar

15 de enero de 2017

Unir archivos PDF (Merge PDFs) mediante Ghostscript

A veces se requiere unir varios archivos PDF en un solo archivo, esto es posible mediante algunos software de manera manual, sin embargo es necesario establecerlo de manera automática para los usuarios. Después de probar varias alternativas quede con las siguientes líneas, lo cual es lo básico, ustedes podrán explotarles más opciones.

La solución de la herramienta Ghostscript es posible. Esta solución está disponible para 32Btis y 64Bits. En este ejemplo utilizaremos la de 64, mediante el archivo gswin64c.exe

folderactual = Sys(5) + Curdir()

* Armanos el BAT que lanzara el Script Merge
TEXT TO cComando TEXTMERGE NOSHOW PRETEXT 15
<<m.folderactual>>bin\gswin64c.exe -dBATCH -dNOPAUSE -dCompatibilityLevel=1.4 -sDEVICE=pdfwrite -sOutputFile="merge.pdf" "pdf1.pdf" "pdf2.pdf" "pdf3.pdf"
ENDTEXT

cArchivo = folderactual + 'Merge.bat'

* Creamos el archivo BAT
Strtofile(cComando, cArchivo)

* Ejecutamos el archivo BAT
oShell = Createobject("WScript.Shell")
oShell.Run(cArchivo,0,.T.)
Messagebox('Proceso de fusión realizado por éxito!',64,"")

* Abrimos el Archivo fusionado
cArchivo = folderactual + 'Merge.pdf'
oShell.Run(cArchivo,0,.T.)

*--- Eliminamos el archivo backup.bat
Delete File folderactual + 'Merge.b

Descarga el proyecto de ejemplo: Merge_PDF.rar

Enlaces:

https://es.wikipedia.org/wiki/Ghostscript
https://ghostscript.com/download/gsdnld.html

Nota: Descargar Ghostscript AGPL Release e instalarlo en la PC usuario

Lic. Allan Raul Acuña
Analista Programador
Managua, Nicaragua

11 de enero de 2017

Clase Barra de progreso con Visual FoxPro

Artículo original: Visual FoxPro Progress Bar Class
http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,87d20512-82d6-4ab2-827f-13a1bb5bbbf4.aspx
Autor: Craig Boyd
Traducido por: Ana María Bisbé York


Las barras de progreso se encuentran por doquier

Ya he creado antes una barra de progreso con Visual FoxPro (¿y quien no?). La última vez que hice una, la subí a la sección de descargas (DownLoads) de Universal Thread http://www.universalthread.com. Esta es una barra de progreso COM que permite indicar el progreso suavemente continúa incluso cuando Visual FoxPro está ocupado en algún tema candente o en una sencilla línea de código. Pero en el proyecto de hoy, voy a crear una sencilla barra de progreso que pudiera ser colocada en un contenedor (formulario o lo que sea).

Otra clase de barra de progreso

Yo quería que esta barra de progreso luciera verdaderamente profesional. Deseaba además, que su control sea redimensionado, por el desarrollador en tiempo de diseño, a cualquier tamaño y ancho . Luego deseo que sea una barra sólida o crear bloques individuales como los que hemos visto en las barras estándar de Windows XP y deseaba tener la posibilidad de mostrar el porcentaje completo con una etiqueta que pudiera cambiar de color como la barra de progreso que tiene un color en una mitad (es decir la mitad, 50% de un color y 50% de otro color.) Finalmente que sea capaz de mostrar colores diferentes (verde como en Windows XP, también rojo y azul.) He aquí algunos aspectos que he visto en otras barras de progreso, y siento que Visual FoxPro podía utilizar también.

Propiedades para una barra de progreso:

barcolor = El color que desea que tenga la barra: 1 = Rojo, 2 = Verde, 3 = Azul (predeterminado es 2)
min = El valor que se considera es 0% (predeterminado es 0)
max = El valor que se considera es 100% (predeterminado es 100)
percentage = El porcentaje completo basado en el valor actual asignado (predeterminado es 0)
showpercentage = Si se debe mostrar el porcentaje al usuario (predeterminado igual a .F.; mejor utilizado cuando solidbar es .T.)
solidbar = Si la barra de progreso debe mostrarse como una barra sólida en lugar de bloques (predeterminado es .F.)
value = El valor actual del progreso (predeterminado en 0; debe entrar en el rango entre el mínimo y el máximo)

Otras notas de desarrollo

El gradiente de la barra de progreso fue creado agregando líneas variando dinámicamente el grado del color. El porcentaje mostrado a través de la barra de progreso fue facilitado estableciendo la propiedad drawmode de las líneas de 14 - Líneas discontinuas.

Bajar el proyecto

Puede ejecutar progressbarex.exe o puede abrir el proyecto y ejecutar example.scx. Todo está incluido en el archivo. He aquí el enlace de descarga y una captura de la pantalla del ejemplo incluido ...

Descargar ejemplo y código de ProgressbarEx (26 KB)
http://www.sweetpotatosoftware.com/files/progressbarex.zip

5 de enero de 2017

Controlar dinámicamente los datos de un Grid

Artículo original: Controlling grid data dynamically
http://www.ml-consult.co.uk/foxst-20.htm
Autor: Mike Lewis
Traducido por: Ana María Bisbé York


¿Cómo le puede dar a sus usuarios mayor control sobre los contenidos de un Grid en Visual FoxPro?

Supongamos que desea crear un formulario como el que se muestra en la figura 1. Como ve, utiliza un Grid para mostrar los datos de una tabla Productos. Los usuarios pueden controlar el contenido del Grid de las siguientes formas:

  • Pueden limitar los registros a mostrar en el Grid según la categoría seleccionada.
  • Pueden escoger cuál de los dos campos  - el nombre en Inglés o el nombre original - es el que va a aparecer en la columna Description.
  • Pueden estipular el orden para el Grid.

Figura1: Tres formas para que el usuario controle el Grid

Luego de hacer estas selecciones, el usuario presiona el botón Refresh. Los datos del Grid cambian para refrescar según la selección del usuario.

Está claro, ¿verdad? Entonces, ¿cómo podemos crear este formulario?

Primeras ideas

La primera idea pudiera ser acceder al dato por medio de una vista local. Esto suena razonable, ya que usted puede modificar el contenido de una vista parametrizada. Se puede hacer en la cláusula WHERE de una vista, de esta forma:

WHERE Products.Category = ?lcCat

Aquí, lcCat es una variable que guarda la categoría escogida por el usuario. Si especifica entonces la vista como RecordSource del Grid, el dato se filtrará por la categoría requerida tantas veces como se invoque la vista. La llamada a la función REQUERY() va en el evento Click del botón Refresh del formulario.

Hasta aquí todo bien, en lo relativo a los filtros. Pero no es posible parametrizar los campos a mostrar en las columnas dadas ni el orden de la vista. Es posible recrear toda la vista programáticamente cada vez que el usuario presione el botón Refresh; pero esto no es una solución particularmente elegante. ¿Existe alguna forma más sencilla?

Intentar SQL SELECT

Utilizar una instrucción SQL SELECT para crear un cursor suena muy prometedor. Sin mucha dificultad, puede escribir un SELECT que represente las opciones de los usuarios, y que genera un cursor, que puede ser utilizado como el RecordSource del Grid.

Vamos a asumir que hemos configurado las siguientes variables:

  • lcCat contiene la categoría requerida.
  • llEnglish es .T. si el usuario escoge English como el lenguaje para la descripción del producto (en cuyo caso vamos a utilizar el campo eng_name como la segunda columna). Es .F. si el usuario desea verlo en el lenguaje original (para lo cual va a utilizar en su lugar el campo prod_name).
  • lcOrder contiene el número de la columna por la que se ordenará el dato (se almacena como cadena de caracteres).

El código en el botón Refresh puede tener este aspecto:

SELECT product_id,; 
  IIF(llEnglish,eng_name,prod_name) AS descript,;
  unit_price, in_stock ;
  FROM Products ;
  WHERE ALLTRIM(Category) = ALLTRIM(lcCat) ; 
  ORDER BY &lcOrder INTO CURSOR csrTemp
THISFORM.refresh

La instrucción SELECT envía el dato requerido al cursor, csrTemp. Este es el RecordSource para el Grid, entonces después que el formulario fue refrescado, el Grid  debe mostrar exactamente el dato que necesita el usuario. Problema solucionado.

No es tan sencillo

Desafortunadamente, no es tan sencillo. Si va a crear este formulario y ejecutarlo, el SELECT debía traer el dato correcto; pero el Grid aparecería como un rectángulo vacío. No se verían los datos.

La razón para este comportamiento no es difícil de ver. Siempre que utilice SELECT para crear un cursor de esta forma, Visual FoxPro destruye primero el cursor existente (si existe), luego, construye completamente uno nuevo. El Grid se desestabiliza con esto, ya que no desea perder el RecordSource, ni siquiera por un pequeño instante. Debido a que los controles dentro del Grid, están enlazados al cursor, destruyendo el cursor se destruyen los controles dentro del Grid, por eso se ve el rectángulo vacío.

Existirá alguna diferencia si utilizamos una tabla física en lugar de un cursor para la salida del SELECT? No, no habrá diferencia alguna.

La solución

Sin embargo, una vez que se entiende lo que ocurre, no es difícil idear una solución. El truco es crear un segundo cursor como el RecordSource, y mover los datos desde el primer cursor (aquel creado por el SELECT) al segundo cursor, el que el usuario desea actualizar en el Grid.

Vamos a colocar el siguiente código en el evento Load del formulario:

CREATE CURSOR csrProducts ; 
  ( product_id C(6), descript C(40), ;
  unit_price N(6,2), in_stock N(6) )

Esto va a crear un cursor, llamado crsProducts, con la misma estructura que el generado por SELECT. Establezca este cursor como RecordSource del Grid.

En el botón Refresh, mantenga el SELECT tal y como lo tenía antes; pero agregue algo de código para copiar el contenido del cursor generado por ese SELECT (csrTemp) en un cursor nuevo (csrProducts). El código entonces sería así:

SELECT product_id, ;
  IIF(llEnglish,eng_name,prod_name) AS descript,;
  unit_price, in_stock ;
  FROM Products ;
  WHERE ALLTRIM(Category) = ALLTRIM(lcCat) ; 
  ORDER BY &lcOrder INTO CURSOR csrTemp
SELECT csrProducts 
ZAP 
APPEND FROM DBF("csrTemp")
THISFORM.refresh

El efecto de esto es copiar los resultados del SELECT en csrProducts. Después que se ha refrescado el formulario, el dato se mostrará correctamente en el Grid.

Vea que no puede utilizar el comando COPY TO para transferir los datos a csrProducts, debido a que ese comando crea un archivo nuevo. En su lugar, necesita limpiar (ZAP) el contenido existente de csrProducts y agregar los datos nuevos. Observe además, el uso de la función DBF(). Esto es necesario debido a que el comando APPEND FROM puede solamente copiar datos desde una tabla física. DBF() devuelve la ruta y el nombre del archivo real que guarda el cursor.

Un detalle final al que debe prestar atención. Probablemente desee que el Grid muestre algún dato inicialmente cuando aparece el formulario por primera vez. Este dato debe basarse en los valores predeterminados para las tres selecciones del usuario. Para lograr esto, simplemente agregue código al Init del formulario para hacer el Select y para abrir los resultados en csrProducts. Por supuesto, va a necesitar además código para configurar las variables utilizadas en el SELECT (lcCat, llEnglish and lcOrder),  pero dejaremos esto como ejercicio para el lector.

Agradecimientos

La técnica que se ha descrito está basada en parte en la información del excelente libro "1001 Things You Always Wanted to Know About Visual FoxPro", por Marcia Akins, Andy Kramek y Rick Schummer (Hentzenwerke, 2000).

Mike Lewis Consultants Ltd. Septiembre 2001