PDA

Ver la Versión Completa : ¿Cómo 'casteo' un TCollection?


elarys
13-09-2012, 20:53:33
Tengo mi Objeto es algo asi simplificado


TCodes = class (TPersistent)
protected
FCode: Integer;
FName: String;
FTipo: TATS_List;

published
property Code: Integer read FCode write FCode;
property Name: String read FName write FName;
property Tipo: TATS_List read FTipo write FTipo;
end;



Y en donde TATS_List es una coleccion de objetos
Ahora con el siguiente procedimiento leo las propiedades de dicho objeto
Nambre y valores

Pero cuando llega al TATS_List que es de tipo TCollection, no lee y pasa al siguiente elemento

Puedo castear, sabiendo que TATS_List es de tipo TCollection
y obtener los valores de esa forma???


procedure GetPropertyObject(Obj: TObject; List: TStringList; Counter: Integer);
var
Names, Value, cName, prueba: String;
PInfo: PPropInfo;
PropList: PPropList;
Count, i, j: Integer;
Ob: TObject;
begin
Count := GetPropList(Obj.ClassInfo, tkProperties, nil);
GetMem(PropList, Count * SizeOf(PPropInfo));
GetPropList(Obj.ClassInfo, tkProperties, PropList);

cName := Obj.ClassName;
Delete(cName, 1, 1);
List.Add('<' + cName + '>');

for i := 0 to Count -1 do
begin
Value := GetPropValue(Obj, Proplist[i].Name);
PInfo := GetPropInfo(Obj, Proplist[i].Name);
Names := Proplist[i].Name;

if PInfo <> nil then
begin
if PInfo^.PropType^.Kind = tkClass then
begin
Ob := GetObjectProp(Obj, Proplist[i].Name);

if Ob is TCollection then
begin
//AQUI QUIERO HACER CAST PARA DEVOLVER LOS VALORES QUE TIENE
//LO QUE SE QUE ES DE TIPO TCollection;
//COMO OBTENGO LOS NOMBRES Y VALORES DENTRO DE ESTE
end
else
begin
GetPropertyObject(Ob, List, Counter + 1);
end;

end
else
begin
List.Add('<' + Names + '>' + Value + '</' + Names + '>');
end;
end;
end;
List.Add('</' + cName + '>');
end;


Por el momento esto me devuelve un stringlist y los datos de mi objeto simplificado para no hacerlo largo y que se entienda
Noten que Tipo que seria mi TCollection RTTI o TypInfo no lee los datos, siendo que mi objeto si tiene datos
<Codes>
<Code>1</Code>
<Name>A</Name>
<Tipo></Tipo>
<Code>2</Code>
<Name>B</Name>
<Tipo></Tipo>
<Code>3</Code>
<Name>C</Name>
<Tipo></Tipo>
</Codes>

Mi idea es castear el TCollection para ver si de ahi puedo leer los datos, pero si hay alguna otra manera bienvenida sea
La clase TATS_List funciona correctamente, y me carga los datos a mi objeto...
Luego le paso al procedure mi objeto en tiempo de ejecucion y que haga el trabajo, pero nada

Edito porque hice nuevas pruebas
NOTA: probe pasando todo mi codigo TCollection con un TStringList, con un TList, TStrings, etc, etc, debe ser porque ninguno de estos tienen propiedad published, se les ocurre algun otro contenedor de objetos, o similares como stringlist
Parece que RTTI no lee propiedades publicas.

Acabo de hacer otra prueba, agregando un nuevo valor simulando que es mi collection pero ahora de un solo campo de tipo Array of String
Al ser public Items mi procedimiento no lo puede leer
Probe pasarlo a published, pero delphi me da error Items no puede ser published por ser un array
Estoy trabajando con delphi 2007 y por ahora no podemos mudarnos si tienen soluciones con delphi mas nuevos


TCodes = class (TPersistent)
protected
FCode: Integer;
FName: String;
FTipo: TATS_List;

FItems: Array of String;

procedure SetItems(Index: Integer; Value: String);
function GetItems(Index: Integer): String;

public
function Add(Str: String): Integer;
property Items[Index: Integer]: String read GetItems write SetItems;

published
property Code: Integer read FCode write FCode;
property Name: String read FName write FName;
property Tipo: TATS_List read FTipo write FTipo;
end;

implementation

function TCodes.Add(Str: String): Integer;
begin
SetLength(FItems, Length(FItems) + 1);
FItems[Length(FItems) - 1] := Str;
Result := Length(FItems) - 1;
end;

