25 de Julio de 1998

Truco Juegos I

Truco Juegos es una pequeña aplicación que empecé desarrollando para mi, la cual permite mantener una base de datos, que contiene trucos de juegos. Esta aplicación hace uso de muchas cosas que he venido comentado no solo en los capítulos de Bases de Datos, sino también en los capítulos anteriores. A lo largo de este capítulo y sucesivos ire explicando los diversos métodos que he usado para poder llevar a cabo el programa. Decir que este programa o aplicación es un "banco de pruebas" así que quizas los métodos usados no están todo lo depurados que deberian ser, pero tener en cuenta que el objetivo es aprender, creo que el código fuente de esta aplicación, el cual publicaré integro, lo deberias tener a mano para su estudio,

El programa permite tener los trucos de juegos ordenados por nombre y clasificados por un tema (arcade, coches,etc). Los temas los puede añadir o borrar a voluntad el usuario. Ante estas premisas que me planteé decidí que necesitaba dos tablas, una que contenga la lista de los juegos y otras que contega la lista de los temas existentes. Así la primera tabla la llamé datos y la segunda Temas. Su estructuras son como siguen:

Datos

  1. Nombre. Campo alfanumérico, 50 caracteres de longuitud
  2. Tema. Campo alfunumérico, 15 caracteres de longuitud.
  3. Truco. Campo BlobMemo.

Esta tabla contiene un dice por el campo Nombre.

Temas

  1. Tema. Campo alfunumérico, 15 caracteres de longuitud.

El campo Nombre, contendrá el nombre del juego. El campo tema, contendrá el tema por el cual esta clasificado el juego, y campo tema, contendrá una descripción del truco.

Uno de los objetivos del programa es tener clasificado los juegos por temas, y que cuando seleccione de un tema, que salgan solo los juegos que esten clasificados por ese tema. Para lo cual tenia dos opciones, relacionar las dos tablas por medio de una relación MasterDetail (de lo cual no he hablado), o cada vez que se seleccionara el tema realizar un filtrado de la tabla. La opción que escogí fue la segunda, ya que así hacía uso de los filtros, y además como la selección del tema no se hacia sobre un control de base de datos. Aunque yo haya usado este método también se puede hacer del otro.

Después de todo el planteamiento lógico llegó la hora pensar en el aspecto del programa, a la hora de mostrar los datos en pantalla. Por una parte tendrá que estar la lista de los temas disponibles, y por otra la lista de Juegos. Así que me decidí por un TDBgrid para mostrar la lista de juegos, el cual tiene el problema que no puede mostrar campos memo, y además personalmente no me gusta para introducir, pero si para mostrar, por lo tanto en el programa debería haber otra manera de mostrar el los datos. La solución que adopte fue colocar un control TPageControl, con dos hojas, una de las cuales contrendrá el TDBgrid, y la otra contendrá un control TDedit para mostrar el primer campo de la tabla Datos, un DbcomboBox que contendrá el tema, y los temas disponibles, y luego un control TDBmeno, para mostrar el texto con la descripción del truco.

Para la lista de temas lo lógico seria un DbcomboBox, o un TlistBox cargado en tiempo de ejecución. Pero me decidí por un solución más vistosa, y fue usar un TTreeView. Para aclarte el aspecto que tiene el programa hechale un vistazo a estas dos imagenes del programa funcionando.

Además de los controles que os cabo de comentar he colocado un control TStatusBar, que es la barra que hay en la parte inferior de la ventanal, para mostrar información sobre el estado del programa, y como cabe esperar, para navegar por la tabla de datos, he usado un DBNavigator.

Para acceder a las tablas he usado, dos componente TTable y dos TDataSourcer, uno para cada base de datos, pero la novedad está en que no los he colocado sobre el formulario principal, sino que he usado un formulario especial que incorpora Delphi 2.0 (y superiores, por lo menos hasta el 3), que se llama DataModule, el cual permite a los controles de varios formularios acceder a a una misma tabla más eficientemente. Para añadir un formulario de este tipo a tu aplicación lo puedes hacer desde la opción New Data Module del menú File del Delphi. Ten en cuenta que es una especie de formulario, así que para poder usar un componente situado en el, debes indicar al formulario desde el que quieres hacer referencia al formulario DataModule que lo use. Para ello debes indicar que tu formulario haga uso del DataModule como si se tratara de otro Formulario, para más información mira el capítulo Ventanas Modales de este cursillo. Por lo tanto verás que hay hay muchas líneas como esta:
DataModule2.Table1.Filtered := False;

