Club Delphi  
    FTP   CCD     Buscar   Trucos   Trabajo   Foros

Retroceder   Foros Club Delphi > Principal > OOP
Registrarse FAQ Miembros Calendario Guía de estilo Temas de Hoy

Grupo de Teaming del ClubDelphi

Respuesta
 
Herramientas Buscar en Tema Desplegado
  #21  
Antiguo 15-09-2005
Avatar de roman
roman roman is offline
Moderador
 
Registrado: may 2003
Ubicación: Ciudad de México
Posts: 20.269
Poder: 10
roman Es un diamante en brutoroman Es un diamante en brutoroman Es un diamante en bruto
Hola,

Antes de decirles de dónde ha salido el código de TDoublePanel déjenme explicarles según como ahora entiendo lo que pasa.

Partamos de la idea original de CAOS, en la que simplemente crea los dos subpáneles (TopPanel y ClientPanel) en el constructor de la componente y vayamos viendo qué sucede.

Cuando recién insertamos la componente los subpáneles se muestran correctamente y podemos insertarles controles.

El problema, como ya hizo notar jmariano, es que el IDE, al momento de escribir el dfm, no se entera de la existencia de los controles insertados porque no están en ningún control que reconozca. De hecho el IDE ni squiera sabe que TopPanel y ClientPanel está ahí, como puede verse en el dfm que crea:

Código:
object Form1: TForm1
  Left = 192
  Top = 161
  Width = 870
  Height = 500
  Caption = 'Form1'

  ...

  object DoublePanel1: TDoublePanel
    Left = 16
    Top = 16
    Width = 377
    Height = 249
    BevelOuter = bvLowered
    Caption = 'DoublePanel1'
    TabOrder = 0
  end
end
La pregunta que cabe aquí, es ¿por qué el IDE sí sabe como guardar tales controles cuando TopPanel y ClientPanel se insertan manualmente durante el diseño?

La primera respuesta es que el IDE simplemente se fija si una componente es un descendiente de TWinControl (el primero en la jerarquía capaz de almacenar controles) y guarda todos los controles que estén contenidos en
él.

Pero resulta que esto es incorrecto. Lo cierto es que cada componente (TWinControl o no) debe indicarle al IDE cuáles controles son sus 'hijos'. Pero para el IDE 'hijo' es un concepto independiente de la relación TControl.Parent y TWinControl.Controls.

Para indicar al IDE cuáles componentes debe tratar como hijos se usa el método GetChildren. TWinControl define implemente GetChildren así:

Código Delphi [-]
procedure TWinControl.GetChildren(Proc: TGetChildProc; Root: TComponent);
var
  I: Integer;
  Control: TControl;

begin
  for I := 0 to ControlCount - 1 do
  begin
    Control := Controls[i];
    if Control.Owner = Root then Proc(Control);
  end;
end;

Es decir, simplemente le dice al IDE con Proc(Control) que considere como hijos a los controles de su lista Controls.

Entonces, en nuestra componente redefinimos GetChildren para decirle al IDE
que considere hijos directos a los controles que hayan sido insertados en los subpáneles.

Con ello el IDE genera un dfm así:

Código:
object Form1: TForm1
  Left = 192
  Top = 161
  Width = 870
  Height = 500
  Caption = 'Form1'

  ...

  object DoublePanel1: TDoublePanel
    Left = 8
    Top = 8
    Width = 377
    Height = 249
    BevelOuter = bvLowered
    Caption = 'DoublePanel1'
    TabOrder = 0
    object Edit1: TEdit
      Left = 8
      Top = 8
      Width = 121
      Height = 21
      TabOrder = 0
      Text = 'Edit1'
    end
    object Button1: TButton
      Left = 288
      Top = 8
      Width = 75
      Height = 25
      Caption = 'Button1'
      TabOrder = 1
    end
    object Memo1: TMemo
      Left = 8
      Top = 8
      Width = 185
      Height = 89
      Lines.Strings = (
        'Memo1')
      TabOrder = 0
    end
  end
end
Donde Edit1 y Buton1 los he insertado en TopPanel y Memo1 en ClientPanel.