procedure TCodes.SetItems(Index: Integer; Value: String);
begin
FItems[Index] := Value;
end;

function TCodes.GetItems(Index: Integer): String;
begin
Result := FItems[Index];
end;

movorack
13-09-2012, 22:16:39
Hola elarys

Lo que puedo entender es que quieres generar un XML a partir de una clase que haz creado.

De casualidad ahorita estoy trabajando en un proyecto donde debo llevar a XML valores de un componente que contiene una o varias TCollection y en algunos casos TCollection dentro de TCollection.

Probé tu solución y si sucede que al llegar a la TCollection no encuentra ninguna información. Leí un poco antes de contestarte y al parecer delphi no sabe exactamente como leer tu TElarysCollection (Me imagino que habrás creado un calse heredada de TCollection) y por eso la salta sin ofrecer ningun resultado. (Si entendí mal agradecería que alguien pueda aclararnos porque no lee el cntenido de la TCollection)

Esto es lo que obtuve al ejecutar tu funcion


<KplGenPlan>
<CanEjecuciones>0</CanEjecuciones>
<CodDefi>KNMPLDMO</CodDefi>
<CodPlan>KNMPLDMO_PL001</CodPlan>
<CodProg>KNmPldmo</CodProg>
<KplDbConnection>
<KplDbBdeParams>
<Database>
<AliasName>Kactus7</AliasName>
<Connected>True</Connected>
<DatabaseName>DBKactus</DatabaseName>
<DriverName></DriverName>
<Exclusive>False</Exclusive>
<HandleShared>False</HandleShared>
<KeepConnection>True</KeepConnection>
<LoginPrompt>True</LoginPrompt>
<Name>DtbKactus</Name>
<StringList>
</StringList>
<ReadOnly>False</ReadOnly>
<SessionName>Default</SessionName>
<Tag>0</Tag>
<TransIsolation>tiReadCommitted</TransIsolation>
</Database>
<DatabaseName>DBKactus</DatabaseName>
<Driver>KDBDrvOracle</Driver>
<Password>digital</Password>
<ShowLoginForm>True</ShowLoginForm>
<Username>Kactus</Username>
</KplDbBdeParams>
<Connected>True</Connected>
<Name>KplDbConn</Name>
<Tag>0</Tag>
<TipoConnecion>KDtcBDE</TipoConnecion>
</KplDbConnection>
<Estado>kplPlanEstInac</Estado>
<FinalizacionValor>kplFinProgNoValid</FinalizacionValor>
<FormString>FrmNmPldmo</FormString>
<Name>KplGenPlan</Name>
<NombrePlan></NombrePlan>
<KplNotificacion>
<Activo>True</Activo>
<Email>sdasd</Email>
<StringList>
</StringList>
<Titulo>sadasdasd</Titulo>
</KplNotificacion>
<Observacion></Observacion>
<KplProgramacion>
<FechaEjecucion>41182,8333333333</FechaEjecucion>
<FechaFinal>41143,6895889468</FechaFinal>
<FechaInicio>41144</FechaInicio>
<FrecuenciaDiaNum>0</FrecuenciaDiaNum>
<FrecuenciaDiaVar>kplDiaVarUltDiaMes</FrecuenciaDiaVar>
<FrecuenciaTipo>kplFrecTipMensu</FrecuenciaTipo>
<Hora>0,833333333333333</Hora>
<TieneVencimiento>False</TieneVencimiento>
</KplProgramacion>
<Tag>0</Tag>
<VersionProg>12.6.0.5</VersionProg>
<StringList>
</StringList>
</KplGenPlan>


Y esto es lo que obtengo al ejecutar la función para obtener el XML de mi componente.


<?xml version="1.0" encoding="utf-8"?>
<KPLPLAN>
<CODPLAN>KNMPLDMO_PL001</CODPLAN>
<CODDEFI>KNMPLDMO</CODDEFI>
<CODPROG>KNmPldmo</CODPROG>
<VERPROG>12.6.0.5</VERPROG>
<MAINFORM>FrmNmPldmo</MAINFORM>
<OBSERVACION/>
<NOTIFICACION>
<NOTIFUSR>sdasd</NOTIFUSR>
<NOTIFTIT>sadasdasd</NOTIFTIT>
<NOTIFMSG>zczxcz xzczczxc zxczxczxczx

