fsdi2006

Desde hace bastante tiempo vengo usando en mis programas una clase llamada FSDI que pretende emular un interfaz de documento único a ventana completa. El primer post donde hablaba de esto se llamaba Full Document Single Interfaz y estaba en Software* que fue mi primer blog. Luego hubo modificaciones de la clase, primero con su adaptación a xHarbour, luego el diálogo contenedor pasó a ser no modal, y hace poco conseguí que el diálogo contenedor se redimensionara al redimensionar la ventana principal de la aplicación gracias al uso del método SetSize().

Ahora publico como ha quedado la clase FSDI con todas estas características, así como un pequeño ejemplo de como montar la ventana principal y un diálogo FSDI sobre ella. El código que acompaña al ejemplo es el siguiente:

  • main.prg – punto de entrada de la aplicación y construcción de la ventana principal. El redimensionamiento de esta se hace en la función ResizeWndMain(). Está incluido el soporte de fuentes grandes en caso de que el usuario de la aplicación las tenga seleccionadas.
  • tfsdi.prg – la clase FSDI.
  • pcustomer.prg – construcción de un diálogo FSDI con la TaskBar de Canalfive a la izquierda y una rejilla de datos a la derecha.
  • tabs.prg – las tab que uso para la parte inferior del diálogo fsdi.

Aquí está el ejemplo para descargar: FSDI2006

completando FSDI: un método llamado SetSize()

Una de las cosas de las que carecía nuestro interfaz TFSDI era del ajuste a la ventana principal de la aplicación al redimensionar esta. En el post anterior se ve en una de las capturas que se publicaron en el artículo de PcActual como queda un trozo de ventana sin el diálogo FSDI encima. Esto es debido a que al crear el diálogo FSDI calculamos las coordenadas que debe tener este y lo ponemos en la zona cliente de la ventana principal de la aplicación, pero al cambiar el tamaño de esta no sabiamos como ajustar el diálogo FSDI con sus controles.

La verdad es que la cosa parecía difícil de resolver. Habiamos hecho varios intentos sin resultado, y en la última reunión de GO2000 José Luis Capel nos enseñó una aplicación con un aspecto similar a las nuestras pero con el ajuste a la ventana perfectamente conseguido. La manera de hacer esto por parte de José Luis era usando paneles, y estuve preguntándole varias cosas pero sin resultado. Lo de los paneles era un auténtico lio, o eso me parecía. El caso es que buscando la manera de ajustar di con un ejemplo en la carpeta SAMPLES de FWH en el que nunca había reparado: fwbios.prg. En este ejemplo se hace un ajuste de un listbox definido por código a una ventana mediante el método SetSize() de aquel, invocado al redimensionar la ventana. El caso es que el método SetSize() pertenece a la clase Window y lo heredan todas las clases que derivan de ella, o sea
todos los controles. Este método permite ajustar el tamaño de cualquier control que haya sido definido por código, como por ejemplo el taskbar, xbrowse y tabs que uso en mis diálogos FSDI además del propio diálogo. Y yo sin enterarme ni de que existía este método.

Lo único que he tenido que hacer es que los objetos taskbar, xbrowse y tabs que aparecen el cada diálogo FSDI sean datas de mi clase TApplication que es la que controla la ventana principal, de manera que al redimensionar la ventana pueda acceder a estos controles para ajustarles el tamaño mediante el método SetSize(). En breve publicaré la edición gratuita de Azeta que es el primer programa donde implemento esto.

sistema de búsquedas

Hasta ahora en mis programas la búsqueda de registros era mediante SEEK. Esto quiere decir que al buscar un término, por ejemplo ‘software’ en un fichero de libros, la búsqueda devolvía como resultado el primer registro que empezase por ese término. Si el título del libro era Ingeniería de software la búsqueda anterior ni le rozaba.

A partir de ahora las búsquedas no van a ser por SEEK sino que van a permitir realizar búsquedas en cualquier parte del campo por el que se quiera buscar. Asi ya podré encontrar mis libros de Ingeniería de software al introducir software en el formulario de búsqueda de Cuaderno de Bitácora. El resultado es este:

El mayor problema que he tenido para implementar este tipo de búsquedas no ha sido la manera de hacer la búsqueda sino como presentar el resultado. Al final la decisíón ha sido mostrar un formulario con la relación de ejemplares que cumplen la condición para ahi hacer la selección definitiva del ejemplar a consultar. Una lista sobre otra lista, como dice mi amigo Manuel, pero creo que es la mejor manera de hacerlo.

fuentes grandes en FWH

Una de las cosas que nos están pidiendo los usuarios de nuestros programas ultimamente es la posibilidad de modificar el tamaño de la fuente de los mismos. Con el aumento de tamaño de los monitores actuales es perfectamente posible tener una fuente grande para no tener que forzar la vista. Es posible definir en el programa una fuente y un tamaño, pero creo que la manera correcto de hacer esto es que el usuario defina en Windows que fuente y que tamaño quieren usar y que los programas tomen la fuente de la configuracíón de Windows. Además Windows permite usar el sistema de alisado de fuentes ClearType que permite tener una fuente muy nítida en monitores grandes.

