PDA

Ver la Versión Completa : problemas la hacer un cast de un TStringGrid(DBGrid)


orfeo
22-05-2003, 06:07:34
Hola, tengo una procediminto <procedure stringGridFill(grid:TStringGrid)> que anda bien para StringGrid este solo hace que las columnas se auto-ajusten dependiendo del largo de los string que muestra.
Quiero usar la misma funcion para una DBgrid, por ejemplo:

DBGrid1.DataSource.DataSet.DisableControls;
TStringGrid(DBGrid1).DefaultRowHeight:=60;
DBGrid1.DataSource.DataSet.EnableControls;

este codigo anda, entonces lo que yo hago es:

DBGrid1.DataSource.DataSet.DisableControls;
stringGridFill(TStringGrid(DBGrid1)) (x)
DBGrid1.DataSource.DataSet.EnableControls;

pero en la linea (x) salta un error de direccionamiento, y no es dentro del procedure, ya que es antes de llegar al begin de stringGridFill, por eso me da a penzar que hay algo mal, en el cast..

que es?

__cadetill
22-05-2003, 10:29:10
que error te da? Porque lo acabo de probar y me funciona sin problemas

andres1569
22-05-2003, 12:25:44
Hola:

Por lo que cuentas, tiene toda la pinta de que el método al que llamas pertenece sólo a la clase TStringGrid y no al ascendiente común TCustomGrid. El compilador te deja pasar la sentencia, pero falla en la ejecución. Con DefaultRowHeight eso no ocurría porque sí es común a ambas clases, auqneu esté protegido para una y public para la otra.

Revisa el código de la VCL, a ver si el TCustomGrid tiene implementado dicho método.

Un saludo

roman
22-05-2003, 17:33:31
Orfeo:

Esto, TStringGrid(DBGrid1).DefaultRowHeight:=60;

*jamás* debe hacerse.

TDBGrid y TStringGrid no están en la misma línea de la jerarquía de clases. En general cuando haces el "casting"

TUnaClase(Objeto)

Objeto debe ser de una clase ancestra de TUnaClase como en


var
WinControl: TWinControl;

begin
TEdit(WinControl){...};
end;


En tu caso la clase TStringGrid no es un ancestro de la clase TDBGrid.

// Saludos

andres1569
23-05-2003, 11:41:20
Hola:

Roman escribió:


Esto, TStringGrid(DBGrid1).DefaultRowHeight:=60;

*jamás* debe hacerse.


Me parece demasiado categórico decir que jamás debe hacerse. Para mí es un truco muy válido cuando se sabe que la propiedad/método al que se accede pertenece a un ancestro común, como es el caso. Nos valemos de una clase que sí tiene dicha propiedad como public y que hace de "wrapper", y en realidad funciona. Se supone que si está implementada en el ancestro común es porque define un comportamiento común y válido para todas sus clases descendientes, aunqeu lo hagamos valer a través de un intermediario.

Quizás una forma más ortodoxa sería heredar un componente de TDBGrid y promover dicha propiedad a la sección public, o en caso de un método virtual, redefinirlo como public, pero es un trabajo extra que podemos evitar.

Por supuesto, este truco está supeditado a conocer de antemano la declaración de las clases que estamos manejando (para eso está el código fuente), pero eso sucede con el uso diario de componentes, su implementación puede variar de una versión a otra, y de hecho sucede, pero esa es una información de la que dispone el programador.

Ya ves, Roman, siempre dispuesto a replicarte. Espero tu contraréplica.

Un slaudo

delphi.com.ar
23-05-2003, 18:00:46
Posteado originalmente por andres1569
Me parece demasiado categórico decir que jamás debe hacerse. Para mí es un truco muy válido cuando se sabe que la propiedad/método al que se accede pertenece a un ancestro común, como es el caso.

Pero Román tiene toda la razón, el problema es que un TStringGrid no es ancestro de TDBGrid, así es la jerarquía:
TDBGrid -> TCustomDBGrid -> TCustomGrid
TStringGrid -> TDrawGrid -> TCustomGrid

roman
23-05-2003, 19:05:57
Andrés, me parece que nos estamos confundiendo. Si una clase ancestra dispone de un método protegido entonces desde luego que es válido hacer el casting usando la clase ancestra. Pero en este caso, como ya lo mostró delphi.com.ar, las clases en cuestión no están en la misma línea jerárquica. Es como hacer que un primo mío se haga pasar por mí. Esto, si bien puede funcionar en algunos casos porque el primo por casualidad tenga un método del mismo nombre, puede llevar a problemas de violación de acceso.

Sí, quizá en algún momento nos ahorre trabajo, pero cuando nos acostumbremos a este tipo de trucos y la aplicación comience a fallar nos costará más trabajo hallar la causa.

// Saludos

andres1569
23-05-2003, 22:18:12
Hola:

Delphi.com.ar escribió:


Pero Román tiene toda la razón, el problema es que un TStringGrid no es ancestro de TDBGrid, así es la jerarquía:
TDBGrid -> TCustomDBGrid -> TCustomGrid
TStringGrid -> TDrawGrid -> TCustomGrid


No he dicho en ningún momento que un TStringGrid sea un ancestro de TDBGrid, sino que ambos tienen un ancestro común, TCustomGrid. El caso es que la propiedad DefaultRowHeight está definida en ese ancestro, pero sólo es accesible desde "fuera" por el programador final que usa un TStringGrid, al ser public en esta clase.

Cuando hacemos el cast TStringGrid(DBGrid1) simplemente engañamos al compilador para que no proteste pero la instrucción/propiedad que se ejecuta es la del ancestro (probadlo si queréis), la del TCustomGrid, no la del TStringGrid (eso si podría ser una fuente de complicaciones). De esa forma accedemos a una propiedad que pertenece plenamente a nuestra clase.

Roman escribió:

Esto, si bien puede funcionar en algunos casos porque el primo por casualidad tenga un método del mismo nombre, puede llevar a problemas de violación de acceso.


En este caso no hay casualidad sino que el método está definido en el TCustomGrid. Eso sí, no creo que nadie deba utilizar este truco confiando en la casualidad, sino cerciorándose de que se accede a un método soportado.

Saludos

delphi.com.ar
23-05-2003, 22:31:09
Posteado originalmente por andres1569
Hola:
No he dicho en ningún momento que un TStringGrid sea un ancestro de TDBGrid, sino que ambos tienen un ancestro común, TCustomGrid. El caso es que la propiedad DefaultRowHeight está definida en ese ancestro, pero sólo es accesible desde "fuera" por el programador final que usa un TStringGrid, al ser public en esta clase.

Ok, tienes razón en que no has dicho eso, pero igualmente sigo de acuerdo con Román, porque al castear este objeto a una clase que no pertenece a su jerarquía, es como si le estuvieras poniendo un molde no apto con su forma.
Para hacer esto es conveniente crear una clase intermedia, heredada de TDBGrid, que ya posea esta propiedad, por ejemplo:

TMyDbGrid = class(TDBGrid)
public
property DefaultRowHeight;
end;

Y si quieres puedes castear a esta clase tu grid sin problemas.

Saludos!

roman
23-05-2003, 23:25:30
Ok Andrés, aquí te va un pequeño relato

El señor Bor Land definió tres clases:


TGrid = class
private
FRowHeight: Integer;

protected
RowHeight: Integer read FRowHeight write FRowHeight;

public
constructor Create; // Establece RowHeight a valor inicial
end;

TDbGrid = class(TGrid)
end;

TStringGrid = class(TGrid)
private
Font: TFont;

public
constructor Create; // crea Font
destructor Destroy; override; // libera Font;

// publica RowHeight (y la redefine)
property RowHeight: Integer read GetHeight write SetHeight;


TStringGrid debía redefinir RowHeight para cambiar el tamaño del font al cambiar la altura del renglón pero sin perder la funcionalidad anterior (no mucha en este caso pero es un cuento)


function TStringGrid.GetRowHeight: Integer;
begin
Result := inherited RowHeight;
end;

procedure TStringGrid.SetRowHeight(Value: Integer);
begin
inherited RowHeight := Value;
Font.Height := Value;
end;