</NOTIFMSG>
</NOTIFICACION>
<PROGRAMACION>
<FECHAS>
<FECINIC>23/08/2012</FECINIC>
<FECFINA>%NOVENC</FECFINA>
</FECHAS>
<HORA>08:00:00 p.m.</HORA>
<FRECUENCIA>
<FRECTIP>%MENSU</FRECTIP>
<FRECDIA>%ULTDIAMES</FRECDIA>
</FRECUENCIA>
</PROGRAMACION>
<EJECUCION>
<PARAMETROS>
<PARAMETRO PARTYPE="%CTRL" PARORDN="1" LABEL="Archivo" OWNER="FrmNmPldmo" OBJREF="EdtFileName" OBJTIP="TEdit">
<OPCIONES>
<OPCION LABEL="Texto" PROPTIP="" PROPTIP="Text">C:\Garh\NM\Wrk\KNmPldmo.txt</OPCION>
</OPCIONES>
</PARAMETRO>
<PARAMETRO PARTYPE="%CONS" PARORDN="2" LABEL="Consulta" OWNER="DtmNmPldmo" OBJREF="Qry" OBJTIP="TQuery">
<OPCIONES>
<OPCION LABEL="SQL" PROPTIP="" PROPTIP="SQL.Text">SELECT * FROM TABLA</OPCION>
</OPCIONES>
</PARAMETRO>
<PARAMETRO PARTYPE="%ACCN" PARORDN="3" LABEL="Aceptar" OWNER="FrmNmPldmo" OBJREF="BitBtnAceptaClick" OBJTIP="TNotifyEvent"/>
</PARAMETROS>
</EJECUCION>
</KPLPLAN>


En mi XML tengo un nodo que se llama PARAMETROS y es aquí donde llevo el contenido de las TCollection (Tres. Una para controles, una para acciones y otra para consultas) que en el resultado obtenido con tu función no lo encuentro.

En conclusión porque he dado mucha lora. Sea que quieras generar un XML o no. Lo que te recomiendo es que analices desde dentro de la clase y generes desde allí mismo el resultado que quieres. con una función no tan automatizada y que en determinado momento pueda evaluar tipos de datos internos, variables privadas, etc...

Pos si te es de utilidad, hice las pruebas con Delphi 5 :cool:

elarys
13-09-2012, 22:38:20
Justamente es lo que tengo que hacer, pero de una Clase X, y generar dicho xml, no conozco que datos pueda tener ese objeto, puede venir el objeto, TPersona, despues el objeto TCualquierCosa, y luego TClientes

Podes darme la idea de como lograste tu xml ???
Tengo este pedazo de codigo pero como leo los datos del objeto para generar el xml...
Vuelvo a repetir que tengo que hacer un creador de xml para objeto X,Y o Z


var
xmlDocs: TXMLDocument;
xmlNodo: IXMLNode;
begin
xmlDocs := TXMLDocument.Create(Application);

xmlDocs.Active := True;
xmlDocs.XML.Clear;

xmlDocs.Version := '1.0';
xmlDocs.Encoding := 'UTF-8';

xmlNodo := xmlDocs.AddChild('Encabezado');
xmlNodo.Attributes['Atributo'] := 'Dato';
xmlDocs.DocumentElement := xmlNodo;

xmlNodo := xmlNodo.AddChild('Nodo_Hijo');
xmlNodo.Attributes['Atributo_Nodo_Hijo'] := 'Valor';
xmlNodo.Text := 'Test';

xmlDocs.SaveToFile(ExtractFilePath(Application.ExeName)+ 'prueba.xml');

xmlDocs.XML.Clear;
xmlDocs.Active := False;

movorack
13-09-2012, 22:47:46
El XML que genero lo hago desde dentro del mismo objeto.

Lo hago para poder validar muchas cosas que de seguro desde fuera me seria mas complicado.

La verdad no sabria como hacerlo de manera general.

elarys
14-09-2012, 14:54:53
la idea es hacer algo general, alguien me puede dar la linea si esto sera posible hacerlo y por donde encararlo...

elarys
14-09-2012, 17:11:39
Me contesto solo por si alguno le interesa, asi quedo mi procedimiento
GetPropList, no lee los elementos de clases, entonces tuve que hacerlo con cast o casteo, no se en realidad como se dice...
No sabia castear, pero cuando lei un poco por aca y otro poco en google, fue mas facil de lo que parecia