Un formulario típico con fuentes pequeñas sería el siguiente:

Para pasar el programa a fuentes grandes, o mejor dicho, para que siempre coja las fuentes del sistema, tenemos que evitar usar tamaños de fuente fijas en nuestro programa y coger la del sistema. En FWH existe la función GetSysFont() que permite coger la fuente del sistema. Contando que al usar fuentes grandes el propio Windows se encarga de redimensionar los diálogos tenemos que nuestro anterior diálogo se ha trasformado en este:

Perfecto, ¿ no ?

Pues no. Usando GetSysFont() cogemos las fuentes del sistema, pero no se el motivo por el cual determinadas partes del formulario no usa el suavizado de fuentes cuando se le pide a Windows que lo haga. Así, si nos fijamos en la imagen de arriba vemos que realmente hay dos tipos de fuentes en el diálogo, una para los GET y otra para los SAY.

Fijándonos en el menú de la aplicación veremos que este sí que usa el suavizado de fuentes, tal como se aprecia en la siguiente imagen:

¿ Que hacer para que la aplicación tome la fuente de Windows con el suavizado ? La solución me la dió mi amigo Paco – gracias otra vez más – y consiste en usar las siguientes funciones:

//___ manejo de fuentes - Paco García 2006 ___________________________________//
#pragma BEGINDUMP
#include "Windows.h"
#include "hbapi.h"
HB_FUNC( GETDEFAULTFONTNAME )
{
LOGFONT lf;
GetObject( ( HFONT ) GetStockObject( DEFAULT_GUI_FONT )  , sizeof( LOGFONT ), &lf );
hb_retc( lf.lfFaceName );
}
HB_FUNC( GETDEFAULTFONTHEIGHT )
{
LOGFONT lf;
GetObject( ( HFONT ) GetStockObject( DEFAULT_GUI_FONT )  , sizeof( LOGFONT ), &lf );
hb_retni( lf.lfHeight );
}
#pragma ENDDUMP

y en el programa definir de esta manera la fuente de la ventana principal:

::oFont = TFont():New( GetDefaultFontName(), 0, GetDefaultFontHeight(),, )</p>

y luego heredar en las ventanas y dialogos del programa esta fuente. Con esto nuestro diálogo queda de esta manera:

donde se ve claramente que ya toma bien la fuente del sistema con el suavizado de bordes.

El último paso para que nuestra aplicación FWH tome bien las fuentes del sistema es modificar las clases de FWH que usan fuentes de paso fijo, como MsgBar, MsgItem y Ttabs para que cojan la fuente de nuestra ventana principal.

Las próximas versiones de nuestros programas ya funcionarán bien con fuentes grandes.

tooltips de balón en FWH

Muchas veces la diferencia entre un programa y un buen programa está en los detalles. Por eso, un programador debe visitar con asiduidad los foros del lenguaje o herramienta de desarrollo que utiliza y estar al tanto de cualquier comentario que se pueda hacer sobre el mismo.

Hace un par de semanas en el foro de FWH Antonio Linares publicó la manera de cambiar los tradicionales tooltips cuadrados por unos de tipo balón. Los cuadrados son estos:

Para conseguir los tooltips de balón lo único que hay que hacer es ir al código fuente de la clase Window y quitar un comentario que aparece en la linea 2762 – o simplemente buscar ballon en el prg -. Hay que dejar la llamada a CreateTooltip con el último paráametro a .t.

hWnd = CreateToolTip( Self:hWnd, cToolTip, .t. ) // for ballon tooltips !

Luego se recompila la librería y nuestro programa. Ya tenemos tooltips de balón 😉

septiembre, el primer mes de curso

Para mi Septiembre es casi siempre el primer mes del año. En verano suelo reflexionar sobre lo que he estado haciendo durante el año anterior y de alguna manera tratar de planificar el próximo. Supongo que ayuda la faceta de docente, que de alguna manera te liga al calendario escolar.

Para este año, la idea es terminar durante el mes de septiembre las nuevas versiones de Cuaderno de Bitácora y el Puchero con la supresión de BtnGet y las altas dinámicas de claves ajenas. Una vez hecho esto intentar queremos jugar en las major leagues: internacionalizar los programas y lanzarnos a vender en el mundo mundial. Antes de eso tenemos que terminar algunas cosas de la web, como la gestión de la lista de correo que se nos está atragantando. Intentamos instalar phplist pero la cosa se complicó, y vamos a probar con dadamail que parece más sencillo aunque menos potente.

Los iconos de los nuevos programas van a ser de iconexperience. Hemos comprado las colecciones Application Basics y Objects & People y estamos adaptando los progamas con los nuevos iconos. Realmente quedan impresionantes… como en esta captura de azeta:

Y para este invierno los libros que tengo en cartera son:

altas de claves ajenas

Una de las nuevas funcionalidades que estamos implementando de cara a las nuevas versiones de los programas que queremos tener listas a vuelta de verano es el alta de claves ajenas. Al introducir un valor que es clave ajena de una tabla, si el valor no existe en la tabla se va a poder introducir en ese momento.