Al indicarle al IDE que los controles insertados en cada suppánel son hijos de DoublePanel, estos controles quedan escritos como si los hubiéramos insertado directamente en él.

Hasta aquí todo va bien en tanto que los controles insertados se han guardado en el dfm.

Pero claro, el problema está en que al regresar a ver el formulario el IDE hace exactamente lo que se supone que debe hacer: construye un formulario con un hijo directo (DoublePanel) que a su vez tiene como hijos directos los controles insertados, es decir, estos últimos ya no viven dentro de los subpáneles.

Los subpáneles existen porque se crean en el constructor de la componente y los controles insertados existen porque se leen del dfm. Es sólo que no están donde deben estar.

Entonces, lo que debemos hacer es reinsertar los controles en sus respectivos contenedores (los subpáneles). No debemos crearlos otra vez porque ya el IDE lo hizo, sólo debemos cambiarles su propiedad Parent.

¿Cuándo se hace esto?

Pues cuando ya se hayan leído completamente desde el dfm, es decir, en el método Loaded.

Pero, ¿cómo sabemos cuál control va dentro de cuál subpánel?

Recordemos que ahora todos se ven como hijos directos de nuestra componente así que no sabemos en cuál meterlos. De hecho, si no les digo donde yo había puesto Edit1, Button1 y Memo1, ustedes no lo sabrían.

Aquí es donde DefineProperties es crucial.

DefineProperties es un método que nos sirve para guardar valores cualesquiera en el dfm. El método usa Filer.DefineProperty para indicar cuál método se usa para escribir los valores al dfm y cuál se usa para leer de vuelta los valores.

El primer parámetro de DefineProperty es el nombre del identificador de la 'propiedad' que deseamos guardar.

Mi error fue nombrarlos igual que los subpáneles y por ello mismo no entendía ayer del todo lo que pasaba pues pensaba que se refería a ellos.

Cambien esos nombres por, por ejemplo, 'TopPanelControlNames' y 'ClientPanelControlNames' para dejar más en claro lo que realmente estamos guardando en el dfm: los nombres de los controles insertados.

Al hacer esto, el dfm queda así:

Código:
object Form1: TForm1
  Left = 192
  Top = 161
  Width = 870
  Height = 500
  Caption = 'Form1'

  ...
  
  object DoublePanel1: TDoublePanel
    Left = 8
    Top = 8
    Width = 377
    Height = 249
    BevelOuter = bvLowered
    Caption = 'DoublePanel1'
    TabOrder = 0
    TopPanelControlList = (
      Edit1
      Button1)
    ClientPanelControlList = (
      Memo1)
    object Edit1: TEdit
      Left = 8
      Top = 8
      Width = 121
      Height = 21
      TabOrder = 0
      Text = 'Edit1'
    end
    object Button1: TButton
      Left = 288
      Top = 8
      Width = 75
      Height = 25
      Caption = 'Button1'
      TabOrder = 1
    end
    object Memo1: TMemo
      Left = 8
      Top = 8
      Width = 185
      Height = 89
      Lines.Strings = (
        'Memo1')
      TabOrder = 0
    end
  end
end
Es decir, en el dfm ya tenemos una lista de los nombres de las componentes que van en cada control. Y estas listas son las que usamos en el método Loaded para reasignar la propiedad Parent.

Cada subpánel recorre su lista, busca la componente que tenga el nombre en turno en el formulario y le reasigna su padre.

----------------------------

Tod está ha salido de Ray Konopka.

Busquen el artículo Compound Components del volúmen 8 para ver la componente original que él hizo.

// Saludos
Responder Con Cita
  #22  
Antiguo 15-09-2005
Avatar de roman
roman roman is offline
Moderador
 
Registrado: may 2003
Ubicación: Ciudad de México
Posts: 20.269
Poder: 10
roman Es un diamante en brutoroman Es un diamante en brutoroman Es un diamante en bruto
Cierto, lo que comentan presenta unos problemas. No sé en estos momentos si a la componente original de Konopka le pasa lo mismo.

Temo que para esto se necesitaría algo más fuerte, como implementar un DesignNotifier para el formulario a fin de ver cuándo el diseñador mueve la componente.

