Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Trucos (https://www.clubdelphi.com/foros/forumdisplay.php?f=52)
-   -   Recorrer objetos dentro de un TFMXObject (https://www.clubdelphi.com/foros/showthread.php?t=90042)

jhonny 24-03-2016 17:21:22

Recorrer objetos dentro de un TFMXObject
 
En Firemonkey, recorrer los objetos que hay dentro de otro es muy sencillo, basta con hacer uso del método EnumObjects que pertenece a la clase TFMXObject... por ejemplo, si queremos saber si existe un TLabel dentro un TTabControl podemos hacerlo de la siguiente manera:

Código Delphi [-]
TabControl1.EnumObjects(
      function (vObjeto: TFMXObject): TEnumProcResult
      begin
        Result := TEnumProcResult.Continue;
        if (vObjeto is TLabel) then
        begin
          ShowMessage('Encontré el Label, ¡victoria!');
          Result := TEnumProcResult.Stop;
        end;
      end
    );

En http://purodelphi.com/2016/03/23/enu...un-tfmxobject/ he creado un artículo con una explicación un poco más extensa de lo que aquí menciono.

Ñuño Martínez 26-03-2016 12:01:06

Buen truco. Estaría bien que Lazarus cambiara en esa dirección también. Ahora mismo sigue la senda de VCL.

Por cierto, un pequeño cambio:

Código Delphi [-]
TabControl1.EnumObjects(
      function (vObjeto: TFMXObject): TEnumProcResult
      begin
        if (vObjeto is TLabel) then
        begin
          ShowMessage('Encontré el Label, ¡victoria!');
          EXIT (TEnumProcResult.Stop)
        end;
        Result := TEnumProcResult.Continue
      end
    );
Parece una tontería, pero será ligeramente más rápido ya que se ahorra una asignación si encuentra un TLabel.

Si se sabe que los TLabel son escasos, se puede optimizar incluso un poco más:

Código Delphi [-]
TabControl1.EnumObjects(
      function (vObjeto: TFMXObject): TEnumProcResult
      begin
        IF NOT (vObjeto IS TLabel) THEN EXIT (TEnumProcResult.Continue);
        ShowMessage('Encontré el Label, ¡victoria!');
        RESULT := TEnumProcResult.Stop
      end
    );
Hoy tengo el día programadoril...

jhonny 26-03-2016 17:30:00

Que bien :), me gustan, otra opción para evitar dicha asignación es el típico:

Código Delphi [-]
TabControl1.EnumObjects(
      function (vObjeto: TFMXObject): TEnumProcResult
      begin        
        if (vObjeto is TLabel) then
        begin
          ShowMessage('Encontré el Label, ¡victoria!');
          Result := TEnumProcResult.Stop;
        end
        else
          Result := TEnumProcResult.Continue;
      end
    );

Pero ya que estamos en día programadoril (jejeje vaya día), se me ocurre que podríamos crear un IfThen genérico para estos casos.

Al González 26-03-2016 21:11:50

Le di un tweet desde que lo vi, Jhonny. Gracias por el aporte. :)

Si tuviera yo que emplear el mismo código, copio aquí cómo lo escribiría (no lo he compilado, aclaro):
Código Delphi [-]
TabControl1.EnumObjects (
  Function (AObj :TFMXObject) :TEnumProcResult
  Begin
    If AObj Is TLabel Then
    Begin
      FMX.Dialogs.ShowMessage ('Encontré la etiqueta, ¡victoria!');
      Result := TEnumProcResult.Stop;
    End
    Else
      Result := TEnumProcResult.Continue;
  End);

Aunque tomando algo de las capacidades de la próxima y prorrogadamente reconstruida GH Freebrary, probablemente escribiría algo así:
Código Delphi [-]
TabControl1.EnumObjects (
  Function (AObj :TFMXObject) :TEnumProcResult
  Begin
    If Result.ghStopWhen (AObj Is TLabel) Then
      FMX.Dialogs.ShowMessage ('Encontré la etiqueta, ¡victoria!');
  End);

Una variante más nativa y estándar, en caso de que yo trabajara en Embarcadero/Idera y me aceptaran añadir estas cosas, podría ser:
Código Delphi [-]
TabControl1.EnumObjects(
  function (AObject: TFMXObject): TEnumProcResult
  begin
    if Result.StopWhen(AObject is TLabel) then
      FMX.Dialogs.ShowMessage('I found the label, bingo!');
  end);

Saludos.

Al González.

jhonny 26-03-2016 21:39:22

Gracias por compartirlo Al.

Pinta bastante bien la forma como lo has simplificado :)