Tiempo después llega un programador que necesita un DBGrid pero desafortunadamente la propiedad RowHeight está protegida. Entonces lee la documentación y se cerciora de que el ancestro TGrid tiene la propiedad y piensa: "debo derivar una clase de TDbGrid para desprotegerla". Pero también nota que otro descendiente de TGrid, TStringGrid, publica la propiedad y piensa: "¡Ah! ¡Más fácil! Mejor hago un "casting".

La documentación nada dice acerca de Font pues es una propiedad privada que no interesa al público en general.

Como DbGrid es una instancia de TDbGrid no posee el campo Font, así que ¿qué sucederá cuando nuestro protagonista escriba:

TStringGrid(DbGrid).RowHeight := 60;

?

¡¡Una violación de acceso!!

Y colorín colorado, este cuento se ha acabado

// Saludos

andres1569
24-05-2003, 13:01:06
Hola foreros:

¡ Me habéis convencido en un 99 %!

El ejemplo de Roman, aunque sea un cuento y no se corresponda con la implementación de DefaultRowHeight en la VCL (Bor Land debe ser un primo de Borland), ilustra que este truco puede ser una fuente potencial de errores al redefinirse el método Set de la propiedad, por mucho que la propiedad sea común a todos. En las pruebas que he hecho, Delphi toma el primer camino conocido, si la propiedad sólo se ha promovido de sección, se ejecuta el código del ancestro y no saltan errores; en cambio, si la propiedad se promueve de sección y además se accede desde un método no común (como el ejemplo de Roman), ya se entra en un terreno sembrado de minas, puesto que se ejecuta el código de la clase que utilizamos para "castear". Si fuera un método virtual, "a secas", y se redefiniera no ocurriría nada malo porque se ejecutaría el método del ancestro (el casting, aunque ilegal según la OOP, tendría un efecto de polimorfismo real). Si fuera un método virtual y abstracto a la vez (lo lógico sería que sí estuviera redefinido en la clase que lo publica), al hacer el casting se ejecutaría igualmente el código del ancestro, saltando un "Abstract error", pero esto ya es afinar demasiado.

Hasta ahora me había parecido un truco válido y fiable, aunque la teoría OOP diga que no se debe moldear salvo a un ascendente, creía que no había peligro al ejecutarse el código ancestral. Sopesando ahora sus pros y sus contras, os doy la razón en que no es un truco recomendable.

Como señala Delphi.Com.Ar, lo mejor es derivar una clase.

Si alguien pregunta, ¿y cuál es el 1% que no te convence? Pues que con TStringGrid(DBGrid1).DefaultRowHeight = 60; logramos el efecto deseado sin errores y sin mucho código, aunque no se atenga a la ortodoxia.

Gracias por vuestras explicaciones.

Saludos

PD: ... y fueron felices y comieron perdices

roman
24-05-2003, 18:43:02
:D :D Voy contra el 1% :D :D

Posteado originalmente por andres1569
Hola foreros:

¡ Me habéis convencido en un 99 %!

¿y cuál es el 1% que no te convence? Pues que con TStringGrid(DBGrid1).DefaultRowHeight = 60; logramos el efecto deseado sin errores y sin mucho código, aunque no se atenga a la ortodoxia.


El villano de la obra, Mr. B. Orlando decide en su versión 8 agregar funcionalidad a TStringGrid y redefine la propiedad DefaultRowHeight...

Epílogo: El lobo se come a caperucita :D

// Saludos

pd: Andrés, esto último es nada más para bromear un poquillo. Siempre es grato contender contigo ya que de los debates en buenos términos siempre se aprenden cosas nuevas.

orfeo
27-05-2003, 05:29:24
En realidad, todos tenian razon con lo del cast, el problema es que en mi codigo en un momento dado, accede a cols[i] y este no esta definido en en algun ancestro de DBgrid, parece que solo se define en stringGrid (aunque no me fije).

Ahora el problema es que nesecito acceder a un DBGrid como si fuese una matriz, osea col[i,j], por eso voy a postear otro mensaje en el foro.

Gracias