Por ejemplo, en este diálogo introduzco una temática en el artículo que no está dada de alta en la tabla de temáticas:

El programa detecta que la clave no existe y avisa

y permite introducirla en ese momento

Aunque parezca una tontería, de esta manera se hace mucho más sencillo introducir grandes volúmenes de datos en los programas, porque no pierdes el hilo de lo que estás haciendo. Si estás dando de alta una artículo no tienes que ponerte a pensar en dar de alta antes la temática sino que se hace sobre la marcha.

cambios en el interfaz

Hasta ahora en los formularios de edición de los programas, cuando un campo podía ser rellenado en base a una tabla auxiliar en la parte derecha del campo aparecía una imagen que al pincharla permitía abrir un formulario de selección sobre la tabla auxiliar. La manera de hacer esto está explicado en un post de mi anterior blog. Un formulario típico podía ser este:

En las nuevas versiones de los programas, las imágenes a la derecha de los Get desaparecen y son sustituidas por botones, de esta manera:

El motivo del cambio es quitarnos de en medio tanto gráfico como usamos ahora para tener una interfaz menos recargada, poder recorrer todo el formulario con el teclado, incluyendo la selección de tablas auxiliares, y poder tener dos botones al lado de un campo para permitir seleccionar y lanzar un archivo, cosa que necesito para la nueva versión de Hemerot que estoy preparando y al que corresponde la imagen.

montar un tree desde una DBF con FWH

En el Puchero se usa una clasificación arborescente denominada clasificación francesa y para jugar con ella la monto en un tree. Como he recibido varios correos preguntando la manera de montar el tree desde la dbf, aqui lo explico un poco.

Lo primero es montar una estructura de datos que permita ser representando en forma de arbol. Un arbol no es más que una jerarquía con varios niveles, y lo que tengo en mi dbf son varios campos -hasta 5 – para indicar en que rama del arbol estoy. Los campos se llaman FrN1, FrN2, FrN3, FrN4 y Frn5 de manera que el arbol lo veo así:

(1,0,0,0,0)
···(1,1,0,0,0)
······(1,1,1,0,0)
······(1,1,2,0,0)
···(1,2,0,0,0)

Una vez esto claro el arbol se monta así:

FUNCTION FrTreeLoad( oTree )
LOCAL oDatabase
LOCAL nStep
LOCAL oLink
LOCAL oLink1, oLink2, oLink3, oLink4, oLink5
LOCAL N1	:= 0
LOCAL N2	:= 0
LOCAL N3	:= 0
LOCAL N4	:= 0
oLink := oTree:GetRoot()
SELECT FR
FR->(DbGoTop())
DO WHILE ! FR->(EOF())
···IF FR->FrN2 == 0
······oLink1 := oLink:AddLastChild(FR->FrTipo,IIF(FR->FrHoja,1,2),IIF(FR->FrHoja,1,2),.t.)
······oLink1:Cargo := Str(FR->Frn1,2)+Str(FR->Frn2,2)+Str(FR->Frn3,2)+Str(FR->Frn4,2)+Str(FR->Frn5,2)
···ELSEIF FR->FrN3 == 0
······oLink2 := olink1:AddLastChild(FR->FrTipo,IIF(FR->FrHoja,1,2),IIF(FR->FrHoja,1,2),.t.)
······oLink2:Cargo := Str(FR->Frn1,2)+Str(FR->Frn2,2)+Str(FR->Frn3,2)+Str(FR->Frn4,2)+Str(FR->Frn5,2)
···ELSEIF FR->FrN4 == 0
······oLink3 := olink2:AddLastChild(FR->FrTipo,IIF(FR->FrHoja,1,2),IIF(FR->FrHoja,1,2),.t.)
······oLink3:Cargo := Str(FR->Frn1,2)+Str(FR->Frn2,2)+Str(FR->Frn3,2)+Str(FR->Frn4,2)+Str(FR->Frn5,2)
···ELSEIF FR->FrN5 == 0
······oLink4 := olink3:AddLastChild(FR->FrTipo,IIF(FR->FrHoja,1,2),IIF(FR->FrHoja,1,2),.t.)
······oLink4:Cargo := Str(FR->Frn1,2)+Str(FR->Frn2,2)+Str(FR->Frn3,2)+Str(FR->Frn4,2)+Str(FR->Frn5,2)
···ELSE
······oLink5:= oLink4:AddLastChild(FR->FrTipo,IIF(FR->FrHoja,1,2),IIF(FR->FrHoja,1,2),.t.)
······oLink5:Cargo := Str(FR->Frn1,2)+Str(FR->Frn2,2)+Str(FR->Frn3,2)+Str(FR->Frn4,2)+Str(FR->Frn5,2)
ENDIF
FR->(DbSkip())
ENDDO
oTree:UpdateTV()
oTree:SetFocus()
RETURN NIL

Lo que hago es recorrer el DBF que tengo ordenado por la concatenación de los 5 campos y cuando cambio de nivel añado una rama al nivel inferior.

El resultado: