Inicio > FireMonkey, OOP, XE2 > VCL y FireMonkey

VCL y FireMonkey

Share Button

imagen1

Está claro que FireMonkey (FM) es una tecnología súmamente atractiva y desde el mismo día que llegó (y vimos los primeros vídeos), la mayoría de nosotros está deseando poder integrarla en nuestras aplicaciones.

En el momento en que nos planteamos desarrollar aplicaciones nuevas, podemos tomar la decisión de escoger FireMonkey, si nuestras necesidades están cubiertas por esta librería (componentes y funcionalidad),  pero la pregunta que nos hacemos muchos de nosotros es:

¿Y qué hay con las aplicaciones que ya tenemos?

Está claro que no podemos “mezclar” en un proyecto formularios de la VCL y de FireMonkey. La ayuda nos dice lo siguiente (docwiki):

______________________________________________________________________
Caution:
FireMonkey (FMX) and the Visual Component Library (VCL) are not compatible and cannot be used in the same project or application. That is, an application must be exclusively one or the other, either FireMonkey or VCL. The incompatibility is caused by framework differences between FireMonkey (FMX) and VCL.
______________________________________________________________________

¿O si? (bueno luego hablaremos de esto…)

Sólo basta con intentarlo en el IDE; Creamos un proyecto/aplicación VCL e intentamos añadir un formulario “tipo FireMonkey”; Instantáneamente obtendremos un mensaje como este:

Imagen907

El mensaje es claro. No podemos añadir a un “proyecto VCL” un formulario FireMonkey.   ;-(
Bueno, no desesperemos; Delphi es mucho Delphi y ofrece “otras” posibilidades… (Packages)

Me he planteado este problema, mi cabeza ha empezado a dar vueltas y he comenzado a “trastear”…  ;-D

NOTA: Estoy pensando en aplicaciones que tenemos funcionando en Windows y que queremos que sigan así (descartando características de Multiplatarforma –al menos a priori-).

NOTA2: Como en las últimas entradas, he cogido Delphi XE2 y he empezado desde cero.

PRIMERA CUESTIÓN

Crear un package con un formulario FireMonkey.

Pues me he puesto manos a la obra…
He abierto un nuevo package y he añadido un formulario de FireMonkey; Un par de controles, un Timer y una vez compilado, se ha generado el package correspondiente (sin aparentes problemas).

Imagen908

Como veis a la izquierda la estructura del formulario es sencilla y es lo único que posee el package.

SEGUNDA CUESTIÓN:

¿Podré cargar este package desde una aplicación Delphi (VCL)?

Bueno, si el package se ha generado sin problemas, cargarlo de forma dinámica desde una aplicación no tendría porque fallar.

Creo un proyecto VCL estándar y utilizo el procedimiento LoadLibrary para cargar una BPL de disco; La BPL en la que he añadido el formulario FireMonkey. El código de la carga es el mismo que para cualquier otro package.

 

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var
  H: Thandle;
  fName:String;
begin
  // Nombre del package
  fName := Edit1.Text;
  // existe el fichero?
  if not FileExists(fName) then begin
    Memo1.Lines.Add('Error al cargar el package; Fichero inexistente: ' +
      fName);
    Exit;
  end;
 
  Memo1.Lines.Add('Se va a cargar el package...');
  // Cargando
  H := LoadPackage(Edit1.Text);
  // Cargado correctamente?
  if (H > 0) then begin
    Memo1.Lines.Add('El package se ha cargado correctamente (' +
       IntToStr(h) + ')');
  end
  else begin
    Memo1.Lines.Add('Error, no se ha cargado el packae');
  end;

 

En este caso el nombre del package lo “pasamos” utilizando un componente de edición. Además he añadido al packages un par de procedimientos de INITIALIZATION y FINALIZATION; El de inicialización se encarga de crear una instancia del formulario y mostrarla por pantalla, mientras que el de finalización lo libera. El código que añadiríamos al final del .PAS sería este:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
procedure Start_Pack();
begin
  // Crear el formulario
  FormMainFM := TFormMainFM.Create(nil);
  FormMainFM.ShowModal;
end;
 
procedure Finish_Pack();
begin
  // Liberar el form
  FormMainFM.Free;
end;
 
//=========================================================================
// I N I T I A L I Z A T I O N
//=========================================================================
initialization
  Start_Pack();
  RegisterClass(TFormMainFM);
 
//=========================================================================
// F I N A L I Z A T I O N
//=========================================================================
finalization
  Finish_Pack();
  UnregisterClass(TFormMainFM);

Si ejecutamos el programa podemos ver que el Package se carga correctamente y que en ese momento se ejecuta el procedimiento de inicialización, con lo que se crea el formulario (FMX). He añadido estilos a ambos formularios para que se aprecie la diferencia; Al formulario de la VCL con los Visual Styles y al de FireMonkey con el componente TStyleBook. El resultado es el que véis aquí:

Imagen909

Llegados a este punto. Pues he de decir que yo personalmente me he llevado una sorpresa. Ya se lo que dice la ayuda (lo he vuelto a leer), pero la aplicación se ha ejecutado y aunque es sencilla, aparentemente no parece tener problemas (he realizado la prueba en XP, en Windows Server y en Windows 7) .

Resumiendo… Lo que he hecho hasta ahora es generar un proyecto en Delphi (VCL) compilarlo sin runtime packages y cargar una BPL de forma dinámica (como si fuera una DLL) que a su vez muestra un formulario “FireMonkey” cuando se Inicializa. No está mal, aunque podemos ir un poco más allá.

¿Me pregunto qué pasará cuando intente acceder por RTTI a la información del package?

TERCERA CUESTIÓN:

Acceder por RTTI desde una aplicación “VCL” a los métodos de un form “FireMonkey” almacenado en una BPL.

Para ello lo primero que debemos hacer algunos cambios:

  1. Nuestra aplicación  VCL, ahora debe compilar con “runtime packages” para poder compartir la información RTTI con los packages dinámicos.
  2. En nuestro package que contiene el formulario “FireMonkey” vamos a Registrar la clase del formulario, para poder acceder a ella posteriormente con GetClass. Para ello utilizaremos los procedimientos de Inicialización y Finalización vistos anteriormente.
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    //=========================================================================
    // I N I T I A L I Z A T I O N
    //=========================================================================
    initialization
      //-- Start_Pack();  // Lo crearemos “manualmente”
      RegisterClass(TFormMainFM);
     
    //=========================================================================
    // F I N A L I Z A T I O N
    //=========================================================================
    finalization
      //-- Finish_Pack();
      UnregisterClass(TFormMainFM);
  3. A nuestro formulario vamos a añadir en la parte published, varios métodos de clase (aunque esto último no es necesario) a los que accederemos vía RTTI. Un Timer y un par de procedimientos para controlar una animación. Además de un procedimiento que creará el formulario (ExecForm).
  4. Por último vamos a eliminar la creación del formulario en la carga del package, para hacerlo nosotros vía RTTI. Si os fijáis en el código superior, he comentado los procedimientos Start_Pack y Finish_Packque se encargaban de la creación y destrucción.
    0
    1
    2
    3
    4
    5
    
      // en la parte published del formualrio
      published
        // Método de clase para crear y visualizar
        class procedure ExecForm();
        class procedure Start();
        class procedure Stop();

     

    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
      // Y la implementación (asumimos que el formulario ya estará creado)
      //...// Actiar el Timer para iniciar la animación
    class procedure TFormMainFM.Start;
    begin
      FormMainFM.TimerRot.Enabled := True;
    end;
     
    // Para el Timer y detener la animación
    class procedure TFormMainFM.Stop;
    begin
      FormMainFM.TimerRot.Enabled := False;
    end;
     
    // Crear el formulario y visualizarlo
    class procedure TFormMainFM.ExecForm();
    begin 
      FormMainFM := TFormMainFM.Create(nil); 
      FormMainFM.Show;
    end;

Hecho esto, nuestro método de carga ahora es un poco más complejo. Una vez cargado el package de forma dinámica, busco mediante RTTI la clase del formulario e intento crearlo. El código es el siguiente:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
var
  H: Thandle;
  res:integer;
  fName:string;
  AClass:TPersistentClass;
begin
 
  fName := Edit1.Text;
 
  // existe el fichero?
  if not FileExists(fName) then begin
    Memo1.Lines.Add('Error al cargar el package; Fichero inexistente: ' +
       fName);
    Exit;
  end;
 
  Memo1.Lines.Add('El fichero de package existe en disco');
  Memo1.Lines.Add('Se va a cargar el package...');
 
  // Cargando
  H := LoadPackage(Edit1.Text);
 
  // Cargado correctamente?
  if (H > 0) then begin
    Memo1.Lines.Add('El package se ha cargado correctamente (' +
         IntToStr(h) + ')');
 
    // Acceder a la clase del form
    AClass := GetClass('TFormMainFM');
    // encontrada
    if Assigned(AClass) then begin
      Memo1.Lines.Add('AClass<>nil; Encontrada la referencia' +
          ' al formulario');
      // Crear el form
      F := TFormClass(AClass).Create(nil);
      Memo1.Lines.Add('Creado el form correctamente; Accediendo ' +
        ' a los métodos');
      // Acceder al método
      Routine.Data := Pointer(F);
      // Ejecutar al código
      Routine.Code := (F).MethodAddress('ExecForm');
 
      // No ha encontrado el código de la rutina...
      if (Routine.Code <> nil) then begin
        Memo1.Lines.Add('Se ha encontrado el punto de entrada del método ');
        Memo1.Lines.Add('Se va a ejecutar el método...');
        // Ejecutarlo
        TExecuteExecForm(Routine);
        Memo1.Lines.Add('Ejecutado OK. Form visible');
 
        btnStart.Visible := True;
        btnStop.Visible := True;
        Button1.Enabled := False;
      end
      else  begin
        Memo1.Lines.Add('No se ha encontrado el punto de entrada del método ');
      end;
    end
    else begin
      Memo1.Lines.Add('Error, no se ha encontrado la referencia a la ' +
        'clase en el package ' + fName);
    end;
  end
  else begin
    Memo1.Lines.Add('Error, no se ha cargado el package');
  end;
end;

 

He utilizado un Memo en el formulario principal para ir mostrando los diferentes mensajes (los pasos que se van realizando). Una vez que ejecutamos el código, podemos ver que la salida de LOG es la esperada y el formulario se visualiza correctamente.

 

Imagen910

 

De la misma manera, si ejecutamos los métodos Start y Stop (vía RTTI) funcionan de forma correcta.

CONCLUSIÓN:

La primera conclusión que extraigo de todo esto (contando que las pruebas que he realizado son bastante sencillas), es que utilizando packages (creo que es la mejor forma), tenemos la posibilidad de utilizar FireMonkey en una aplicación “VCL”. Está claro que esto invalida la posibilidad de utilizar la “multiplataforma” con esa aplicación, pero tal y como os he comentado, no era ese el objetivo de estas pruebas.

¿Para qué puede servir esto?

Se me ocurren a priori 2 situaciones donde esto podría ser “utilizable”.

La primera es que tengamos aplicaciones antiguas desarrolladas en Delphi y a las que queramos “añadir” alguna característica donde FireMonkey pueda aplicar su potencial. Por ejemplo, un módulo donde tengamos que realizar animaciones o visualizaciones 3D; Incluso sacar partido de la facilidad que FireMonkey tiene para trabajar con modelos importador de herramientas de modelado (3DS).

La segunda, es la de poder “migrar” de forma paulatina aplicaciones que tengamos funcionando; Si cumplen unos determinados requisitos, puede ser que la migración se pueda hacer de forma escalonada. En este caso ya deberían trabajar con packages y facilitaría mucho si la lógica de negocio la tenemos “independiente” a la parte de Interficie.

Todo esto con la RESERVA de ver qué otras implicaciones más profundas puede tener este diseño. El hecho de que Embarcadero diga que es incompatible utilizarlos en el mismo proyecto, debe ser por algo, aunque en este momento no se porqué.  ¿?¿?¿?

 

BONUS TRACK:

Por último se me ha ocurrido hacer una prueba más. Probar, por probar,…    ;-)

