Un tema que para mi ha sido un dolor de cabeza continuo es el tema de la fuente que utilizo en mis programas. El problema lo llevaba arrastrando muchos años y es el siguiente: en Windows los programas de 32bits se ven borrosos al utilizar fuentes grandes o un escalado del tamaño de fuentes en la pantalla. En mi caso utilizo escalado de fuentes tanto en un monitor de 24″ y en un portátil que utilizo habitualmente. Hasta hora en mis programas utilizaba la fuente del sistema tal como expliqué en una entrada anterior.
El caso es que hace poco estuve revisando la carpeta samples de FWH y algunos de sus programas, como FiveDBU, se veían tremendamente bien, sin fuentes borrosas. La manera de definir la fuente era algo tan sencillo como esto:
DEFINE FONT ::oFont NAME "Calibri" SIZE 0, -14
y como decía un conocido programador ¡listo el pollo!. Increíble. Con la de vueltas que le he dado yo a este tema y la solución era la más sencilla posible. Esta solución tenía un pero, y es que tuve que adaptar algunos controles para que la letra elegida se mostrase correctamente. Como los formularios que utilizo en mis programas los genero desde recursos también tuve que adaptar todos los formularios a la nueva letra. Los formularios son ahora un poco más grandes que antes pero el trabajo ha merecido la pena.
En la siguiente imagen muestro mi Cuaderno de Bitácora, a la izquierda con el nuevo tipo de letra y a la derecha con el antiguo. La claridad y legibilidad del programa ha mejorado bastante.
Esta entrada se publicó originalmente en Harbour Magazine, mi publicación sobre el lenguaje de programación Harbour.
Desde hace mucho tiempo he querido incorporar una galería de imágenes a algunos de mis programas. Esto data de mis tiempos de fanboy de Apple, cuando quedé prendado de un programa llamado Delicious Library al que me referí en esta entrada.
El caso es que hace poco estaba dándole vueltas a la cabeza en la manera de hacerlo y pregunté en el foro de FWH. La primera intención fue crear una clase a medida dentro de una ventana, pero esa solución no me valía porqué el flujo del programa es muy complicado de controlar pues las ventanas quedan independientes. Hubo otra propuesta de clase basada en diálogo utilizando la clase TPanel de FWH, y buscando ejemplos de esta clase me topé con la clase TScrollPanel que no conocía y de la que apenas hay documentación. Utilizando esta clase y el código que Mr. Rao había realizado para la clase TAlbum fue muy sencillo implementar la galería de imágenes.
Esta galería de imágenes la puedo utilizar, por ejemplo, para crear una galería de portadas de libros.
Lo bueno de esta galería es que muestra las portadas en el orden de la rejilla de libros y si hay algún filtro activo la galería muestra unicamente los libros que aparecen en el filtro.
El código que he utilizado para crear la galería es el siguiente:
Function LiGaleria()
LOCAL cAlias := "LI"
LOCAL nRecno := ( cAlias )->( RecNo() )
LOCAL nOrder := ( cAlias )->( ordNumber() )
local aImages := {}
local aLabels := {}
local aRecno := {}
local oDlgAlbum, oAlbum
LI->(DbGoTop())
WHILE ! LI->(EOF())
IF ! Empty(Rtrim(LI->LiImagen))
AAdd(aImages, Rtrim(LI->LiImagen))
AAdd(aLabels, Rtrim(LI->LiTitulo))
AAdd(aRecno, LI->(Recno()))
ENDIF
LI->(DbSkip())
ENDDO
LI->(DbGoTo(nRecno))
DEFINE DIALOG oDlgAlbum SIZE oApp():oGrid:nWidth, oApp():oGrid:nHeight PIXEL TRUEPIXEL ;
TITLE "Galería de portadas de libros - dobleclik para editar un libro"
oDlgAlbum:SetFont(oApp():oFont)
//
oAlbum := TScrollPanel():New( 20, 20, oApp():oGrid:nHeight-50, oApp():oGrid:nWidth-20, oDlgAlbum, .f. )
oAlbum:SetColor(CLR_WHITE, CLR_WHITE)
oAlbum:SetFont( oDlgAlbum:oFont )
@ oApp():oGrid:nHeight-40, oApp():oGrid:nWidth-96 BUTTON "Aceptar" ;
SIZE 76, 24 PIXEL OF oDlgAlbum ACTION oDlgAlbum:End()
ACTIVATE DIALOG oDlgAlbum ;
ON INIT ( LiAlbum( oAlbum, aImages, aRecno, oDlgAlbum ), oDlgAlbum:Center( oApp():oWndMain ) )
RETURN NIL
function LiAlbum( oPanel, aPhotos, aRecno, oDlgAlbum )
local nImgPerRow := 8
local nImgWidth // := 180
local nImgHeight // := Int( nImgWidth * 4 / 3 )
local nHGutter := 10
local nVGutter := 20
local nCols := nImgPerRow
local nRows, nRow, nCol, x, y, nImage, xMax, nImages := Len( aPhotos )
local oImage, oSay
// el ancho del scrollbar es 16
nImgWidth := INT((oPanel:nWidth-16-(nImgPerRow+1)*nHGutter)/nImgPerRow)
nImgHeight:= Int( nImgWidth * 4 / 3 )
nRows := Ceiling( nImages / nCols )
xMax := nCols * ( nImgWidth * nHGutter )
y := nVGutter
nImage := 1
do while nImage <= nImages
x := nHGutter
nCol := 1
do while nCol <= nCols .and. nImage <= nImages
// llamo a una funcion para conseguir detached locals
LiAlbumImage(y, x, nImgWidth, nImgHeight, oPanel, aPhotos, aRecno, nImage, nVGutter, oDlgAlbum)
nImage++
nCol++
x += ( nImgWidth + nHGutter )
enddo
y += ( nImgHeight + nVGutter )
enddo
//::nImgCols := nCols
// ::nHeight := y
oPanel:SetRange() // call this after defining all controls
return nil
Function LiAlbumImage(y, x, nImgWidth, nImgHeight, oPanel, aPhotos, aRecno, nImage, nVGutter, oDlgAlbum)
local oImage, oSay, nLiRecno
nLiRecno := aRecno[nImage]
@ y, x XIMAGE oImage SIZE nImgWidth, nImgHeight OF oPanel NOBORDER
oImage:SetSource( If( HB_ISARRAY( aPhotos[ nImage ] ), aPhotos[ nImage, 1 ], aPhotos[ nImage ] ) )
oImage:nUserControl := 0
oImage:lBmpTransparent := .f.
oImage:bLDblClick := { || ( LiForm( oApp():oGrid, "edt", , nLiRecno, oDlgAlbum )) }
//@ y+nImgHeight+(nVGutter/2), x SAY oSay PROMPT nLiRecno FONT oApp():oFont ;
// COLOR CLR_BLACK, CLR_WHITE ;
// SIZE nImgWidth, 2*nVGutter CENTER PIXEL OF oPanel
return NIL
En este código estoy utilizando tres funciones:
LaGaleria que recorre el fichero de libros para crear 3 arrays donde guardo las portadas, los títulos y los número de registro a que se refiere cada portada. Aquí creo el diálogo y el ScrollPanel.
LiAlbum donde recorro el array de imágenes para crear las imágenes dentro del ScrollPanel. Como luego quiero acceder a cada imagen para editar el libro haciendo doble click tengo que crear las imágenes en otra función utilizando la técnica de ‘detached locals’.
LiAlbumImage que es donde creo cada una de las imágenes, este código lo realizó Mr. Rao en el foro de FWH.
En una próxima actualización de Cuaderno de Bitácora incluiré galerías con las portadas de libros, discos y videos.
Esta entrada se publicó originalmente en Harbour Magazine, mi publicación sobre el lenguaje de programación Harbour.
Una de las opciones que trae el Puchero es la posibilidad de generar un fichero .rtf con la información almacenada de una receta. Hasta ahora el fichero .rtf que se generaba era algo como lo siguiente:
Para generar este fichero utilizo la clase TRtfFile de FWH que basicamente genera un fichero rtf a partir de una variable de tipo texto, pero sin permitir formateos ni muchas alegrías. Algo muy básico. La pregunta que te harás es ¿ por qué no generar un fichero docx directamente con la clase TWord o similar ? Pues porque la clase Tword para FWH requiere que el usuario tenga Word instalado, y eso es algo que evito a toda costa. La generación de ficheros XLS en mis programas la realizo utilizando la clase FileXls que Ramón Avendaño publicó en el foro de FWH hace muchos años, y que he subido a un repositorio de GitHub para evitar que desaparezca. Al generar el fichero desde cero, creando un fichero con el formato adecuado, se evita que el programa pierda funcionalidad si el usuario no tiene tal o cual programa instalado. En mi caso utilizo desde hace muchos años LibreOffice sin echar de menos ninguna funcionalidad del paquete Office de Microsoft, y los ficheros XLS los abro perfectamente con Calc.
Me puse a investigar sobre la generación de ficheros .rtf desde Harbour y FWH y encontré cosas interesantes hasta que llegué a un post en el grupo de Harbour Users en que había una clase para generar ficheros .rtf que me llamó la atención. Me puse a probarla y era lo que estaba buscando, la posibilidad de generar ficheros .rtf desde cero, creando el fichero con el formato adecuado. La clase está escrita por Thomas R. Marchione y la he publicado en Github sin tocar una coma. Espero que si el autor lee este post no haya ningún problema.
Utilizando esta clase, la exportación de receta a formato .rtf en el Puchero ahora mejora sustancialmente, puedo dar formato a los párrafos y crear tablas dentro del .rtf. Justo que lo quería.
A la hora de utilizar la clase con FWH hay que tener en cuenta lo siguiente:
En el fichero richtext.prg hay una instrucción #Command SET DEFAULT <x> TO <y> => <x> := IIF( HB_ISNIL( <x> ), <x> := <y>, <x> ) que genera un error debido a que FWH tiene una definición similar. Lo que he hecho ha sido cambiar esta instrucción por #Command SET RTFDEFAULT <x> TO <y> => <x> := IIF( HB_ISNIL( <x> ), <x> := <y>, <x> ) y modificar el fichero en consecuencia.
También hay una función FUNCTION cValToChar( xVal ) que hace reescribe otra de FWH y da problemas, en mi caso el los bitmaps de los browses aparecía NIL encima del bitmap en cuestión. En mi caso la comenté y listo.
A ver si alguien se anima a incluir posibilidad de añadir imágenes en el fichero .rtf
Esta entrada se publicó originalmente en Harbour Magazine, mi publicación sobre el lenguaje de programación Harbour.
En mis programas Windows utilizo un interfaz propio que he llamado FSDI — full simple display interface — del que publiqué varios artículos en mi blog cincomundos y que puede ser descargado junto con el código de Colossus que tengo publicado en GitHub.
Este interfaz FSDI consta basicamente de:
Una barra lateral de acciones, que permite elegir las acciones a realizar con los datos mostrados.
Una rejilla de datos configurable que muestra tipicamente los datos de un fichero DBF.
Una fila de pestañas, que permite elegir la ordenación de los datos de la rejilla.
El problema de este interfaz es que es demasiado rígido. Normalmente muestran todos los datos del DBF salvo que en un índice se haya incluido una condición. Para añadir flexibilidad a los datos a mostrar podemos incluir opciones de filtrado.
La opción de filtrado despliega un menú con todas las opciones de filtrado y la opción de eliminar el filtro. Normalmente el filtrado lo realizo por todos los campos que son clave ajena de la tabla, así como por algunos valores o rango de valores propios de la tabla como fechas, campos de marcas, etc. Antes de realizar el filtro se permite elegir el valor a filtrar mediante un diálogo auxiliar.
En Harbour se puede definir filtros sobre un DBF usando la sentencia dbSetFilter([<bCondition>], [<cCondition>]) y definiendo las condiciones mediante codeblocks. Lo bueno de los filtros es puedes realizar otras acciones sobre las tablas como puede ser cambios de índice o búsquedas, y simplemente estas acciones se realizarán sobre los datos filtrados.
Un problema que podemos tener al definir filtros es que realicemos una acción en bloque sobre la tabla, por lo que deberemos llevar cuidado de quitar el filtro y luego reponerlo. Para guardar el contenido del filtro podemos usar las funciones dbFilter() y para eliminarlo dbClearFilter().
Una acción importante, desde mi punto de vista, cuando creamos un filtro es que el usuario debe tener conocimiento visual de ello, es decir, debemos decirle a nuestro usuario que hay un filtro activado. En mis programas hago esto de dos formas:
Añadiendo el nombre del filtro al nombre del mantenimiento en la barra de opciones.
Cambiando el color de la etiqueta de la opción de definición de filtros a rojo.
Una vez se elimina el filtro, mediante la misma opción de filtrado todos estos elementos visuales desaparecen.
La posibilidad de definición de filtros sobre tablas es una opción existente en Harbour que podemos incluir de manera sencilla en nuestros programas, lo que nos permitirá añadir una gran flexibilidad a los mismos. Es importante que cuando apliquemos filtros lo hagamos de manera que el usuario vea claramente que hay un filtro activo, y no le lleve a confusión.
Esta entrada se publicó originalmente en Harbour Magazine, mi publicación sobre el lenguaje de programación Harbour.
En la reunión que realizamos en Novelda grabamos todas las conferencias. Bueno, todas no porque se acabó el disco de la cámara justo al final de la charla de Manu Expósito, por lo que la conferencia de Manuel Calero no la pudimos grabar. He creado un canal en Youtube con los videos de las conferencias y aquí os las muestro. Todas las conferencias están en español.
Fundamentos técnicos de la Inteligencia Artificial, por Antonio Linares
Api Rest y su uso en Google, por Cristobal Navarro
Servicios web con Harbour, por Rafa Carmona
Win & Web, los nuevos escenarios, por Carles Aubia
Harbour Data Objects, por Manu Expósito
XEdit y control de versiones, por José F. Giménez
Experiencias de una publicación sobre Harbour, por José Luis Sánchez
Una funcionalidad que quería implementar en el Puchero es la gestión de dietas y tolerancias de las recetas. Es decir que para cada receta se pueda poner si es adecuada para gente con colesterol, celíacos, o para dietas de puntos, Dunkan o lo que sea. Esto supone que cada receta podía llevar asociadas múltiples dietas y la verdad es que no tenía claro cómo hacerlo. Muchas veces lo principal no es la funcionalidad sino como se implementa esta funcionalidad lo que hace que esta sea aceptada por los usuarios o no.
El caso es que dándole vueltas a la cabeza recordé el sistema de etiquetado de documentos que usa Evernote. Algo así:
En FWH hay una clase llamada TTagCloud que podía servirme como base. Esta clase la hizo Francisco García Fernández, que creo que es un gran creador de controles para FWH y además un buen amigo mio. Estuve varias semanas dándole vueltas al control sin conseguir avances significativos, hasta que me puse en contacto con Paco para pedirle ayuda. Paco en un par de días hizo el control, y ahora en el Puchero tengo hecha la implementación de las dietas de esta manera.
Esta web utiliza cookies para que podamos ofrecerte la mejor experiencia de usuario posible. La información de las cookies se almacena en tu navegador y realiza funciones tales como reconocerte cuando vuelves a nuestra web o ayudar a nuestro equipo a comprender qué secciones de la web encuentras más interesantes y útiles.
Cookies estrictamente necesarias
Las cookies estrictamente necesarias tiene que activarse siempre para que podamos guardar tus preferencias de ajustes de cookies.
Si desactivas esta cookie no podremos guardar tus preferencias. Esto significa que cada vez que visites esta web tendrás que activar o desactivar las cookies de nuevo.