Una pregunta para Al, ¿ghStopWhen es algún método de algún ayudante de TEnumProcResult?, me gustaría (si es posible) ver el código de ghStopWhen, le he hecho click al enlace que tienes en la firma, con el animo de ver su magia pero me sale una página en blanco.

AgustinOrtu 26-03-2016 21:44:04

Muy bueno, me recuerda mucho a esta pequeña joya creada por David Heffernan que utilizo para Vcl, y me permite escribir este tipo de codigo, utilizando genericos, predicados y acciones:

Código Delphi [-]

procedure TForm1.btnCountAllClick(Sender: TObject);
begin
  InfoMsgFmt('%d', [TControls.ChildCount< TWinControl >(Self)]);
end;

procedure TForm1.btnCountButtonsClick(Sender: TObject);
begin
  InfoMsgFmt('%d', [TControls.ChildCount< TWinControl >(Self, function(AControl: TWinControl): Boolean
  begin
    Result := AControl is TButton
  end)]);
end;

procedure TForm1.btnWalkControlsClick(Sender: TObject);
begin
  TControls.WalkControls< TWinControl >(Self, procedure(AControl: TWinControl)
  begin
    InfoMsg(AControl.ClassName);
  end);
end;

Saludos

jhonny 26-03-2016 22:26:40

Cita:

Empezado por AgustinOrtu (Mensaje 503749)
Muy bueno, me recuerda mucho a esta pequeña joya creada por David Heffernan que utilizo para Vcl, y me permite escribir este tipo de codigo, utilizando genericos, predicados y acciones:

Acabo de echarle un vistazo y estoy de acuerdo con lo que dices, una joya.

Al González 27-03-2016 06:14:47

Cita:

Empezado por jhonny (Mensaje 503748)
¿ghStopWhen es algún método de algún ayudante de TEnumProcResult?

La idea es que en efecto lo sea. Es la primera vez que oigo hablar del tipo TEnumProcResult, pero en cuanto tenga que usarlo no dudaré en crearle su extensión, es decir, su ayudante, escribiendo un método como ghStopWhen o algún nombre similar (se aceptan sugerencias de otros posibles identificadores).

En cuanto a la forma de escribir ese método, una manera simple podría ser como en el siguiente código. Uso para este ejemplo el tipo enumerado TEnumControlsResult, el cuál sí está presente en mi versión de Delphi (esto sí lo he probado):
Código Delphi [-]
Type
  TEnumControlsResultHelper = Record Helper For TEnumControlsResult
    Function ghStopWhen (Const ACond :Boolean) :Boolean;
  End;

//...

  Function TEnumControlsResultHelper.ghStopWhen (Const ACond :Boolean) :Boolean;
  Begin
    If ACond Then
      Self := TEnumControlsResult.Stop
    Else
      Self := TEnumControlsResult.Continue;

    Result := ACond;
  End;

Y ya en estas, considerando que los valores ordinales de System.False, System.True, TEnumControlsResult.Continue, TEnumControlsResult.Discard y TEnumControlsResult.Stop son 0, 1, 0, 1 y 2, respectivamente, una implementación más programadoril, que consume menos ciclos de CPU sería:
Código Delphi [-]
  Function TEnumControlsResultHelper.ghStopWhen (Const ACond :Boolean) :Boolean;
  Begin
    Self := TEnumControlsResult (Ord (ACond) * 2);
    Result := ACond;
  End;

Además el método podría ser declarado Inline, aunque en ese caso modificaría un poco su implementación a fin de resolver cierta impericia del compilador (ese tema me gustaría discutirlo en otro hilo):
Código Delphi [-]
  Function TEnumControlsResultHelper.ghStopWhen (Const ACond :Boolean) :Boolean;
  Begin
    Self := TEnumControlsResult (Ord (ACond) * 2);
    Result := Self = TEnumControlsResult.Stop;
  End;

Cita:

Empezado por jhonny (Mensaje 503748)
[...]le he hecho click al enlace que tienes en la firma, con el animo de ver su magia pero me sale una página en blanco.

Esto es porque suspendí ese enlace hasta que libere la nueva versión de mi biblioteca, la cual fue redefinida por completo. Ahora es 100% orientada a objetos y los únicos elementos globales que declaro son clases e interfaces (incluso a varios ayudantes los declaro en la sección pública de clases de uso general, como TghSys). Ha sido toda una revolución y un crecimiento personal el aplicar mucho de lo nuevo que trae Delphi y erradicando viejos vicios de mi técnica. La GHF de hace tres años me da vergüenza. :o