Crear un proyecto “VCL”, y añadir directamente un formulario FMX al proyecto. Aunque en IDE avisa con un Warninig, deja hacerlo.
Compilas, ejecutas y ¡Voilà! ¡Sorpresa!

Se ejecuta perfectamente.

En este punto he buscado por Internet si a alguien más se le ha ocurrido hacer esto mismo y he encontrado algunas referencias. La más interesante en StackOverFlow (y con esto completo la entrada) es que apenas con 3 líneas, podemos conseguir, no sólo abrir un formulario FireMonkey como os he mostrado, sino “incrustarlo” dentro de uno “VCL”.

0
1
2
3
4
5
6
procedure TForm3.Button1Click(Sender: TObject);
begin
  FormMainFM := TFormMainFM.Create(nil);
  FormMainFM.Show;
  Panel1.Form := FormMainFM;
  FormMainFM.Start;
end;

 

     

Aquí os enseño las imágenes en diseño y en ejecución.

Vuelvo a decir, que todo esto no creo que se pueda tomar como base para diseño de aplicaciones, entre otras cosas porque las pruebas son bastantes  “simples” y porque desde Embarcadero lo desaconsejan, pero se puede tener en cuenta como alternativa puntual.

Os adjunto los enlaces a los proyectos con código y a los ejecutable compilados por si queréis verlos funcionando. Los segundos están comprimidos con UPX para reducir su tamaño.

Aunque ya he comentado el enlace, os animo a revisar la entrada en Stackoverflow, ” Delphi XE2: Possible to instantiate a FireMonkey Form in VCL application?”

Os añado también un enlace al blog de RemObjects, donde aparece una referenca a esta cuestión; “Hydra and FireMonkey – Best Friends Forever”.

Como siempre, comentarios, quejas, críticas, sugerencias,…  serán bienvenidas.

Un saludo.

Share Button
Categories: FireMonkey, OOP, XE2 Tags: , ,
  1. miércoles, 5 de octubre de 2011 a las 12:35 | #1

    Yo creo que es una limitación impuesta, ya que como dices se pierde la “multiplataformidad” (toma ya palabro inventado). En realidad funciona porque en Windows tanto VCL como FireMonkey utilizan el API de Windows. O eso pienso.

    Por cierto, ¿qué sistema operativo usas? Porque me ha parecido ver un estilo KDE por ahí…

  2. Neftalí
    miércoles, 5 de octubre de 2011 a las 12:52 | #2

    @Ñuño Martínez
    Sí parece que funciona, con algunas limitaciones, pero llama la atención la afirmación que se hace desde Embarcadero, que parece bastante “tajante”.

    Como he dicho, no es una buena solución de futuro para nuevos desarrollos (la mezcla), porque estamos “perdiendo”, como tú dices, una de las características principales de FM, pero es interesante poder utilizarla “como puente” en aplicaciones que ya tenemos actualmente (y que no son multiplataforma -por lo tanto no perdemos nada-).

    KDE¿?¿? ¿Qué es eso? ;-D
    Uso un XP, que va de maravilla y muy rápido en la máquina que tengo.
    Aunque tengo algun otro (entre ellos un Ubuntu) virtualizados, para temas de pruebas.

    Un saludo.

  3. miércoles, 5 de octubre de 2011 a las 12:59 | #3

    Como siempre, estupenda entrada.
    Un placer seguir tus inquietudes con el nuevo XE2.

  4. miércoles, 5 de octubre de 2011 a las 13:03 | #4

    Por cierto, en referencia al KDE de ÑuÑo.
    Yo tengo algo similar a lo tuyo pero en sentido contrario.
    Utilizo debian con KDE, y virtualizo XP. Como dices, XP va de maravilla, pero me siento mas cómodo y seguro dejando los archivos y la navegación web sobre linux.

  5. miércoles, 5 de octubre de 2011 a las 13:06 | #5

    @Neftalí
    KDE es una extensión de las XWindow (ya sabes, UNIX, Linux, etc) basada en Qt (esta última sí que te sonará, ¿no?). La ventana del fondo del ejemplo de la Segunda cuestión se parece a uno de los estilos que pueden asignarse (también esiste para Gnome, que es otra extensión de las X solo que basada en GTK+).

  6. Neftalí
    miércoles, 5 de octubre de 2011 a las 14:51 | #6

    @Ñuño Martínez
    Lo se, lo se, Ñuño; Era un forma de hablar… ;-)

  7. miércoles, 5 de octubre de 2011 a las 22:07 | #7

    Hola Germán:

    Muy interesante. jejeje ¿Recuerdas que comentábamos -durante el evento-, algo similar?. El problema de fondo es que no tienes la completa garantía de que pueda existir algún tipo de incompatibilidad sobrevenida, ya en un desarrollo complejo.

    Hubiera sido ideal que aunque ambas librerías coexistan en el entorno, tuvieran algún punto de interacción, de cara a dotar a algunos desarrollos VCL de las nuevas funcionalidades en un marco concreto, sin obligar a la compañía o al desarrollador a tener que rehacer toda la aplicación para migrar a FMX. Yo creo que aun teniendo una herramienta que facilite la migración del desarrollo en muchas casos será inviable.

    Si alguien avanza y hace pruebas que sean de garantía y pone unas condiciones de acoplamiento puede ser un punto muy interesante. :-)

  8. Neftalí
    miércoles, 5 de octubre de 2011 a las 23:05 | #8

    @salvador jover
    Hola Salvador.
    Pues justo a raiz de la conversación que mantuvimos nació la inquietud y la idea de realizar esta pruebas.

    Yo creo que la utilización de packages es bastante “segura”, aunque menos potente. la utilización de RTTI ya es más compleja, pero al fin y al cabo si lo pensamos, FireMonkey no deja de ser una librería de componentes más (en cierta forma).

    Esperemos que con el tiempo se vaya profundizando en el tema.

  9. casimiro
    jueves, 6 de octubre de 2011 a las 09:02 | #9

    Puede ser que Embarcadero diga que no sean compatibles porque habrá alguna característica que no funciona y no hayan podido/querido dedicarle más tiempo a solucionarlo. De esa manera si encuentras un problema entonces pueden decir: “ya avisé de que no son compatibles”.
    Porque *teóricamente* no tiene por qué haber incompatibilidad.
    Aunque son sólo conjeturas y suposiciones mías.
    Saludos.

  10. Neftalí
    jueves, 6 de octubre de 2011 a las 09:43 | #10

    @casimiro
    Pues tal y como tú comentas, sólo son conjeturas, porque la página web no explicada nada. ¿?¿?¿
    Nos quedamos con la duda, de todas formas estas cosas van saliendo a medida que se experimenta.

    Un saludo.

  1. Sin trackbacks aún.
What is 19 + 20 ?
Please leave these two fields as-is:
IMPORTANTE! Para continuar, debes contestar la pregunta anterior (para evitar SPAM) :-)