Club Delphi  
    FTP   CCD     Buscar   Trucos   Trabajo   Foros

Retroceder   Foros Club Delphi > Otros temas > Trucos
Registrarse FAQ Miembros Calendario Guía de estilo Buscar Temas de Hoy Marcar Foros Como Leídos

Los mejores trucos

Respuesta
 
Herramientas Buscar en Tema Desplegado
  #1  
Antiguo 04-02-2017
Avatar de AgustinOrtu
[AgustinOrtu] AgustinOrtu is offline
Miembro Premium
NULL
 
Registrado: ago 2013
Ubicación: Argentina
Posts: 1.858
Poder: 15
AgustinOrtu Es un diamante en brutoAgustinOrtu Es un diamante en brutoAgustinOrtu Es un diamante en brutoAgustinOrtu Es un diamante en bruto
Ayudante generico para enumerativos

Saludos,

Una buena introduccion a este tema seria leer este otro ¿String a Enumerativo con record helper? ¿Es posible?

La idea general consiste en tener un ayudante para los tipos enumerados. Parece que se llega a una encrucijada porque los record helper no soportan herencia, lo cual nos impide reutilizar el codigo para tal fin. Esto termina en una explosion de helpers los cuales hacen todos exactamente lo mismo, la unica variante es el tipo sobre el cual opera el helper

Tomando varias ideas de la web he escrito un record generico que permite manipular los tipos enumerativos de Delphi de manera sencilla, y gracias al generico, se puede permitir usar cualquier tipo enumerativo. La idea es envolver la RTTI para obtener informacion dinamicamente del tipo enumerado.

Si bien no lo he probado, es necesario Delphi 2010 como minimo que es en donde entra en juego esta nueva RTTI extendida

Sin mas, la interface que se expone consiste en dos tipos fundamentales, el primero y mas importante es Enum<T>

Código PHP:
  /// <summary> Record que contiene metodos estaticos para trabajar con tipos enums </summary>
  
Enum<Trecord> = record
  
public
    
/// <summary> El nombre del tipo enum </summary>
    
class function TypeNamestring; static; inline;
    
/// <summary> El nombre del valor enum </summary>
    
class function ValueName(const ValueT): string; static; inline;
    
/// <summary> Devuelve el valor del tipo enum anotado por el atributo EnumNames </summary>
    /// <summary> Si el enum no esta anotado por el atributo EnumNames, o no esta anotado por un atributo
    /// EnumNames con el identificador indicado, se eleva una excepcion EEnumNameNotFound </summary>
    /// <remarks> Ver EnumNamesAttribute </remarks>
    
class function EnumName(const Identifierstring; const ValueT): string; static; inline;
    
/// <summary> Devuelve el valor del tipo enum anotado por el atributo EnumNames </summary>
    /// <summary> En lugar de elevar una excepcion EEnumNameNotFound, se devuelve el valor Default </summary>
    /// <summary> Si Default = EmptyStr se devuelve ValueName(Value) </summary>
    /// <remarks> Ver EnumNamesAttribute </remarks>
    
class function EnumNameOrDefault(const Identifierstring; const ValueT; const Default: string ''): string; static; inline;
    
/// <summary> Devuelve todos los nombres con los que fue anotado el enum </summary>
    
class function EnumNames(const Identifierstring): TArray<string>; static; inline;
    
/// <summary> Devuelve el valor enum dado un Ordinal </summary>
    
class function Parse(const OrdinalInteger): T; static; inline;
    
/// <summary> Convierte el valor enum a su correspondiente Ordinal </summary>
    
class function ToInteger(const ValueT): Integer; static; inline;
    
/// <summary> El valor maximo del enum. Equivalente a Ord(High(T)) </summary>
    
class function MaxValueInteger; static; inline;
    
/// <summary> El valor maximo del enum. Equivalente a Ord(Low(T)) </summary>
    
class function MinValueInteger; static; inline;
    
/// <summary> Devuelve True si el valor del tipo enum se encuentra dentro del rango permitido </summary>
    
class function InRange(const ValueT): Booleanoverload; static;
    
/// <summary> Devuelve True si el entero se encuentra dentro del rango permitido del tipo enum </summary>
    
class function InRange(const ValueInteger): Booleanoverload; static;
    
/// <summary> Eleva una excepcion EEnumOutOfRange si el valor del tipo enum esta fuera del rango
    // permitido </summary>
    /// <param name="Value"> El valor a testear </param>
    /// <param name="Namespace"> Describe el "contexto" de quien invoca a este metodo (ej clase o unidad) </param>
    /// <param name="MethodName"> Nombre del metodo que invoco a esta rutina </param>
    
class procedure CheckInRange(const ValueT; const Namespace, MethodNamestring); static;
    
/// <summary> Cantidad de elementos del enum </summary>
    
class function CountInteger; static;
    
/// <summary> Devuelve un Array con los elementos del enum </summary>
    
class function AsArrayTArray<T>; static;
  
end
Lamentablemente en Delphi no tenemos como constraint (restriccion) para los genericos algo que limite al generico a solamente aceptar tipos enumerativos. Esto implica que deba usar el contraint record, que se traduce en que se puede pasar como parametro generico cualquier tipo "no nullable". Por ejemplo, enumerativos, integer, etc

Los metodos son bastante sencillos de entender y tienen su pequeño comentario sobre que hacen

Una pequeña muestra de codigo de lo que se puede lograr es la siguiente:

Código PHP:

type
{$SCOPEDENUMS ON}
  
// podemos usar enumerativos con calificacion completa si queremos sin ningun problema
  
TScoped = (FirstSecondThird);

{
$SCOPEDENUMS OFF}

  
TTestEnumeration = (ttFirstttSecondttThird);

procedure Main;
var
  
TestEnumTTestEnumeration;
  
ScopedEnumTScoped;
begin
  
// Imprime los valores "0", "1", "2"
  
for TestEnum in Enum<TTestEnumeration>.AsArray do
  
begin
    Write
('Enum<TTestEnumeration>.ToInteger: ');
    
Writeln(Enum<TTestEnumeration>.ToInteger(TestEnum));
  
end;
  
Writeln;
  
  
// Imprime los valores "First", "Second", "Third"
  
for ScopedEnum in Enum<TScoped>.AsArray do
  
begin
    Write
('Enum<TScoped>.ValueName: ');
    
Writeln(Enum<TScoped>.ValueName(ScopedEnum));
  
end;
  
Writeln

  
// imprime "TTestEnumeration"
  
Writeln(Enum<TTestEnumeration>.TypeName);
  
  
// imprime "TScoped"
  
Write(Enum<TScoped>.TypeName); 
  
// "TScoped" tiene 3 elementos
  
Writeln(' tiene ' Enum<TScoped>.Count.ToString ' elementos');
  
Writeln('El valor maximo es ' Enum<TScoped>.MaxValue.ToString);
  
Writeln('El valor minimo es ' Enum<TScoped>.MinValue.ToString);

  
Write('5 es un ordinal dentro del rango permitido de TScoped? ');
  
// evalua False
  
Writeln(Enum<TScoped>.InRange(5));

  
Write('2 es un ordinal dentro del rango permitido de TScoped? ');
  
// evalua True
  
Writeln(Enum<TScoped>.InRange(2));
end
Todo esto esta muy bien, pero ahora, para responder a la inquietud del hilo inicial, esto al usar RTTI no puede hacer "mucho mas" que devolver el nombre con el que declaramos la enumeracion.

Que pasa si quiero tener distintas representaciones en string del mismo enumerativo? Una forma bastante elegante y que permite el reuso de codigo es el uso de atributos.

Quien nunca haya leido o usado atributos le invito a explorar la documentacion

El segundo tipo importante que exporta la unidad es el atributo EnumNamesAttribute

Código PHP:
  EnumNamesAttribute = class(TCustomAttribute)
  
strict private
    
FIdentifierstring;
    
FNamesTArray<string>;
  public
    
constructor Create(const IdentifierNamesstring; const Delimiterstring ',');
    function 
NameOf<Trecord>(const ValueT): string;
    
property Identifierstring read FIdentifier;
    
property NamesTArray<stringread FNames;
  
end
Los atributos basicamente son informacion extra adicional que se usa para "decorar", o "anotar" un determinado tipo (en realidad los atributos se pueden usar sobre muchas mas cosas, para ver que es posible anotar con atributos, mejor referirse a la documentacion)

Como es informacion que se almacena en el ejecutable, como si fuera "una rtti mas", deben ser valores constantes, que se puedan resolver en tiempo de compilacion.

Una limitante de Delphi es que, aunque realmente un "array of TMiEnumerativo of string" es constante, no lo permite en atributos. Es ese el motivo por el cual el atributo EnumNamesAttribute se implementa usando un string delimitado por algun caracter

Otro pormenor es que si bien el tipo enumerado puede ser anotado con atributos, no sucede lo mismo con los valores del enumerado (el codigo compila, ni da advertencias, pero luego no hay forma de extraer la informacion)

Pasemos a un ejemplo que creo que va a ser mas practico:

Código PHP:
type
{$SCOPEDENUMS ON}

  [
EnumNames('Test''Hello,World')] // utilizamos el atributo
  
TScoped = (FirstSecondThird); // declaracion del tipo, como toda la vida

{$SCOPEDENUMS OFF}

procedure Main;
begin
  
// extraer el valor
  
Writeln(Enum<TScoped>.EnumName('Test'TScoped.First)); // imprime "Hello"
end
Que esta pasando aqui?

Bueno, en realidad cuando anotamos al tipo TScoped con el atributo, en realidad lo que estamos haciendo es "invocar a su constructor". Si se fijan en el constructor, esta definido asi:

