Lazarus. Primeros pasos. Creación de un editor de textos
Para empezar, conozcamos un poco más de Lazarus y Free Pascal con este artículo: Lazarus. Primeros pasos. Creación de un editor de textos.
Puesto que ya habíamos realizado un breve introducción a esta estupenda herramienta, Lazarus, en esta ocasión avanzo un poco más mostrando cómo crear un editor de textos básico. He empleado el menor número de componentes y la menor complejidad que he estimado posible. Se ha creado con la versión 2.0.0 64 bits Windows de Lazarus.
El código lo he subido a un repositorio en GitHub: https://github.com/JosemaDR/OSFEditor para que podáis disponer de él libremente.
Objetivos
La idea con este artículo es:
- Conocer la estructura de un proyecto Lazarus.
- Declaración de variables y tipos de datos.
- Estructuras básicas de control.
- Crear un menú.
- Interactuar entre componentes.
- Utilizar cuadros de diálogo básicos.
- Uso básico del Portapapeles.
Sin ser ni mucho menos perfecto, espero que ayude a avanzar en el conocimiento de Free Pascal y Lazarus.
Entre sus funcionalidades están:
- Edición básica de textos simples sin formatear.
- Apertura de archivos de texto.
- Creación de nuevos ficheros de texto.
- Actualización de ficheros de texto ya existentes.
- Cortado, copiado, pegado básico de texto seleccionado.
Es simplemente un comienzo, más adelante, se avanzará más en una nueva versión del proyecto.
Estructura de un proyecto Lazarus
Un proyecto Lazarus consta, como siempre, de un grupo de ficheros y carpetas. Algunos de estos ficheros se van creando según se desarrolla y otros, cuando compilamos. Lo dividiremos en precompilación y postcompilación.
Precompilación
Ficheros:
- LPI: Lazarus Project Information. Información general del proyecto Lazarus.
- LPR: Lazarus Project SouRce. Código fuente del proyecto sin la clásica extensión PAS
de Pascal. - LFM: ficheros con la información, propiedades, etc., de cada formulario, pantalla o ficha de la aplicación.
- PAS: ficheros de unidades Pascal, incluyendo aquí el código fuente de los formularios. Por cada formulario existirá tanto un fichero LFM como un PAS.
Postcompilación
Durante la compilación se generan automáticamente una serie de ficheros y carpetas:
- ICO: icono de la aplicación.
- RES: fichero que contiene todas las imágenes asignadas a propiedades de componentes.
- LPS: fichero de sesión en Lazarus. Guarda los paneles abiertos, su situación, etc. La próxima vez que se utilice Lazarus, volverá a abrirse tal como se dejó el entorno antes de cerrarlo.
- EXE: fichero ejecutable de la aplicación (en Windows) y, esto es lo que más me gusta: totalmente portable.
- lib: carpeta que contiene los ficheros generados durante la compilación, antes de la creación del ejecutable, como los ficheros objeto (extensión O). Se guardan en una subcarpeta cuyo nombre indica el sistema para el que se ha compilado (en este caso, Windows 64 bits).
- backup: carpeta que guarda las copias de seguridad del proyecto.
Primeros pasos
Para crear el proyecto, simplemente abrimos Lazarus y éste nos aparecerá con un formulario por defecto. Para comenzar no necesitamos nada más: un solo formulario, pantalla o ficha, como queramos llamarlo.
Sin embargo, lo anterior nos servirá estamos utilizando por primera vez Lazarus, sino, aparecerá el último proyecto abierto y necesitaremos uno nuevo. Si es así, lo primero que haremos será pulsar la opción de menú [Archivo/Cerrar Todos]. No es obligatorio pero nos movemos más limpiamente. Lo segundo será crear un nuevo proyecto pulsando en la opción de menú [Archivo/Nuevo…]. Se abrirá una ventana emergente donde elegiremos [Aplicación] en la carpeta [Proyectos]:
Tras esto, guardaremos todo el proyecto tal cual, sin hacer nada más. Podemos guardar directamente con [Ctrl + S] o bien desde las opciones de menú [Archivo/Guardar], [Archivo/Guardar Como…] o bien [Archivo/Guardar Todo]. Como es la primera vez que guardamos el proyecto, se guardará todo. Obtendremos el mismo resultado pulsando el icono del disco o discos de la barra de herramientas, arriba a la izquierda del entorno de Lazarus.
Nombrar las entidades del proyecto Lazarus
En consecuencia de lo anterior, se nos pedirá un nombre para el proyecto (fichero LPI) y otro para la unidad Pascal (del formulario). Automáticamente se crearán y nombrarán otros ficheros. Es simple pero sin embargo, hay que tener en cuenta lo siguiente:
– La unidad Pascal del formulario (fichero PAS), se llamará igual que el fichero descriptivo del formulario (fichero LFM).
– El fichero descriptivo del proyecto (fichero LPI), no tiene por qué llamarse igual que el fichero con el código fuente del proyecto (LPR). Sin embargo, las referencias internas entre los diferentes ficheros del proyecto, han de tener ese nombre.
editor.texto.lpr
– En la línea 1, vemos que el nombre del programa coincide con el del fichero del proyecto: editor.texto.
– En la línea 10, observamos cómo en la cláusula uses, se emplea la unidad FEditorU que coincide con el nombre de los ficheros PAS y LFM, ya que se trata del formulario. Posteriormente, vemos que en la línea 19, se crea el formulario mediante Application.CreateForm
donde, nuevamente, indicamos el mismo nombre.
Dentro del entorno de Lazarus, si en algún momento queremos ver el código del proyecto, pulsaremos en la opción de menú [Proyecto/Ver Fuente Proyecto].
En el código del resto de ficheros, vemos que también se encuentran esos nombres referenciados.
Instanciar los componentes necesarios
Como toda herramienta RAD (Rapid Application Development), Lazarus dispone de un conjunto de controles o componentes para dotar de interfaz y ciertas funcionalidades a la aplicación. En este artículo que nos ocupa, utilizaremos:
– De la pestaña [Standard]: [TMainMenu] y [TMemo].
– De la pestaña [Dialogs]: [TOpenDialog] y [TSaveDialog].
Nada más. No cambiaremos las dimensiones del formulario ni lo moveremos. Simplemente, añadiremos al formulario activo los componentes indicados. Podemos arrastrarlos y situarlos donde nos interese o más fácil: doble pulsación del ratón en cada componente de la barra (tal como se indica en la imagen anterior) y automáticamente se instanciarán en el formulario. Da igual donde aparezcan. Después cambiaremos más cosas.
Establecer las primeras propiedades
En este punto tenemos un formulario y los componentes TMainMenu, TMemo, TOpenDialog y TSaveDialog instanciados en ese formulario. Vamos a cambiar algunas de sus propiedades y para ello será más fácil gracias al Inspector de Objetos. En este panel, aparecerán tanto el formulario activo como los componentes instanciados en él.
En conclusión, seleccionar un componente u otro o el propio formulario es muy fácil desde aquí. Simplemente, pulsamos con el ratón en el componente que nos interesa e inmediatamente, aparecerán debajo las propiedades:
Propiedades del formulario
Vamos primero con las propiedades del formulario. Éste por defecto recibe un nombre. Queremos cambiar ese nombre. Para nombrar cualquier componente incluyendo los formularios, podemos usar el llamado sistema «PascalCase». En el lenguaje Java, las clases han de nombrarse usando ese sistema. Aunque aquí no es obligatorio, es buena práctica.
Seleccionamos el formulario en el Inspector de Objetos. Si hemos seguido adecuadamente los pasos, se llamará Form1. Más abajo, en la pestaña de [Propiedades], buscaremos la propiedad [Name]. Es una propiedad que tiene todo componente. Veremos que su valor es Form1, lo cambiamos por algo como FMain (por formulario principal por ejemplo), como en el código en GitHub: FEditor o como nos parezca adecuado.
La propiedad [Caption] es el título del formulario o ventana. Cambiamos su valor a lo que nos interese.
La propiedad [Position] la cambiamos al valor: [poScreenCenter]. Esto hará que la ventana o formulario quede centrada en la pantalla al arrancar la aplicación.
Finalmente, la propiedad [WindowState] tendrá el valor: [wsMaximized]. Con esto, el formulario será maximizado nada más arrancar la aplicación.
Propiedades del componente Memo
Aparecerá en el Inspector de Objetos como Memo1 si hemos seguido la secuencia de pasos. Lo primero será cambiarle el nombre en la propiedad correspondiente, tal como hemos hecho con el formulario.
Este componente será el encargado de contener los textos que se editarán. Nos interesa que ocupe toda la ventana aunque en realidad, ocupará todo excepto el menú de nuestra aplicación y un pequeño margen.
Como al instanciarse en la ventana lo hace hacia la parte superior izquierda del formulario salvo que lo hayamos arrastrado, vamos a cambiar dos propiedades:
– [Left]: insertamos valor 10. Esto indica que su esquina superior izquierda estará a 10 pixeles del borde izquierdo de la ventana en la que está incluído. El clásico eje X de coordenadas.
– [Top]: insertamos valor 10. Esta propiedad hace referencia al lado superior de la ventana. Es el clásico eje Y de coordenadas.
Hemos situado el campo Memo o editor a 10 pixeles de la esquina superior izquierda de la ventana.
Propiedades del componente TOpenDialog
Dejamos las propiedades sin modificar, excepto [Name] para poner el nombre que estimemos oportuno.
Propiedades del componente TSaveDialog
También dejamos las propiedades con sus valores por defecto excepto [Name].
Propiedades del componente TMainMenu
Nuevamente, salvo la propiedad [Name], dejaremos todo tal cual. Éste componente es un poco especial y merece como tal, un trato especial.
Crear un menú en Lazarus
En Lazarus podemos crear los clásicos menús de aplicaciones de escritorio: menú principal y de contexto. Este ejemplo de editor solamente usa el primero que ya habremos instanciado.
Al instanciar un componente de tipo TMainMenu, su nombre (su propiedad [Name]) queda asociado inmediatamente a la propiedad [Menu] del formulario o ventana en la que se ha instanciado. Podemos comprobarlo en el Inspector de Objetos.
El componente TMainMenu parece disponer de pocas propiedades pero si desplegamos la propiedad [Items] comprobaremos que no es así y nos podemos perder un poco. Queremos crear un menú y hasta el momento no parece fácil (cuando si que lo es).
Hagamos doble pulsación con el ratón en el componente TMainMenu instanciado en la ventana. Aparecerá un cuadro de diálogo que nos permitirá gestionar este menú:
El panel de la izquierda aparece con sus controles desactivados porque no hemos creado ninguna opción de menú todavía. Su función es precisamente crear opciones de menú, subopciones, separadores, etc. Podemos usar estos controles más adelante o usar los menús contextuales, propiedades, etc.
El panel derecho solamente nos muestra el botón [Añadir elemento de menú]. Pulsamos ahí. Se crea una opción de menú con un nombre y propiedades por defecto; aparece el botón [Añadir submenú] y algunos controles del panel izquierdo se activan.
Si utilizamos el botón [Añadir submenú] agregamos eso, un submenú pero queremos otro a la derecha del primero que hemos creado; necesitaremos tres opciones de menú principales: Archivo, Edición y Salir. Utilizaremos el botón marcado en rojo de la imagen anterior.
– MenuItem1 (Archivo) contendrá las subopciones: Nuevo, Abrir…, Guardar…, Guardar como… y Salir.
– MenuItem2 (Edición) contendrá las subopciones: Cortar, Copiar, Pegar.
Seleccionamos la primera opción de menú. Tenemos que crear cuatro subopciones, un separador y una subopción más. Para añadir una subopción o submenú podemos elegir pulsar el botón [Añadir submenú], pulsar el botón con el símbolo más y flechas para abajo o simplemente [Ctrl + Insert].
Realmente, disponemos de tres formas de gestionar las opciones de los menús (no sus propiedades). Dos ya las he mencionado: panel izquierdo y panel derecho. Sin embargo, tanto en uno como en otro pueden faltarnos opciones para esa gestión. Existe una tercera forma: seleccionar una opción de menú con el ratón y, pulsando el botón derecho, usar el menú secundario o contextual. Para una determinada opción de menú, ahí aparecen todas las acciones que podemos realizar con ella: moverla, renombrarla, eliminarla, añadir opciones nuevas, subopciones, etc.
Tras añadir el primero de ellos, necesitaremos añadir más. Seleccionamos la recién creada subopción y ahora podemos pulsar en [Añadir elemento de menú] o el botón del panel izquierdo con el símbolo más y una flecha hacia abajo. Repetimos el proceso tres veces más.
Añadimos un separador pulsando en el botón correspondiente del panel izquierdo o simplemente, botón derecho sobre la última subopción creada y seleccionamos [Separadores/Añadir un separador después] del menú contextual.
Agregamos debajo del separador recién creado una última subopción. Botón derecho del ratón sobre el separador recién creado y pulsamos en [Añadir nuevo elemento antes].
Hecho esto, crearemos tres subopciones de la segunda opción principal del menú. Procederemos del mismo modo que acabamos de hacer. Finalmente, el resultado será algo como esto:
Es el momento de cambiar sus propiedades.
Propiedades de los elementos del menú
Cada uno de los elementos de menú creados (e incluímos el separador) es ahora un nuevo componente y, como tal, aparece en el Inspector de Objetos. Para cambiar las propiedades de cada uno de ellos, simplemente lo seleccionamos en el Inspector de Objetos y la pestaña [Propiedades] se actualizará con las del componente seleccionado.
Lo primero será cambiar los nombres de cada opción del menú: propiedad [Name] de cada uno de ellos.
Lo segundo que haremos es cambiar el texto de cada menú por el que le corresponde según vimos más arriba: propiedad [Caption]. Una particularidad de esta propiedad es que si usamos el carácter ampersand (&) delante de uno de los caracteres usados como valor de [Caption], será posible activar esa opción de menú pulsando [Alt + Carácter]: &Archivo, &Edición, &Salir. Podemos activar cada opción de menú respectivamente con [Alt + A], [Alt + E] y [Alt + S].
Hay un cambio adicional de propiedades: [ShortCut]. A las opciones de menú que no hayamos asignado tecla rápida con Alt (todas las subopciones), podemos agregarle tecla rápida modificando la propiedad [ShortCut]. Para darle un valor, elegimos una combinación de entre las que están en el desplegable de esta propiedad.
Con esto terminamos de editar las propiedades que necesitamos. El fichero FEditorU.lfm contendrá todas las propiedades del formulario por lo que otra forma de editar las propiedades de una ventana y sus componentes, es editar directamente su código. La tecla [F12] permite intercambiar entre la pantalla del formulario y su código. Para ver el código donde están las propiedades, podemos pulsar botón derecho del ratón sobre un área límpia del formulario y en el menú contextual resultante, pulsar en [Ver Fuente(.lfm)]:
FEditorU.lfm
Mostrando y comentando el código
El código del formulario (donde se definen las clases, instancian objetos, etc.) se encuentra en:
FEditorU.pas
Comentemos el código.
Variables globales al formulario
Las variables globales al formulario las declaramos e inicializamos en la sección var (líneas 50 a 58):
– cambiosGuardados : Boolean = true;
Esta variable de bandera sirve para saber si se ha guardado cualquier cambio en el texto del editor.
– tituloFormularioOriginal : String = 'OSFEditor';
Esta variable contiene una cadena que sirve como título básico de la ventana.
– textoGuardar : ShortString = '¿Guardar?';
Si hay cualquier cambio en el texto del editor, se avisa al usuario si lo desea guardar, añadiendo el valor de esta variable al título de la ventana.
– mostradoTextoGuardar : Boolean = false;
Esta variable sirve para controlar si aparece la cadena «¿Guardar?» o no, en el título de la ventana.
– esGuardado : Boolean = false;
¿Se han guardado los cambios? Otra variable bandera.
– ficheroGuardar : String = '';
Variable que guarda el nombre del fichero donde se guardará el texto editado y su ruta.
– FEditor: TFEditor;
Instanciación del objeto que conforma el formulario o ventana. Esto lo crea automáticamente Lazarus.
Cuando cambia el texto del campo de edición
Líneas 71 a 80. Es el código que se ejecuta cuando cambia el texto del componente TMemo.
procedure TFEditor.campoEdicionChange(Sender: TObject);
begin
{Ha habido un cambio: no se han guardado.}
cambiosGuardados := false;
{Si no se muestra "¿Guardar?" en el título, hay que mostrarlo y guardar esa condición.}
if not mostradoTextoGuardar then
begin
caption := caption + ' ' + textoGuardar;
mostradoTextoGuardar := true
end;
end;
Modificar el campo de edición para que ocupe toda el área disponible del formulario
Líneas 82 a 86. De forma dinámica, cada vez que el sistema pinta la ventana, el campo TMemo de edición, ocupará todo el espacio disponible.
procedure TFEditor.FormPaint(Sender: TObject);
begin
{Igualamos el ancho (Width) y alto (Height) del componente TMemo o editor (campoEdicion)
para que sea igual que el del formulario.
El objeto self hace referencia al formulario.
}
campoEdicion.Width := self.Width;
campoEdicion.Height := self.Height;
end;
Abrir un fichero de texto existente
Líneas 88 a 99.
procedure TFEditor.mAbrirClick(Sender: TObject);
begin
{El método Execute del componente TOpenDialog permite que se ejecute y controlamos que todo va bien.
se ha seleccionado un fichero para abrir, se toma su ruta y nombre (FileName).
El título del formulario añade la ruta y nombre del fichero abierto.
Desaparece del título del formulario la cadena "¿Guardar?"
Se establecen los cambios como guardados.}
if ventanaAbrir.Execute then
begin
ficheroGuardar := ventanaAbrir.FileName;
campoEdicion.Lines.LoadFromFile(ficheroGuardar);
self.Caption := tituloFormularioOriginal + ': ' + ficheroGuardar;
mostradoTextoGuardar := false;
esGuardado := true;
cambiosGuardados := true;
end;
end;
Gestión básica del Portapapeles
El componente de tipo TMemo ofrece una serie de métodos que nos permiten interactuar con el Portapapeles del sistema. Por ello, vamos a utilizarlos usando los componentes de menú que hemos destinado a [Edición].
Los iremos seleccionando en el Inspector de Objetos y pulsaremos en la pestaña [Eventos] que está a la derecha de la pestaña de [Propiedades]. Utilizaremos el evento [OnClick], pulsando en el botón con puntos suspensivos (…) del extremo derecho. Automáticamente el fichero PAS con el código del formulario se actualizará y generará un procedimiento vacío. El nombre del procedimiento es el nombre del componente al que pertenece el evento. Adicionalmente, el nombre se completa con un sufijo que corresponde al evento sin el On delante. Ejemplo: procedure TFEditor.mCopiarClick(Sender: TObject).
Líneas 101 a 109.
{
Lazarus genera los procedimientos necesarios para los eventos. Según la subopción de menú que elijamos, se realizará
una acción u otra sobre el Portapapeles:
- CopyToClipboard: copia el texto seleccionado al Portapapeles.
- CutToClipboard: corta el texto seleccionado al Portapapeles.
- PasteFromClipboard: pega el texto existente en el Portapapeles.
}
procedure TFEditor.mCopiarClick(Sender: TObject);
begin
campoEdicion.CopyToClipboard;
end;
procedure TFEditor.mCortarClick(Sender: TObject);
begin
campoEdicion.CutToClipboard;
end;
Líneas 162 a 165.
procedure TFEditor.mPegarClick(Sender: TObject);
begin
campoEdicion.PasteFromClipboard;
end;
Guardar el texto del editor en un archivo
Líneas 111 a 130.
{
Cuando se pulsa en la subopción de menú correspondiente y se dispara su evento OnClick:
- Particularmente comprobamos si la variable bandera esGuardado está a verdadero.
- Si está a verdadero, ya han sido guardados los cambios que hubiera en el editor. Si no es así, se ejecutarán
las demás instrucciones.
- Básicamente abrimos el cuadro de diálogo donde indicar un lugar y un archivo donde guardar los cambios. Llamamos al método .Execute para mostrar el cuadro de diálogo y comprobamos que se ha seleccionado o creado un nuevo archivo, mediante el if en el que se encuentra la llamada.
- Finalmente, aprovechamos el método .SaveToFile de la colección Lines (líneas) al que le pasamos como parámetro
la ruta y nombre de fichero utilizado por el cuadro de diálogo.
- El título del formulario cambia, indicando la ruta y el fichero donde se han guardado los cambios.
- Las variables bandera mostradoTextoGuardar, esGuardado y cambiosGuardados pasan a valer falso.
}
procedure TFEditor.mGuardarClick(Sender: TObject);
begin
if not esGuardado then
begin
if ventanaGuardar.Execute then
begin
ficheroGuardar := ventanaGuardar.FileName;
campoEdicion.Lines.SaveToFile(ventanaGuardar.FileName);
end;
end
else
begin
campoEdicion.Lines.SaveToFile(ficheroGuardar);
end;
self.Caption := tituloFormularioOriginal + ': ' + ficheroGuardar;
mostradoTextoGuardar := false;
esGuardado := true;
cambiosGuardados := true;
end;
Guardar como…
Líneas 132 a 143.
Esta subopción es prácticamente igual que la anterior. Como resultado, no comprobamos si se han guardado anteriormente los cambios. Al pulsar aquí, buscamos guardar si o si. El código de guardado es prácticamente idéntico al del apartado «Guardar el texto del editor en un archivo».
Nuevo archivo
Líneas 145 a 160.
Nos encontramos nuevamente con un código muy similar a Guardar y Guardar como… De este modo, se permiten guardar los cambios antes de continuar, si los hubiera y después aparece la diferencia: se limpia el editor de cualquier texto y el título del formulario refleja el cambio. Son las líneas 158 y 159:
self.Caption := tituloFormularioOriginal + ': Archivo nuevo no guardado';
campoEdicion.Lines.Clear;
De nuevo, el método Clear de la colección Lines del editor, realiza la acción de limpiar todo el texto de éste.
Eventos
En conclusión por lo visto, comprobamos que son necesarios realmente dos tipos de evento: OnChange y OnClick aunque eso sí, en diferentes componentes:
– OnChange se aplica al componente TMemo, al editor. De esta forma, cada vez que cambia el texto que contiene el editor, se dispara el evento OnChange.
– OnClick. Aplicado a diferentes componentes, se ejecuta como resultado de pulsar con el ratón sobre los componentes que lo implementan. Del mismo modo, también se dispara en aquellos componentes que tienen asignada una tecla rápida (como las opciones de menú).
Código del proyecto
Terminamos. Finalmente, en cuanto al proyecto en sí, vemos que el código de éste, se trata en realidad de un fichero XML. Igualmente podemos sacar en conclusión que es posible editar estos ficheros directamente desde código. Efectivamente se puede hacer pero hemos de tener mucho cuidado porque hay referencias a variables, objetos o componentes en diferentes ficheros. Lo mejor es utilizar las opciones que Lazarus nos da para por ejemplo, eliminar un componente: refactorizará por nosotros; cambiamos el nombre de un componente: refactorizará por nosotros.
editor.texto.lpi
Conclusión
Realmente aprendí a «programar de verdad» con Pascal y con Delphi; realicé unas aplicaciones de gestión que a día de hoy ahí siguen; ningún problema de instalación, ningún problema con el Registro de Windows. Es un placer cada vez que trabajo con Lazarus. Aquí lo doy a conocer un poco más. Espero que os dé tantas satisfacciones como a mí.
Nada más (y nada menos), espero que os haya gustado este tocho de artículo y el pequeño proyecto libre que lo sustenta. Un saludo y hasta otra.
2 comentarios
Hello I am so excited I found your web site, I really found you by accident, while I was researching on Digg for something else, Anyhow I am here now and would just like to say cheers for a incredible post and a all round enjoyable blog (I also love the theme/design), I donít have time to read it all at the minute but I have bookmarked it and also added in your RSS feeds, so when I have time I will be back to read a lot more, Please do keep up the fantastic work.
Thank you very much! We will continue working on it