Ejemplo con PB/Forms

En el anterior artículo de Classic PowerBasic gratis, BiaNamaran me sugería hacer un pequeño tutorial tipo «Hello World» explicando la filosofía de trabajo de PowerBasic for Windows (PB/Win), y PowerBasic Forms (PB/Forms), unas herramientas que pese a estar valoradas en 80$, podéis conseguir de manera gratuita.

Asumiendo que ya hayáis instalado PBWin y PBForms, cosa que podrás hacer incluso en un equipo con Windows 2000 y 32 Mb. de memoria RAM, empezamos con esta pequeña guía paso a paso.

Abrimos PBFORMS.EXE, que es la herramienta de diseño de ventanas. Procedemos a dibujar visualmente nuestro diálogo, no tiene mucho misterio. Vamos colocando componentes, hasta disponer de un label y un botón, y listos.

Ejemplo con PB/Forms

Desde PB/Forms, asignamos las propiedades a cada componente, el diálogo en si, el botón, y el label o etiqueta, así ajustándolos a nuestras necesidades.

Ejemplo con PB/Forms

Al guardarlo, nos generará un código PowerBasic, que podemos abrir directamente con el editor de PB/Win (PBEDIT.EXE).

Ejemplo con PB/Forms

Podemos irnos directamente a Run > Compile and Execute (CTRL-E), para verificar que todo ha ido correctamente. Deberemos ver el diálogo que justo hemos creado.

Ejemplo con PB/Forms

Si pulsamos sobre el botón de Cerrar, veremos que el programa muestra un MessageBox. Esto es porque PB/Forms lo ha insertado en el código, que deberemos modificar a nuestro gusto.

Ejemplo con PB/Forms

En este punto, la primera virguería que vemos, es que fiel a sus orígenes, el ejecutable, ocupa tan solo 28.160 bytes. Hablamos de un ejecutable standalone, que no necesita ni de frameworks, ni de librerías externas, ni de DLL de terceros.

Podemos comprobar, que ha generado una enormidad de código, pero no os asustéis. Son más de 100 lineas, pero porque contienen muchos comentarios para guiarnos y separar secciones:

#PBFORMS Created v1.51
'------------------------------------------------------------------------------
' The first line in this file is a PB/Forms metastatement.
' It should ALWAYS be the first line of the file. Other   
' PB/Forms metastatements are placed at the beginning and 
' end of "Named Blocks" of code that should be edited     
' with PBForms only. Do not manually edit or delete these 
' metastatements or PB/Forms will not be able to reread   
' the file correctly.  See the PB/Forms documentation for 
' more information.                                       
' Named blocks begin like this:    #PBFORMS BEGIN ...     
' Named blocks end like this:      #PBFORMS END ...       
' Other PB/Forms metastatements such as:                  
'     #PBFORMS DECLARATIONS                               
' are used by PB/Forms to insert additional code.         
' Feel free to make changes anywhere else in the file.    
'------------------------------------------------------------------------------

#Compile Exe
#Dim All

'------------------------------------------------------------------------------
'   ** Includes **
'------------------------------------------------------------------------------
#PBFORMS Begin Includes 
#If Not %Def(%WINAPI)
	#Include "WIN32API.INC"
#EndIf
#PBFORMS End Includes
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
'   ** Constants **
'------------------------------------------------------------------------------
#PBFORMS Begin Constants 
%IDD_DLGEJEMPLO      =  101
%IDC_LBLINSTRUCTIONS = 1003
%IDC_BTNCERRAR       = 1002
#PBFORMS End Constants
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
'   ** Declarations **
'------------------------------------------------------------------------------
Declare CallBack Function ShowDLGEJEMPLOProc()
Declare Function ShowDLGEJEMPLO(ByVal hParent As Dword) As Long
#PBFORMS Declarations
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
'   ** Main Application Entry Point **
'------------------------------------------------------------------------------
Function PbMain()
	ShowDLGEJEMPLO %HWND_DESKTOP
End Function
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
'   ** CallBacks **
'------------------------------------------------------------------------------
CallBack Function ShowDLGEJEMPLOProc()

	Select Case As Long CbMsg
		Case %WM_INITDIALOG
			' Initialization handler

		Case %WM_NCACTIVATE
			Static hWndSaveFocus As DWord
			If IsFalse CbWParam Then
				' Save control focus
				hWndSaveFocus = GetFocus()
			ElseIf hWndSaveFocus Then
				' Restore control focus
				SetFocus(hWndSaveFocus)
				hWndSaveFocus = 0
			End If

		Case %WM_COMMAND
			' Process control notifications
			Select Case As Long CbCtl
				Case %IDC_BTNCERRAR
					If CbCtlMsg = %BN_CLICKED Or CbCtlMsg = 1 Then
						MsgBox "%IDC_BTNCERRAR=" + Format$(%IDC_BTNCERRAR), _
							%MB_TASKMODAL
					End If

				Case %IDC_LBLINSTRUCTIONS

			End Select
	End Select
