Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   OOP (https://www.clubdelphi.com/foros/forumdisplay.php?f=5)
-   -   problemas la hacer un cast de un TStringGrid(DBGrid) (https://www.clubdelphi.com/foros/showthread.php?t=706)

orfeo 22-05-2003 05:07:34

problemas la hacer un cast de un TStringGrid(DBGrid)
 
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 09:29:10

que error te da? Porque lo acabo de probar y me funciona sin problemas

andres1569 22-05-2003 11: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 16: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

Código:

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 10:41:20

Hola:

Roman escribió:

Cita:

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 17:00:46

Cita:

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 18: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 21:18:12

Hola:

Delphi.com.ar escribió:

Cita:

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ó:
Cita:

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 21:31:09

Cita:

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 22:25:30

Ok Andrés, aquí te va un pequeño relato

El señor Bor Land definió tres clases:

Código:

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)

Código:

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 12: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 17:43:02

:D :D Voy contra el 1% :D :D

Cita:

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 04: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


La franja horaria es GMT +2. Ahora son las 00:48:41.

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