Después de todo este rollo, hay que empezar. Lo primero que hay que tener claro en un programa en Delphi, es como empieza. Todos sabes que al arrancar se crean los formulario, y en nuestro caso hay más de uno, por lo tanto se creará el principal y posteriormente los otros. Por lo tanto es en este momento donde debemos realizar todas las operaciones de inicialización. El problema principal de este programa es la creación del arbol con los temas, y los datos son leidos de una tabla, la cual esta dentro de un DataModule, así que el momento de realizar este trabajo es cuando abrá la tabla correspondiente. Un error común entre la gente que empieza en Delphi es tratar de abrir la tabla en evento Oncreate del formulario principal, lo cual conduce a un error en tiempo de ejecución, ya que el DataModule no esta creado y por tanto el TTable tampoco, así que el sitio correcto es en el evento Oncreate pero del DataModule. Estas son las líneas que hay en nuestro programa.

procedure TDataModule2.DataModule2Create(Sender: TObject);
begin
  Table1.DatabaseName := Form1.Directorio;
  Table2.DataBaseName := Form1.Directorio;

  Table1.Active := True;
  Table2.Active := True;
  LeerTemas;
  Table1.First;
  Form1.CrearEstructura;
end;

En este procedimiento hay mucho. Por una parte se asigna a la propiedad DatabaseName contenido de la variable Directorio que está definida en el formulario Form1, en su parte Publica para ser accesible desde otra unidad. Si te pierdes en este concepto te recomiendo que visites los primeros capítulos de este cursillo. Esta variable contiene el directorio donde se esta ejecutando la aplicación, que es donde estan las bases de datos. Esto es un sistema para no tener que usar un alias.

Después abro las tablas, ya que en tiempo de diseño están cerradas. Lo siguiente es LeerTemas, que es un procedimientos declarado en esta unidad por mi. Y para terminar llamo a otro procedimiento que crea la estructura del arbol de temas.

Procedure TDataModule2.LeerTemas;
Begin
  Table2.First;
  While Not Table2.EOF Do
   Begin
     Form1.DbComboBox1.Items.Add (table2.FieldByName ('Tema').AsString);
     Table2.Next;
   End;
End;

Este procedimiento se encarga de leer la tabla de temas desde su principio y cada registro cargarlo en un DbComboBox que hay en Form1 el cual ordena automaticamente sus elementos, ya que su propiedad Sort esta en True. Así ya tenemos los elementos ordenados, aunque la tabla no tenga indice, y como después voy a crear el arbol de temas con los datos contenidos en este componente, el arbol quedará ordenado, todo esto es realizado en el procedimiento CrearEstructura contenido en el formulario Form1, vamos a verlo con detalle.

Procedure TForm1.CrearEstructura;
Var
   x : integer;
   Raiz : TTreeNode;
Begin
    With DbComboBox1 Do
    Begin
     Raiz := TreeView1.Items.Add (TreeView1.Selected,'Todos');
     For x := 0 To Items.Count-1 Do
     Begin
      TreeView1.Items.AddChild (Raiz,Items[x]);
     end;
    TreeView1.FullExpand;
    End;
    TreeView1.Selected := Raiz;
    StatusBar1.Panels[2].Text := 'Tema: Todos';
    DataModule2.ActualizaParcial;
End;

Lo primero es crear un elemento del cual dependerán el resto, este es el tema Todos, que tiene la particularidad qur cuando sea seleccionado se mostraran todos los trucos de juegos sin importar el tema por el cual estén clasificados. Luego recorrro los elementos del DbComboBox desde el primero hasta el último y los voy añadiendo al arbol, indicandole que son "hijos" o "ramas" del elemento que cree antes del bucle. Una vez que acabo expando el arbol, selecciono el primer elemento, muestro en la barra de estado que el tema que esta seleccionado que es raiz, y luego llamamo a un procedimiento que esta el DataModule2, llamado ActualizaParcial, veamos como es y que hace.