End Function
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
'   ** Dialogs **
'------------------------------------------------------------------------------
Function ShowDLGEJEMPLO(ByVal hParent As Dword) As Long
	Local lRslt As Long

#PBFORMS Begin Dialog %IDD_DLGEJEMPLO->->
	Local hDlg  As Dword

	Dialog New hParent, "Ejemplo PBForms", 188, 216, 201, 121, To hDlg
	Control Add Button, hDlg, %IDC_BTNCERRAR, "&Cerrar", 75, 96, 50, 15
	Control Add Label,  hDlg, %IDC_LBLINSTRUCTIONS, "Click en Cerrar para " + _
		"cerrar", 0, 0, 200, 80
#PBFORMS End Dialog

	Dialog Show Modal hDlg, Call ShowDLGEJEMPLOProc To lRslt

#PBFORMS Begin CleanUp %IDD_DLGEJEMPLO
#PBFORMS End CleanUp

	Function = lRslt
End Function
'------------------------------------------------------------------------------

Ahora añadiremos algunas metainstrucciones, que se encargan en el caso de PowerBasic de modificar el compilador, y el preprocesador, agregaremos estas tres, con el fin de generar el código más veloz posible:

#OPTIMIZE SPEED
#REGISTER ALL
#ALIGN 16

El primer nos dice que queremos que se optimice el código generado, en este caso para velocidad (SPEED), pero podría ser también para tamaño (SIZE). La segunda induce al compilador a utilizar registros del procesador para almacenar variables siempre que sea posible, de forma automática. Los registros, son mucho más rápidos que la memoria. Por último, forzamos que todas las estructuras estén alineadas en memoria usando párrafos (16 bytes). Esto hace que su acceso sea más veloz en procesadores modernos.

Ejemplo con PB/Forms

Después haremos una tontería, que consiste en cambiar el texto del label en tiempo de ejecución. Lo suyo hubiera sido generarlo ya con ese texto desde PB/Forms, pero así vemos la operativa básica con los componentes o controles. En la función ShowDLGEJEMPLO, que es la que se invoca al mostrar el diálogo, fuera del código generado por PBFORMS, añadiremos lo siguiente:

CONTROL SET TEXT hDlg, %IDC_LBLINSTRUCTIONS, "Bienvenido a PB/Forms"

Ejemplo con PB/Forms

Por último, nos queda cerrar efectivamente la ventana o diálogo cuando el usuario pulse el botón. Para ello, donde estaba el MessageBox original, agregamos el código:

DIALOG END CBHNDL, 0

Ejemplo con PB/Forms

Volvemos a ejecutar nuestro programa, y obtenemos el resultado esperado. El texto del label ha cambiado, y pulsando en Cerrar, cerramos la ventana, y el programa.

Ejemplo con PB/Forms

El resultado, de toda esta funcionalidad de ejemplo, es un .EXE de 27.136 bytes, y del mismo modo que cuando empezamos, totalmente autocontenido, que ha quedado de esta manera:

#PBFORMS Created v1.51
'------------------------------------------------------------------------------
' The first line in this file is a PB/Forms metastatement.
' It should ALWAYS be the first line of the file. Other
' PB/Forms metastatements are placed at the beginning and
' end of "Named Blocks" of code that should be edited
' with PBForms only. Do not manually edit or delete these
' metastatements or PB/Forms will not be able to reread
' the file correctly.  See the PB/Forms documentation for
' more information.
' Named blocks begin like this:    #PBFORMS BEGIN ...
' Named blocks end like this:      #PBFORMS END ...
' Other PB/Forms metastatements such as:
'     #PBFORMS DECLARATIONS
' are used by PB/Forms to insert additional code.
' Feel free to make changes anywhere else in the file.
'------------------------------------------------------------------------------

#COMPILE EXE
#OPTIMIZE SPEED
#REGISTER ALL
#ALIGN 16
#DIM ALL

'------------------------------------------------------------------------------
'   ** Includes **
'------------------------------------------------------------------------------
#PBFORMS Begin Includes
#IF NOT %DEF(%WINAPI)
    #INCLUDE "WIN32API.INC"