Ya veremos...

// Saludos
Responder Con Cita
  #23  
Antiguo 15-09-2005
Avatar de jmariano
jmariano jmariano is offline
Miembro
 
Registrado: jul 2005
Posts: 376
Poder: 19
jmariano Va por buen camino
Gracias por la info roman! (y sobretodo por la dirección con los artículos, se ven muy interesantes!)

Ya conocía las funciones para trabajar con el Stream porque una vez me surgió la necesidad, y hay un problema que nunca conseguí solucionar y que os comento a continuación (a ver si hay suerte!):

Comprobareis que el objeto para leer o guardar datos en el Stream (TReader y TWriter) permiten trabajar con muchos tipos de datos: Booleanos, Colecciones, Enteros, Reales, Componentes, etc., y precisamente con el método para almacenar un componente es con quien siempre tuve problemas.

Si intentais almacenar un componente (como, por ejemplo, uno de los panels del que hablábamos) vereis que al ir a la vista del formulario en modo texto (para ver el .dfm) el IDE mostrará el mensaje de error "Stream error".

La única forma que conseguí solucionarlo es definiendo la propiedad como binaria (DefineBinaryProperty), y eso que el ejemplo de la ayuda de Delphi, sobre el almacenamiento de un componente creado en tiempo de ejecución, utiliza una propiedad normal, pero a mi me falla. (Por cierto, al definir una propiedad como binaria, ésta, se almacena como una secuencia de números hexadecimales que contendrían la información sobre las propiedades del componente que se quiere almacenar).

En fins, a ver si alguien descubre porqué falla...

Saludos!

Última edición por jmariano fecha: 15-09-2005 a las 19:31:01.
Responder Con Cita
  #24  
Antiguo 15-09-2005
Avatar de dec
dec dec is offline
Moderador
 
Registrado: dic 2004
Ubicación: Alcobendas, Madrid, España
Posts: 13.109
Poder: 34
dec Tiene un aura espectaculardec Tiene un aura espectacular
Hola,

Muy buena explicación roman. Te felicito. Oyes, ¿de dónde sacaste "DesignNotifier" que lo busco en la ayuda de Delphi, en todos los archivos "*.pas" que tengo a mano en Delphi e incluso en Google y no me aparece? Ah, si aquí partimos de que hay quién tiene información privilegiada y quién no la tiene... yo me apeo. Por cierto jmariano, a nuevas preguntas, nuevos Hilos, que seguro ayudará a que obtengas la ayuda necesaria.
__________________
David Esperalta
www.decsoftutils.com
Responder Con Cita
  #25  
Antiguo 15-09-2005
Avatar de roman
roman roman is offline
Moderador
 
Registrado: may 2003
Ubicación: Ciudad de México
Posts: 20.269
Poder: 10
roman Es un diamante en brutoroman Es un diamante en brutoroman Es un diamante en bruto
Cita:
Empezado por dec
¿de dónde sacaste "DesignNotifier"
De mi imaginación

En realidad es IDesignNotification y está en la unidad DesignIntf.

// Saludos
Responder Con Cita
  #26  
Antiguo 15-09-2005
Avatar de jmariano
jmariano jmariano is offline
Miembro
 
Registrado: jul 2005
Posts: 376
Poder: 19
jmariano Va por buen camino
Cita:
Empezado por dec
Por cierto jmariano, a nuevas preguntas, nuevos Hilos, que seguro ayudará a que obtengas la ayuda necesaria.
Pues sip, tienes razón (después lo pensé). Aunque hice la "consulta" aquí porque tenía relación con los métodos comentados por roman (de todas formas, la consulta era mas bien a modo de curiosidad para que vierais como uno de los métodos fallaba y la alternativa que encontré)

Saludos!
Responder Con Cita
  #27  
Antiguo 15-09-2005
Avatar de roman
roman roman is offline
Moderador
 
Registrado: may 2003
Ubicación: Ciudad de México
Posts: 20.269
Poder: 10
roman Es un diamante en brutoroman Es un diamante en brutoroman Es un diamante en bruto
Cita:
Empezado por dec
Yo topé con un problema y es que cuando pones un componente (creo que solo ocurre con los derivados de "TWinControl") en cualquiera de los paneles este se "esconde" si se redimensiona o mueve el componente "padre" de los paneles.
No me ha pasado lo mismo al redimensionar aunque si al moverlo o cambiar alguna propiedad como BevelOuter o BorderStyle.

BevelOuter provoca un Invalidate del panel contenedor mientras que BorderStyle genera un RecreateWnd.

Aparentemente el problema se soluciona, en los tres casos, redefiniendo Invalidate en el DoublePanel para llamar a TopPanel.Refresh y ClientPanel.Refresh.

Queda pendiente los de componentes con nombres en blanco.

// Saludos
Responder Con Cita
  #28  
Antiguo 15-09-2005
Avatar de dec
dec dec is offline
Moderador
 
Registrado: dic 2004
Ubicación: Alcobendas, Madrid, España
Posts: 13.109
Poder: 34
dec Tiene un aura espectaculardec Tiene un aura espectacular
Hola,

Cita:
Empezado por roman
No me ha pasado lo mismo al redimensionar aunque si al moverlo o cambiar alguna propiedad como BevelOuter o BorderStyle.
Llevas razón. Ocurre no al redimensionar el componente, pero al moverlo: no probé si también ocurre cambiando alguna que otra propiedad.

Cita:
Empezado por roman
BevelOuter provoca un Invalidate del panel contenedor mientras que BorderStyle genera un RecreateWnd.

Aparentemente el problema se soluciona, en los tres casos, redefiniendo Invalidate en el DoublePanel para llamar a TopPanel.Refresh y ClientPanel.Refresh.
No parece roman; al menos yo me encuentro con una bonita "violación de acceso". Iba a probar con "IDesignNotification", aunque me daba miedo solamente verlo, pero, no me pareció que fuera a funcionar... quizás pensé así por el nombre de los métodos... algo así como "InAddComponet", "InOutComponent", pero no "InResizeComponent", por ejemplo.

Cita:
Empezado por roman
Queda pendiente los de componentes con nombres en blanco.
Aquí me perdí. Tendré que comenzar de nuevo, porque ese es otro problema para mí, vamos, que acabo de conocerlo
__________________
David Esperalta
www.decsoftutils.com
Responder Con Cita
  #29  
Antiguo 16-09-2005
Avatar de jmariano
jmariano jmariano is offline
Miembro
 
Registrado: jul 2005
Posts: 376
Poder: 19
jmariano Va por buen camino
Cita:
Empezado por roman
Queda pendiente los de componentes con nombres en blanco.
Una forma de solucionarlo sería almacenar, en vez del nombre de los componentes, otro valor que identificara de forma única cada componente, como, por ejemplo, un índice.

Para calcular este índice, a la hora de recuperar componentes, tomaríamos como base el índice de nuestro componente, TDoublePanel (ya que el propietario de los controles insertados en nuestros panels va a ser el mismo que el de nuestro componente), y aprovecharíamos el hecho de que los controles se almacenarán de forma consecutiva, en el archivo de recursos .dfm, inmediatamente después de nuestro componente. Para la escritura, lo que podríamos hacer es generar un índice relativo para cada control insertado en los panels, comenzando, por ejemplo, en 0 (teniendo en cuenta que primero se almacena los controles del panel superior y después los del inferior).

Hice una prueba en base a esto y, de momento, parece que funciona bien (tanto en ejecución como en diseño). Aqui os dejo el código completo modificado:

Código Delphi [-]
unit DoublePanel;

interface

uses
  Classes, Controls, ExtCtrls, Forms;

type
  TSubPanel = class(TPanel)
  private
    FDropList: TStringList;

  protected
    property DropList: TStringList read FDropList;

    procedure ReadControls(Reader: TReader);
    procedure SetControls(Root: TComponent);

    procedure WriteControls(Writer: TWriter);
    procedure GetControls(Proc: TGetChildProc; Root: TComponent);

  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

  TDoublePanel = class(TPanel)
  private
    TopPanel: TSubPanel;
    ClientPanel: TSubPanel;
    IndCtrl: Integer; // Índice relativo para controles insertados
  protected
    procedure CreateWnd; override;

    procedure DefineProperties(Filer: TFiler); override;
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
    procedure Loaded; override;

  public
    constructor Create(AOwner: TComponent); override;
  end;

procedure Register;

implementation

uses
  SysUtils, Dialogs;
  
{ TSubPanel }

constructor TSubPanel.Create(AOwner: TComponent);
begin
  inherited;

  TabStop := false;
  FDropList := TStringList.Create;
end;

destructor TSubPanel.Destroy;
begin
  FDropList.Free;
  inherited;
end;

procedure TSubPanel.ReadControls(Reader: TReader);
begin
  FDropList.Clear;
  Reader.ReadListBegin;

  while not Reader.EndOfList do
    DropList.Add(Reader.ReadString);

  Reader.ReadListEnd;
end;

procedure TSubPanel.SetControls(Root: TComponent);
var
  Component: TComponent;
  I, J: Integer;
begin
  inherited;

  { Calculamos el índice para el primero de los controles del panel,
    en el array de componentes perteneciente al propietario de dichos
  controles, según el índice del propietario del panel.  
  
  (Este cálculo se basa en el hecho de que los controles se almacenarán 
  de forma consecutiva despues del propietario del panel) } 
  J := Owner.ComponentIndex + 1; 

  for I := 0 to DropList.Count - 1 do
  begin
    Component := Root.Components[StrToInt(DropList[i]) + J];  

    if Assigned(Component) then
      TControl(Component).Parent := Self;
  end;
end;

procedure TSubPanel.WriteControls(Writer: TWriter);
var
  I: Integer;
begin
  Writer.WriteListBegin;

  // Almacenamos el índice relativo de cada control
  for I := 0 to ControlCount - 1 do 
  begin
  Writer.WriteString(IntToStr(TDoublePanel(Owner).IndCtrl); 
    Inc(TDoublePanel(Owner).IndCtrl);
  end;

  Writer.WriteListEnd;
end;

procedure TSubPanel.GetControls(Proc: TGetChildProc; Root: TComponent);
var
  Control: TControl;
  I: Integer;

begin
  for I := 0 to ControlCount - 1 do
  begin
    Control := Controls[i];

    if Control.Owner = Root then
      Proc(Control);
  end;
end;

{ TDoublePanel }

constructor TDoublePanel.Create(AOwner: TComponent);
begin
  inherited;
  ControlStyle := ControlStyle - [csAcceptsControls];

  Width := 377;
  Height := 249;
  BevelOuter := bvLowered;

  TopPanel := TSubPanel.Create(Self);
  TopPanel.Align := alTop;
  TopPanel.Parent := Self;

  ClientPanel := TSubPanel.Create(Self);
  ClientPanel.Align := alClient;
  ClientPanel.Parent := Self;
end;

procedure TDoublePanel.CreateWnd;
begin
  inherited;
end;

procedure TDoublePanel.DefineProperties(Filer: TFiler);
begin
  inherited;
  IndCtrl := 0; // Reiniciamos la variable para calcular los índices de los controles
  Filer.DefineProperty('TopPanel', TopPanel.ReadControls, TopPanel.WriteControls, true);
  Filer.DefineProperty('ClientPanel', ClientPanel.ReadControls, ClientPanel.WriteControls, true);
end;

procedure TDoublePanel.GetChildren(Proc: TGetChildProc; Root: TComponent);
begin
  inherited;

  TopPanel.GetControls(Proc, Root);
  ClientPanel.GetControls(Proc, Root);
end;

procedure TDoublePanel.Loaded;
var
  Root: TComponent;

begin
  inherited;

  Root := GetParentForm(Self);

  TopPanel.SetControls(Root);
  ClientPanel.SetControls(Root);
end;

{ Unit methods }

procedure Register;
begin
  RegisterComponents('Samples', [TDoublePanel]);
end;

end.

Comprobareis que ahora si da igual que los componentes tengan nombres o no, ya que siempre los recupera bien y los posiciona correctamente.

(De todas formas, cualquier error que econtréis avisadme!)

Saludos!
Responder Con Cita
Respuesta



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


La franja horaria es GMT +2. Ahora son las 21:55:50.


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