procedure GetPropertyObject(Obj: TObject; List: TStringList; Counter: Integer);
var
Names, Value, cName, iName: String;
PInfo: PPropInfo;
PropList: PPropList;
Count, i, j: Integer;
Ob: TObject;
begin
Count := GetPropList(Obj.ClassInfo, tkProperties, nil);
GetMem(PropList, Count * SizeOf(PPropInfo));
GetPropList(Obj.ClassInfo, tkProperties, PropList);

cName := Obj.ClassName;
Delete(cName, 1, 1); //ACA LE QUITO LA T
List.Add('<'+ cName +'>');

for i := 0 to Count -1 do
begin
Value := GetPropValue(Obj, Proplist[i].Name);
PInfo := GetPropInfo(Obj, Proplist[i].Name);
Names := Proplist[i].Name;

if PInfo <> nil then
begin
if PInfo^.PropType^.Kind = tkClass then
begin
Ob := GetObjectProp(Obj, Proplist[i].Name);

if Ob is TCollection then
begin
List.Add('<'+ Names +'>');
for j := 0 to (Ob as TATS_List).Count - 1 do
begin
iName := (Ob as TATS_List).Items[j].Name;
Value := (Ob as TATS_List).Items[j].Value;
List.Add('<' + iName + '>'+ Value +'</'+ iName +'>');
end;
List.Add('</'+ Names +'>');
end
else
begin
//NO ES DE TIPO COLECCION, PERO PUEDE SER UNA CLASE DE UN SOLO INDICE
//OSEA UNA CLASE DENTRO DE MI CLASE PRINCIPAL... LLAMO RECURSIVAMENTE
GetPropertyObject(Ob, List, Counter + 1);
end;
end
else
begin
List.Add('<'+ Names +'>'+ Value +'</'+ Names +'>');
end;
end;
end;
List.Add('</'+ cName +'>');
end;


Por las dudas aqui mi unidad Colecciones

unit ATS_Collections;

interface

uses
SysUtils, Classes, Variants;

type
TATS_Item = class(TCollectionItem)
private
FData: Variant;
FName: String;
FNull: Boolean;
FBound: Boolean;
FNativeStr: String;
function GetAsVariant: Variant;
procedure SetAsVariant(const Value: Variant);
function GetAsBoolean: Boolean;
function GetAsDateTime: TDateTime;
function GetAsFloat: Double;
function GetAsInteger: LongInt;
function GetAsString: String;
function GetIsNull: Boolean;
function IsEqual(Value: TATS_Item): Boolean;
procedure SetAsBoolean(const Value: Boolean);
procedure SetAsDate(const Value: TDateTime);
procedure SetAsDateTime(const Value: TDateTime);
procedure SetAsFloat(const Value: Double);
procedure SetAsInteger(const Value: LongInt);
procedure SetAsString(const Value: String);
procedure SetAsTime(const Value: TDateTime);
procedure SetAsWord(const Value: LongInt);
procedure SetText(const Value: String);

protected
function GetDisplayName: String; override;

public
constructor Create(Collection: TCollection); overload; override;
procedure Clear;
property AsBoolean: Boolean read GetAsBoolean write SetAsBoolean;
property AsDate: TDateTime read GetAsDateTime write SetAsDate;
property AsDateTime: TDateTime read GetAsDateTime write SetAsDateTime;
property AsFloat: Double read GetAsFloat write SetAsFloat;
property AsInteger: LongInt read GetAsInteger write SetAsInteger;
property AsString: String read GetAsString write SetAsString;
property AsTime: TDateTime read GetAsDateTime write SetAsTime;
property AsWord: LongInt read GetAsInteger write SetAsWord;
property Bound: Boolean read FBound write FBound;
property IsNull: Boolean read GetIsNull;
property NativeStr: String read FNativeStr write FNativeStr;
property Text: String read GetAsString write SetText;

published
property Name: String read FName write FName;
property Value: Variant read GetAsVariant write SetAsVariant;
end;


TATS_List = class(TCollection)
private
function GetItem(Index: Integer): TATS_Item;
procedure SetItem(Index: Integer; const Value: TATS_Item);

public
procedure AddPameter(AName: String; AValue: Variant);
procedure AssignValues(Value: TATS_List);
function IsEqual(Value: TATS_List): Boolean;
function ParamByName(const Value: String): TATS_Item;
function FindParam(const Value: String): TATS_Item;

property Items[Index: Integer]: TATS_Item read GetItem write SetItem; default;
end;