Espero ya pronto subir la nueva GH Freebrary a algún repositorio, y tener la satisfacción de ofrecérselas y recibir productiva retroalimentación. Quizá esta vez sí la uses, Jhonny. Intentaré que sea digna de ello. :)

AgustinOrtu 27-03-2016 07:04:55

Cita:

Empezado por Al González (Mensaje 503765)
Ha sido toda una revolución y un crecimiento personal el aplicar mucho de lo nuevo que trae Delphi y erradicando viejos vicios de mi técnica. La GHF de hace tres años me da vergüenza. :o

Que coincidencia, hoy justo leyendo este articulo

Punto #9

Cita:

Tus programas nunca serán perfectos ni lo suficientemente buenos. Con el paso de los años te avergonzarás de tu propio código. Esto es bueno, has mejorado.

Al González 27-03-2016 09:02:30

Y es verdad, Agustín. Al escribir esa parte de mi mensaje anterior, me vino a la mente aquella reflexión de Galli. :) ^\||/

Cada cierto tiempo ocurre que ves al que fuiste y lo que hiciste, notando que ambas cosas han mejorado. Algo anda mal en un individuo cuando pasan muchos años sin presentársele este fenómeno de introspección.

Y hablando de progreso, veo que vas llegando a una de las vallas. ;)

Casimiro Notevi 27-03-2016 10:51:59

Eso demuestra que 'más sabe el diablo por viejo que por diablo'.
Los jóvenes no se dan cuenta que no saben. Con el tiempo van descubren que no sabían, que ahora sí que saben.
Más tarde se vuelven a dar cuenta que tampoco sabían, sino que es ahora cuando saben.
Hasta que llegan a 'viejo' y descubren que toda su vida no han sabido nada, que por fin ya saben...

Si a eso le añadimos el ser de caracter perfeccionista, provoca que nunca estemos contento con nuestro código, siempre necesita reformas, minimizarlo, incluso rehacerlo... y nunca se acaba, cada vez más perfecto, más eficiente... y así toda la vida :)

jhonny 27-03-2016 18:39:34

Hombre Al, siempre es tan edificante leer tus post, twits, comentarios... Muchas gracias por tus aportes, siempre se aprende mucho, en cuanto a la GH Freebrary, no es que sea digna o no de usarla, más bien quizá está vez sí esté en la capacidad de aprender más de ella.

En cuanto al comentario de que ahora se sabe más que antes, me viene a la mente ese dicho que dice: "Si el joven supiera y el viejo pudiera..." jejeje, pero la clave de este asunto es saber aplicarla a nosotros mismos, superarnos a nosotros mismos cada vez y no perder mucho el tiempo comparándonos con otros o caeremos en la tentación de olvidar nuestra esencia, nuestra pasión.

Y hasta aquí escribo, que se me va saliendo el "yo" romántico y creo que este no es el lugar :D

AgustinOrtu 31-03-2016 21:31:28

Al, por cierto

Cita:

... del tipo TEnumProcResult, pero en cuanto tenga que usarlo no dudaré en crearle su extensión, es decir, su ayudante, escribiendo un método como ghStopWhen o algún nombre similar (se aceptan sugerencias de otros posibles identificadores).
En el framework Spring4D, la interfaz genérica IEnumerable< T > que es implementada por todas las colecciones, tal y como se puede ver en la Wiki o en el código, expone dos metodos llamados TakeWhile, declarados asi:

Código Delphi [-]
    ///  < summary >
    ///    Returns elements from a sequence as long as a specified condition is
    ///    true.
    ///  < /summary >
    function TakeWhile(const predicate: TPredicate< T >): IEnumerable< T >; overload;

    ///  < summary >
    ///    Returns elements from a sequence as long as a specified condition is
    ///    true. The element's index is used in the logic of the predicate
    ///    function.
    ///  < /summary >
    function TakeWhile(const predicate: TFunc< T, Integer, Boolean >): IEnumerable< T >; overload;

Quiza sea mas idiomático ya que TakeWhile, el While le podria recordar a cualquier desarrollador a la estructura de control y de ese modo dar una pista bastante certera de cual es su funcionalidad, sin necesidad de leer documentacion o implementacion

Luego hay otro curioso metodo que puede resultar util (ya que estamos), y es el SkipWhile, que basicamente es lo contrario del anterior

Código Delphi [-]
    ///  < summary >
    ///    Bypasses elements in a sequence as long as a specified condition is
    ///    true and then returns the remaining elements.
    ///  < /summary >
    function SkipWhile(const predicate: TPredicate< T >): IEnumerable< T >; overload;

    ///  < summary >
    ///    Bypasses elements in a sequence as long as a specified condition is
    ///    true and then returns the remaining elements. The element's index is
    ///    used in the logic of the predicate function.
    ///  < /summary >
    function SkipWhile(const predicate: TFunc< T, Integer, Boolean >): IEnumerable< T >; overload;