Código PHP:
constructor Create(const IdentifierNamesstring; const Delimiterstring ','); 
Delphi nos permite ahorrarnos el sufijo "Attribute" cuando anotamos un tipo, y tambien el llamado al metodo Create. Si se fijan en el popup con los parametros al escribir "EnumNames(" el IDE muestra esto:



Que es justamente el constructor

El parametro "Names" es un string, que deberia ser un string delimitado por un algun caracter (personalizable por el parametro "Delimiter", por defecto la coma). Dicho string se separa usando el delimitador y se almacena en un arreglo. En el caso de que el numero de string sea diferente al del enumerativo, aquellos que queden indefinidos se interpretan como "EmptyStr" y lo que sobre se ignora

Que es esa cosa que llame "identificador". Bueno ya que Delphi permite anotar todas las veces que uno quiera con el mismo atributo a un tipo, me parecio una buena idea que la API permitiera
definir distintas representaciones, pero obviamente era necesario algo que "identifique"

Un ejemplo en accion:

Código PHP:
type
{$SCOPEDENUMS ON}

  [
EnumNames('Test''Hello,World')]
  [
EnumNames('MoreNames''OnlyFirst')]
  
TScoped = (FirstSecondThird);

{
$SCOPEDENUMS OFF}

procedure Main;
var
  
sstring;
begin
  
// imprime "Hello"
  
Writeln(Enum<TScoped>.EnumName('Test'TScoped.First));
//  Writeln(Enum<TScoped>.EnumName('test', TScoped.First)); // excepcion, sensible a mayusculas

  // imprime "OnlyFirst"
  
Writeln(Enum<TScoped>.EnumName('MoreNames'TScoped.First));
  
  
// imprimen string vacio
  
Writeln(Enum<TScoped>.EnumName('MoreNames'TScoped.Second));
  
Writeln(Enum<TScoped>.EnumName('MoreNames'TScoped.Third));

//  Writeln(Enum<TScoped>.EnumName('blabla', TScoped.Third)); // excepcion ya que no fue anotado con "blabla"

  // imprime "Third", es decir, el valor del tipo enumerado tal y como fue declarado
  
Writeln(Enum<TScoped>.EnumNameOrDefault('blabla'TScoped.Third));

  
// imprime "LoQueSea"
  
Writeln(Enum<TScoped>.EnumNameOrDefault('blabla'TScoped.Third'LoQueSea'));

  
Writeln;
  
// arreglo con todos los nombres bajo el identificador "Test"
  // imprime Hello World
  
for s in Enum<TScoped>.EnumNames('Test') do
    
Writeln(s);
end
Pueden descargar la unidad aca
Responder Con Cita
  #2  
Antiguo 04-02-2017
Avatar de Casimiro Notevi
Casimiro Notevi Casimiro Notevi is offline
Moderador
 
Registrado: sep 2004
Ubicación: En algún lugar.
Posts: 32.011
Poder: 10
Casimiro Notevi Tiene un aura espectacularCasimiro Notevi Tiene un aura espectacular
Responder Con Cita
  #3  
Antiguo 06-02-2017
Avatar de Neftali [Germán.Estévez]
Neftali [Germán.Estévez] Neftali [Germán.Estévez] is offline
[becario]
 
Registrado: jul 2004
Ubicación: Barcelona - España
Posts: 18.220
Poder: 10
Neftali [Germán.Estévez] Es un diamante en brutoNeftali [Germán.Estévez] Es un diamante en brutoNeftali [Germán.Estévez] Es un diamante en bruto
Muy interesante Agustín.
__________________
Germán Estévez => Web/Blog
Guía de estilo, Guía alternativa
Utiliza TAG's en tus mensajes.
Contactar con el Clubdelphi

P.D: Más tiempo dedicado a la pregunta=Mejores respuestas.
Responder Con Cita
Respuesta


Herramientas Buscar en Tema
Buscar en Tema:

Búsqueda Avanzada
Desplegado

Normas de Publicación
no Puedes crear nuevos temas
no Puedes responder a temas
no Puedes adjuntar archivos
no Puedes editar tus mensajes

El código vB está habilitado
Las caritas están habilitado
Código [IMG] está habilitado
Código HTML está deshabilitado
Saltar a Foro

Temas Similares
Tema Autor Foro Respuestas Último mensaje
Ayudante de office 97 gianfranco_tont Varios 5 20-02-2012 19:29:13
programar un ayudante al estilo del office 2000 maricarmenNS Varios 9 20-02-2012 16:19:38
Procedimiento Generico para oprimir un boton GerTorresM Varios 4 29-12-2009 16:24:35
procedimiento de asignación genérico gushynet OOP 23 09-09-2008 11:50:15
reporte generico piyugo Impresión 8 07-05-2004 19:20:03


La franja horaria es GMT +2. Ahora son las 06:23:44.


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
Copyright 1996-2007 Club Delphi