#ENDIF
#PBFORMS End Includes
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
'   ** Constants **
'------------------------------------------------------------------------------
#PBFORMS Begin Constants
%IDD_DLGEJEMPLO      =  101
%IDC_LBLINSTRUCTIONS = 1003
%IDC_BTNCERRAR       = 1002
#PBFORMS End Constants
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
'   ** Declarations **
'------------------------------------------------------------------------------
DECLARE CALLBACK FUNCTION ShowDLGEJEMPLOProc()
DECLARE FUNCTION ShowDLGEJEMPLO(BYVAL hParent AS DWORD) AS LONG
#PBFORMS Declarations
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
'   ** Main Application Entry Point **
'------------------------------------------------------------------------------
FUNCTION PBMAIN()
    ShowDLGEJEMPLO %HWND_DESKTOP
END FUNCTION
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
'   ** CallBacks **
'------------------------------------------------------------------------------
CALLBACK FUNCTION ShowDLGEJEMPLOProc()

    SELECT CASE AS LONG CBMSG
        CASE %WM_INITDIALOG
            ' Initialization handler

        CASE %WM_NCACTIVATE
            STATIC hWndSaveFocus AS DWORD
            IF ISFALSE CBWPARAM THEN
                ' Save control focus
                hWndSaveFocus = GetFocus()
            ELSEIF hWndSaveFocus THEN
                ' Restore control focus
                SetFocus(hWndSaveFocus)
                hWndSaveFocus = 0
            END IF

        CASE %WM_COMMAND
            ' Process control notifications
            SELECT CASE AS LONG CBCTL
                CASE %IDC_BTNCERRAR
                    IF CBCTLMSG = %BN_CLICKED OR CBCTLMSG = 1 THEN
                        DIALOG END CBHNDL, 0
                    END IF

                CASE %IDC_LBLINSTRUCTIONS
            END SELECT
    END SELECT
END FUNCTION
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
'   ** Dialogs **
'------------------------------------------------------------------------------
FUNCTION ShowDLGEJEMPLO(BYVAL hParent AS DWORD) AS LONG
    LOCAL lRslt AS LONG

#PBFORMS Begin Dialog %IDD_DLGEJEMPLO->->
    LOCAL hDlg  AS DWORD

    DIALOG NEW hParent, "Ejemplo PBForms", 188, 216, 201, 121, TO hDlg
    CONTROL ADD BUTTON, hDlg, %IDC_BTNCERRAR, "&Cerrar", 75, 96, 50, 15, %BS_DEFAULT OR %WS_TABSTOP
    CONTROL ADD LABEL,  hDlg, %IDC_LBLINSTRUCTIONS, "Click en Cerrar para " + _
        "cerrar", 0, 0, 200, 80
#PBFORMS End Dialog

    CONTROL SET TEXT hDlg, %IDC_LBLINSTRUCTIONS, "Bienvenido a PB/Forms"
    DIALOG SHOW MODAL hDlg, CALL ShowDLGEJEMPLOProc TO lRslt

#PBFORMS Begin CleanUp %IDD_DLGEJEMPLO
#PBFORMS End CleanUp

    FUNCTION = lRslt
END FUNCTION
'------------------------------------------------------------------------------

PB/Forms, viene a ser como las Microsoft Foundation Classes (MFC) de Microsoft. Una ayuda para crear entornos gráficos, va más allá de un editor de recursos, pero no es una herramienta RAD propiamente dicha. Como habéis visto, genera código BASIC, que sigue el esquema de mensajes de Windows. Funciona muy bien, y el códido terminado, podemos pasarlo nuevamente a PB/Forms, y cambiar los componentes que queramos, todo ello, sin alterar ni romper el código que hayamos escrito a mano.

Es una evolución de lo que antes conocíamos como DDT (Dynamic Dialog Tools), una buena ayuda en aplicaciones sencillas, pero que se queda corta en desarrollos más complejos. En caso de duda a la hora de seguir el artículo, podéis echar mano de la documentación en linea.

Como muestras más avanzadas, tenemos CSV_Edit.exe, un completo ejemplo incluido en PB/Forms, que parte de más de 60 Kb. de código fuente (cerca de 2.000 lineas), pero que se compilan a un compacto ejecutable de apenas 55 Kb.

Ejemplo con PB/Forms

Pongo a vuestra disposición el ejecutable y el binario aquí (15 Kb. en formato ZIP).

4 comentarios en “Ejemplo con PB/Forms”

  1. vaya curro que te has pegado, menudo artículo, está genial, y muy bien explicado, como siempre.

    La verdad que el código impone bastante, por su extensión como bien dices. Por lo que veo el control de los forms es absoluto, pasando toda la responsabilidad al código y dejando el GUI en lo mínimo imprescindible para dibujarlo y listo. Se parece mucho a las MFC, como bien dices (cosa que nunca me gustó :D), en efecto no es una RAD, más bien es un diseñador de formularios. Me recuerda también mucho a Java en sus inicios, donde tenías que andar dibujándolo todo. Aunque en ese aspecto creo que PB es más cómodo. Quizá sea porque me gusta más esa cercanía con VB que con C/Java.

    No obstante pensaba que podría llegar a ser un potencial «sustituto moderno» a mi querido VB5 y no, por lo que veo, para nada 😀 Eso sí, el minimalismo y potencia de PB es indudable, de manera que para algunas aplicaciones realmente puede ser muy útil. Me gusta mucho esa no-dependencia externa, algo frente a lo cual el VB de Microsoft no puede hacer nada (aunque en un sistema operativo moderno, con toda la parafernalia y frameworks que ya se encargan en Microsoft de meterle, no creo que haya aplicación de VB que no funcione, exceptuando ActiveX o alguna cosita parecida muy concreta).

    Muchas gracias por el currazo Guti, y la estupenda redacción. Un post para conservar sin duda.

  2. Javier Gutiérrez Chamorro (Guti)

    Precisamente recuerdo en los tiempos de VB5, que es cuando descubrí Powerbasic para Windows. Un anuncio en una revista especializada, hablaba que Powerbasic generaba programas mucho más eficientes que Visual Basic. Citaba una reducción del espacio de 23 veces, que es justo lo que he encontrado en su web:

    So, how does it really stack up? In our comparisons, we’ve found that PowerBASIC can outperform Visual Basic by a factor from 3 to 23 times. That’s correct, it’s up to 2,300% faster than VB! Of course, PowerBASIC never requires any DLLs or run-times of any sort. So specifications on program size can be even more impressive. A «Hello, World!» program in VB is over 1,400,000 bytes with the necessary runtime code… but with PowerBASIC Compiler for Windows? Just 5,632 bytes on disk, or 2,615 bytes in memory. That’s something like 23,000% smaller than Visual Basic!

    En aquella época, no había C++ Builder, y Delphi me seguía sin gustar. En cambio VB me gustaba desde que lo conocí, salvo por su falta de rendimiento. Ahí es donde pensé que Powerbasic podría ser la clave.

    Sin embargo no era tan fácil. Lo que te sugerían es que en PB, hicieras tus funciones críticas, y generaras una DLL, que luego cargarías desde VB. Precisamente por eso, lo que ahora es PB/Win, antes era PB/DLL Compiler. PB era en aquellos días pura API de Windows, vamos ni siquiera esto tipo MFC que ya nos parece tedioso. El resultado que prometían, era facilidad de programación y RAD de VB, con rendimiento de PB. En cambio para mi, el resultado fue lo peor de ambos. Lentitud y runtimes de VB, sumado a la dificultad de programar, y sobre todo depurar esas DLL que hacías. En ese punto, dejé PB/DLL, y decidí crear esas DLL con Visual C++. Al fin y al cabo, era casi lo mismo una cosa que otra…

    Tiempo después, salió PB/CC, el compilador de consola que tan poco te gusta, y del que tengo un artículo pendiente. Ahí ya le vi la clave. Podías probar algoritmos en BASIC, o hacer aplicaciones compactas y eficientes, donde lo importante era la aplicación, y no la GUI. Un poco como el ROT13 que te enseñé.

    Muchas gracias por tus palabras.

  3. Tiene mucha lógica eso que explicas, y aclara bastantes conceptos sobre PB que en su web no cuentan (por causas obvias) pero que alguien interesado en desarrollar aplicaciones con esa herramienta debería conocer muy bien.

    Tu conclusión también es muy acertada: la auténtica virtud de PB se encuentra en el modo consola. Y claro, a mí el modo consola me gusta, y mucho, pero no para programar. Así que para los que no queremos meternos ahí PB se queda muy corto o, más bien, para aplicaciones muy concretas. No obstante, así y todo, es una buena solución y de hecho es una herramienta que me parece con mucho potencial. Lástima que, como contabas en el anterior post, tal vez no llegue a mucho más.

  4. Javier Gutiérrez Chamorro (Guti)

    BiaNamaran, voy siguiendo las novedades de Powerbasic Inc, pero de momento no se ha filtrado nada. Es una pena, el soporte Windows de 64 bits, o incluso un port de PB/CC para Linux u OS/X, sería genial.

    Sin embargo, parece que no va a tener un buen final. Será otro producto brillante, que quede abandonado, como Watcom. Probablemente peor que eso, porque dudo que liberan el código fuente, y que en el caso de hacerlo, alguien se encargue de mantenerlo. Espero estar errado.

Deja un comentario