Procedure TDataModule2.ActualizaParcial;
Begin
 {Si el la tabla no está filtrada el número de registro aceptados en una
  filtración es cero, pero se visualizan todos, así que se toma el número
  de registros existentes en la tabla}
 If Not Table1.Filtered Then
    Parcial := NumeroTotalRegistros;
 With Form1.StatusBar1 Do
  Panels[3].text := IntToStr (Parcial)+'/'+IntToStr (NumeroTotalRegistros);
End;

Lo que hace esta rutina es comprobar si la tabla es preguntar como esta la tabla si esta filtrada, si no está filtrada el número de registros seran todos, pero por si su número han cambiado lo vuelve a consultar, lo cual hace por medio de la función NumeroTotalRegistros que esta en esta unidad. Luego muestra en la tercera sección de la barra de estado el resultado, esta rutina es algo liosa de ver, así que os recomiendo que pongais un punto de ruptura en el if, y veas que ocurre. Además esta rutina, es llamada constantemente desde diversos puntos del programa, cada vez que se borrar, añade algún registro, o se cambia de tema en el arbol de temas.

Un vez llegado a este punto, la aplicación ya esta lista. Ahora solo queda empezar a responder a las acciones del usuario. La primera que vamos a ver es responder al cambio de Tema, que es cuando un usuario selecciona un elemento del arbol. Esta acción vamos a gestionarla en el evento Onclick del arbol.

procedure TForm1.TreeView1Click(Sender: TObject);
Var
 Texto : String;
begin
 Texto := TreeView1.Selected.Text;

 If Texto <> 'Todos' Then
   DataModule2.Filtrar (Texto)
   Else
    DataModule2.Table1.Filtered := False;

 StatusBar1.Panels[2].Text := 'Tema: '+Texto;
 DataModule2.ActualizaParcial;
end;

Lo primero que hago es obtener el texto que contiene el elemento seleccionado. Si el elemento no es igual a Todos, significa que el usuario desea filtrar la base de datos, por lo cual llamo a un procedimiento que se llama Filtrar pasandole como parametro una cadena que contiene el tema por el cual va a ser filtrada, en caso anulo cualquier condición de filtrado que pudiera existir poniendo en falso la propiedad Filtered de la Table1 que esta en el DataModule2. Después, coloco en la barra de estado el tema seleccionado por el usuario, y llamo al procedimiento de ActualizaParcial, que he comentado antes.

Procedure TDataModule2.Filtrar (Var Condicion : String);
Begin
  Parcial :=0;
  Filtro := Condicion;
  Table1.Filtered := True;
  ActualizaParcial;

End;

El procedimiento de filtrado es muy sencillo; la variable Parcial, contiene el número de registros que se filtran, se inicializa a cero, se guarda el tema en la variable Filtro, que es una variable privada y se activa el filtro en la tabla, lo cual hace que se ejecute un procedimiento propio de la tabla (como explique en el capítulo anterior), y cuando termina llamo a nuestro conocido procedimiento ActualizaParcial.

procedure TDataModule2.Table1FilterRecord(DataSet: TDataSet;
  var Accept: Boolean);
begin
  Accept := DataSet ['Tema'] =Filtro;
  If Accept Then Inc (Parcial);
end;

Este es el procedimiento que se ejecuta cuando se activa el filtro de una tabla, y se ejecuta una vez por cada registro que contega la tabla, así que la condición que pongamos no debe ser muy complicada. Aqui acepto y registro si su campo Tema coincide con el filtro que ha elegido el usuario, después compruebo por medio del if que si he aceptado el registro incremento en uno la variable Parcial. He puesto la orden Inc (Variable) porque es más rápida y efectiva que el clásico Variable := Variable +1.

En el próximo capítulo explicaré más aspecto de este programa, concretamente el aspecto de añadir, modicar y borrar campos. En el area de descarga puedes obtener todos los ficheros que conforman el código fuente de esta aplicación, así como las tablas ya creadas y con datos, es recomendable que ejecutes el programa con puntos de parada para poder ir viendo lo que hace y como lo hace. El grueso del programa esta entre la unidad uno y la dos, fijate como en los ejemplos de procedimientos que hemos visto hoy y que hay a lo largo del programa hacen referencia a otras unidades, y concretamente como las dos unidades que acabo de nombrar se hacen referencia una a la otra, esto es posible porque la línea Uses que hay después de la palabra implenentación en cada unidad hace referencia a la otra unidad. Algunos libros y texto le llaman a esto referencia ciclica, porque es un como circulo vicioso.