implementation

{ TATS_Item }

constructor TATS_Item.Create(Collection: TCollection);
begin
inherited;
FNull := True;
end;

procedure TATS_Item.Clear;
begin
FNull := True;
FData := Unassigned;
end;

function TATS_Item.GetAsBoolean: Boolean;
begin
if IsNull then
Result := False
else
Result := FData;
end;

function TATS_Item.GetAsDateTime: TDateTime;
begin
if IsNull then
Result := 0
else
Result := VarToDateTime(FData);
end;

function TATS_Item.GetAsFloat: Double;
begin
if IsNull then
Result := 0
else
Result := FData;
end;

function TATS_Item.GetAsInteger: LongInt;
begin
if IsNull then
Result := 0
else
Result := FData;
end;

function TATS_Item.GetAsString: String;
begin
if IsNull then
Result := ''
else
Result := FData;
end;

function TATS_Item.GetAsVariant: Variant;
begin
Result := FData;
end;

function TATS_Item.GetIsNull: Boolean;
begin
Result := FNull or VarIsNull(FData) or VarIsClear(FData);
end;

procedure TATS_Item.SetAsBoolean(const Value: Boolean);
begin
Self.Value := Value;
end;

procedure TATS_Item.SetAsDate(const Value: TDateTime);
begin
Self.Value := Value;
end;

procedure TATS_Item.SetAsDateTime(const Value: TDateTime);
begin
Self.Value := Value;
end;

procedure TATS_Item.SetAsFloat(const Value: Double);
begin
Self.Value := Value;
end;

procedure TATS_Item.SetAsInteger(const Value: LongInt);
begin
Self.Value := Value;
end;

procedure TATS_Item.SetAsString(const Value: String);
begin
Self.Value := Value;
end;

procedure TATS_Item.SetAsTime(const Value: TDateTime);
begin
Self.Value := Value;
end;

procedure TATS_Item.SetAsVariant(const Value: Variant);
begin
FNull := VarIsClear(Value) or VarIsNull(Value);
FData := Value;
end;

procedure TATS_Item.SetAsWord(const Value: LongInt);
begin
Self.Value := Value;
end;

procedure TATS_Item.SetText(const Value: String);
begin
Self.Value := Value;
end;

function TATS_Item.GetDisplayName: String;
begin
if FName = '' then
Result := inherited GetDisplayName
else
Result := FName;
end;

function TATS_Item.IsEqual(Value: TATS_Item): Boolean;
begin
Result := (VarType(FData) = VarType(Value.FData)) and
(VarIsClear(FData) or (FData = Value.FData)) and
(Name = Value.Name) and (IsNull = Value.IsNull);
end;

{ TATS_List}

procedure TATS_List.AddPameter(AName: String; AValue: Variant);
var
P: TATS_Item;
begin
P := TATS_Item(Self.Add);
P.Name := AName;
P.Value := AValue;
end;

procedure TATS_List.AssignValues(Value: TATS_List);
var
I: Integer;
P: TATS_Item;
begin
for I := 0 to Value.Count - 1 do
begin
P := FindParam(Value[I].Name);
if P <> nil then
P.Assign(Value[I]);
end;
end;

function TATS_List.FindParam(const Value: String): TATS_Item;
var
I: Integer;
begin
for I := 0 to Count - 1 do
begin
Result := TATS_Item(inherited Items[I]);
if AnsiCompareText(Result.Name, Value) = 0 then Exit;
end;
Result := nil;
end;

function TATS_List.GetItem(Index: Integer): TATS_Item;
begin
Result := TATS_Item(inherited Items[Index]);
end;

function TATS_List.IsEqual(Value: TATS_List): Boolean;
var
I: Integer;
begin
Result := Count = Value.Count;
if Result then
for I := 0 to Count - 1 do
begin
Result := Items[I].IsEqual(Value.Items[I]);
if not Result then Break;
end
end;

function TATS_List.ParamByName(const Value: String): TATS_Item;
begin
Result := FindParam(Value);
if Result = nil then
raise Exception.Create('El parámetro no fue localizado');
end;

procedure TATS_List.SetItem(Index: Integer; const Value: TATS_Item);
begin
inherited SetItem(Index, TCollectionItem(Value));
end;

end.

movorack
14-09-2012, 17:15:00
Me alegro que hallas solucionado tu problema. Pero desde dentro o desde fuera debiste tener el "conocimiento" de que propiedades tiene la colección por dentro.