Un saludo :)

EDITO:

Por cierto Casimiro, no será posible hacer algo con la sintaxis para los genéricos? Cuando se usan las etiquetas de código:

Cita:

TList<Integer>
Se convierte en:

Código Delphi [-]
  TList

jhonny 31-03-2016 22:12:09

Agustín, de pura curiosidad, ¿La función llamada ElementAt implementada en dicha interfaz es con el fin de filtrar la colección o sería más bien con el propio TakeWhile?

jhonny 31-03-2016 22:21:56

Ahhh caramba, ya me respondí, lo que busco estaría implementado en la función Where, que interesante framework, tendré que estudiarlo está muy interesante. Aunque cosas como esta del Where ya deberían venir por defecto en Delphi, pienso.

AgustinOrtu 31-03-2016 22:42:59

Hola jhonny

El metodo ElementAt es para acceder al elemento mediante un indice

Cita:

/// <summary>
/// Returns the element at a specified index in a sequence.
/// </summary>
/// <param name="index">
/// The zero-based index of the element to retrieve.
/// </param>
/// <returns>
/// The element at the specified position in the source sequence.
/// </returns>
function ElementAt(index: Integer): T;
Podria decirse que es el equivalente a la propiedad indizada Items de las coleciones de la RTL

Código Delphi [-]
uses
  Generics.Collections;

var
  list: TList< Integer >;
begin
  list := TList< Integer >.Create;
  try
    list.AddRange([1, 2, 3]);
    ShowMessage(list.Items[1].ToString); // imrpime 2
  finally
   list.Free;
  end; 
end;

Usando Spring:

Código Delphi [-]
uses
  Spring.Collections;

var
  list: IList< Integer >;
begin
  list := TCollections.CreateList< Integer >;
  list.AddRange([1, 2, 3]);
  ShowMessage(list.Items[1].ToString); // imrpime 2
  ShowMessage(list.ElementAt(1).ToString); // imrpime 2

AgustinOrtu 31-03-2016 22:46:41

Te recomiendo que veas este seminario online, que es bastante basico pero como introduccion a las colecciones de Spring es mas que suficiente: Enumerators, IEnumerable™ and the Spring Framework

Al comienzo habla sobre los Enumeradores nativos de Delphi, pero a partir de 14:35 se habla de Spring4d

Saludos

jhonny 31-03-2016 22:55:32

Cita:

Empezado por AgustinOrtu (Mensaje 503909)
Te recomiendo que veas este seminario online, que es bastante basico pero como introduccion a las colecciones de Spring es mas que suficiente: Enumerators, IEnumerable™ and the Spring Framework

Al comienzo habla sobre los Enumeradores nativos de Delphi, pero a partir de 14:35 se habla de Spring4d

Saludos

¡Genial!, gracias por tu recomendación Agustín. :)

AgustinOrtu 31-03-2016 23:02:47

Una de las cosas mas interesantes es que los enumeradores de Spring tienen ejecucion retardada

Basicamente todos los metodos que retornen IEnumerables (sean subconjuntos de la coleccion inicial, como en el caso del citado Where, o conjutos agregados como por ejemplo el caso del metodo Concat), hasta que no los iteremos por ejemplo en un bucle for-in, nada realmente pasa

Esto es muy poderoso para casos en los que encadenamos operaciones por ejemplo: .Where.Concat.Where.ForEach


Fuente con una explicacion mas detallada y precisa de Stefan Glienke

jhonny 01-04-2016 02:16:05

Cita:

Empezado por AgustinOrtu (Mensaje 503913)
Una de las cosas mas interesantes es que los enumeradores de Spring tienen ejecucion retardada

Basicamente todos los metodos que retornen IEnumerables (sean subconjuntos de la coleccion inicial, como en el caso del citado Where, o conjutos agregados como por ejemplo el caso del metodo Concat), hasta que no los iteremos por ejemplo en un bucle for-in, nada realmente pasa

Esto es muy poderoso para casos en los que encadenamos operaciones por ejemplo: .Where.Concat.Where.ForEach


Fuente con una explicacion mas detallada y precisa de Stefan Glienke

Pues de verdad que es muy... pero muy interesante dicho tema de ejecución retardada. Segun veo Delphi debería tener dicho framework (Spring4D) o sus caracateristicas ya incluidas.


La franja horaria es GMT +2. Ahora son las 11:59:33.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi