Club Delphi  
    Paypal   FTP   CCD     Buscar   Trucos   Trabajo   Foros

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

Coloboración Paypal con ClubDelphi

Respuesta
 
Herramientas Buscar en Tema Desplegado
  #1  
Antiguo 14-09-2005
CAOS CAOS is offline
Registrado
 
Registrado: mar 2005
Posts: 9
Poder: 0
CAOS Va por buen camino
La verdad es que un compañero mío ha intentado crear el componente a partir de Frames y el problema es el mismo.
Responder Con Cita
  #2  
Antiguo 14-09-2005
Avatar de jmariano
jmariano jmariano is offline
Miembro
 
Registrado: jul 2005
Posts: 376
Poder: 21
jmariano Va por buen camino
Cita:
Empezado por roman
¿Cómo?

Quizá es fácil pero hasta el momento no veo como lograr esto. Cuando se usa "View as text" y luego "View as form" el constructor Create se ejecuta antes de leer el dfm creando una instancia de los paneles interiores que nunca serán las que contengan componentes insertadas en diseño.

Posiblemente redefiniendo DefineProperties, no lo sé. Creo que es demasiada complicación para algo que se resuelve con Frames (hasta ahora no he visto la razón para no usarlos).

// Saludos
Una forma de hacerlo sería buscando dos paneles en el propietario cuyo padre sea nuestro componente (ya que nuestro componente ha de poseer dos paneles). Para realizar esta operación habría que sobrecargar el método "CreateWnd", ya que si realizamos dicha operación en el constructor Create fallaría (creo que esto es debido a que "CreateWnd" debe de ejecutarse despues del constructor de la clase y despues de que fue leído el archivo de recursos .dfm, al igual que el método "Loaded" pero con la diferencia de que "CreateWnd" es llamado siempre que el control necesita "recrearse").

Pongo un ejemplo que construí para la ocasión:

Código Delphi [-]
type
  TMiPanel = class(TPanel)
  private
    FPanelSup: TPanel;
    FPanelInf: TPanel;
  protected
    procedure Loaded; override;
    procedure CreateWnd; override;
  public
    constructor Create(AOwner: TComponent); override;
    procedure CrearPanels;
  end;

procedure TMiPanel.CrearPanels;
var
  I: Integer;
begin
  { Recorremos los componentes del propietario para encontrar dos panels que
    tengan como padre nuestro componente. }
  for I := 0 to Owner.ComponentCount - 1 do
    if (Owner.Components[i] is TPanel) and (TPanel(Owner.Components[i]).Parent = Self) then
      if not Assigned(FPanelSup) then
        FPanelSup := TPanel(Owner.Components[i])
      else if not Assigned(FPanelInf) then
        FPanelInf := TPanel(Owner.Components[i])
      else Break; // Si no encontramos ningún panel salimos del bucle

  // Creamos el panel superior si no existe
  if not Assigned(FPanelSup) then
    FPanelSup := TPanel.Create(Owner);

  with FPanelSup do // Configuramos algunas propiedades
  begin
    Parent := Self;
    Align := alTop;
  end;

  // Creamos el panel inferior si no existe
  if not Assigned(FPanelInf) then
    FPanelInf := TPanel.Create(Owner);

  with FPanelInf do // Configuramos algunas propiedades
  begin
    Parent := Self;
    Align := alClient;
  end;
end;

constructor TMiPanel.Create(AOwner: TComponent);
begin
  inherited;
  Height := 100;
  Width := 250;
end;

procedure TMiPanel.CreateWnd;
begin
  inherited;
  CrearPanels; // Creamos los paneles
end;

procedure TMiPanel.Loaded;
begin
  inherited;
  CrearPanels; // Creamos los paneles
end;

Si lo probáis veréis que se crea un panel con dos paneles en su interior y que se pueden añadir controles en ambos paneles sin que se pierdan en ejecución. Tambien vereis, si visualizais el formulario como texto, que los controles añadidos forman parte del formulario (gracias a esto se mantiene la persistencia) y que, al volver a la vista de formulario normal, no ocurre el error de que tal panel ya existe (gracias a la comprobación que hacemos en el metodo "CrearPanels").

La desventaja, como ya dije, es que se pueden modificar los paneles en diseño y eliminar ( aunque siempre que la aplicación se ejecute los paneles aparecerán).


Cita:
Empezado por dec
Hola,


Esa fue una de las formas en que probé a hacerlo, y, obtenía un bonito error tal que este:

Project Project1.exe raised exception class EClassNotFound with message 'Class TPanel not found'.

Sin embargo, ahora que lo has dicho he probado de nuevo a hacerlo y he conseguido que el error no se de, añadiendo a la cláusula "initialization" de la unidad en que está el componente (que vengo probando) la instrución "RegisterClass(TPanel);". Creo que no queda sino impedir que se puedan eliminar los paneles en tiempo de diseño...
Para que esto no ocurra es necesario darle un nombre a los paneles (verás que, por defecto, Delphi los nombra como "Components[n]"), pero es mejor dárselos en tiempo de diseño (desde el mismo formulario) y no en el mismo componente a la hora de crearlos, porque puede haber problemas si se eliminan paneles y se vuelven a crear (es por eso que no les dí nombre en el método "CrearPanels"). (en verdad, podemos dejar vacía la propiedad "Name" de todos los componentes que soltamos en un formulario, pero despues será necesario registrar las diferentes clases de nuestros componentes manualmente).

Cita:
Empezado por CAOS
La verdad es que un compañero mío ha intentado crear el componente a partir de Frames y el problema es el mismo.
Eso ocurre porque, seguramente, está registrando el Frame en el IDE como un componente normal (utilizando el método "RegisterComponent"), lo que es un error porque el frame pierde, entonces, la capacidad de poder manejar el archivo de recursos .dfm relacioando.

Ten encuenta que un frame casi se comporta como si fuera un componente más. Fíjate que, incluso, cuando lo añadimos a un formulario pareciera que añadimos un control normal.

Si tu problema para usar los frames es porque necesitas propiedades en tiempo de diseño (es decir, propiedades que aparezcan en el inspector de objetos) te comento que puedes crearlas, aunque tendrás que realizar alguna operación especial (comentame si es por esto y te digo como hacerlo).

Saludos!
Responder Con Cita
  #3  
Antiguo 14-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,

Escribí mi anterior mensaje y me fui a dormir y me quedé pensando en el método Loaded y ahora mismo iba a hacer las pruebas pero visto que ya las hiciste y te han funcionado veo que es posible.

Lo de evitar que se destruyan los paneles en el diseño pienso que podría hacerse en el método Notification.

Aún así, ya CAOS decidirá si posiblemente use finalmente frames. Como ya le comentas, si lo que quiere es añadir propiedades publicadas al mismo frame, puede hacerse.

// Saludos
Responder Con Cita
  #4  
Antiguo 14-09-2005
Avatar de jmariano
jmariano jmariano is offline
Miembro
 
Registrado: jul 2005
Posts: 376
Poder: 21
jmariano Va por buen camino
Cita:
Empezado por roman
Lo de evitar que se destruyan los paneles en el diseño pienso que podría hacerse en el método Notification.
El problema es que el propietario (en este caso, el formulario) tambien recibe la notificación de la destrucción del componente, por lo que se complica mucho evitarlo (de hecho, intenté forzar la creación del panel desde el componente al recibir la notificación y obtuve un error de excepción, por lo que creo que la operacion es casi imposible). En realidad, el componente no debería recibir la notificación, ya que no es el propietario, por lo que creo que es el mismo formulario quien lo notifica a todos los subcomponentes que sean padres de los componentes o, mejor dicho, controles que posea.

(Sigo pensando que la mejor solución para CAOS está en utilizar los frames)

Saludos!
Responder Con Cita
  #5  
Antiguo 14-09-2005
CAOS CAOS is offline
Registrado
 
Registrado: mar 2005
Posts: 9
Poder: 0
CAOS Va por buen camino
La verdad es que al final voy a poder evitar el problema, no solucionarlo sólo evitarlo.

De todas maneras, probaré vuestras últimas ideas, ya más por curiosidad que por ir a utilizarlo en este caso concreto.

Muchas gracias por vuestra ayuda, la verdad es que ideas no os han faltado. Otra vez gracias y un saludo.
Responder Con Cita
  #6  
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, les tengo buenas noticias. El código que muestro es prácticamente lo que originalmente se quería: un panel con dos subpaneles en los cuales se pueden insertar componentes pero que no son modificables durante el diseño. Claro está que las componentes se quedan donde deben estar al ejecutar la aplicación y/o reabrir el formulario.

Quisiera decir que esto se me ha ocurrido a mi pero no es así. Esto no evita que me de gusto descubrir, una vez más, que Delphi es mucho más potente de lo que imaginamos.

Como ahora ya me voy a dormir les dejaré en suspenso hasta más tarde en cuanto a la procedencia del código. Sólo diré ahora que lo he adaptado para nuestro caso y si algo falla seguramente será culpa mía y no desde luego del autor original.

La componente tiene aún un pequeño problema pero estoy seguro que jmariano lo podrá arreglar. Los demás también pero él fue quien tomó en cuenta dicho problema.

Confieso además que no he terminado de entender cabalmente el código pero ya entre todos hallaremos la explicación.

Por hoy sólo me resta comentarles que lo he probado con Delphi 7 pero supongo que con algunos cambios funcionará en otras versiones (lo supongo porque para simplificar quité algunas condicionales de compilación ).

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;

  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

{ 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.ReadIdent);

  Reader.ReadListEnd;
end;

procedure TSubPanel.SetControls(Root: TComponent);
var
  Component: TComponent;
  I: Integer;

begin
  inherited;

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

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

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

begin
  Writer.WriteListBegin;

  for I := 0 to ControlCount - 1 do
    Writer.WriteIdent(Controls[i].Name);

  Writer.WriteListEnd;
end;

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

begin
  inherited;

  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;

  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.

// Saludos
Responder Con Cita
  #7  
Antiguo 15-09-2005
Avatar de jmariano
jmariano jmariano is offline
Miembro
 
Registrado: jul 2005
Posts: 376
Poder: 21
jmariano Va por buen camino
Bravo roman! (reconozco que pasé por alto los métodos para trabajar con el stream porque no quería complicar mucho el problema, ademas de que no creo que se me hubiera ocurrido hacerlo tan sencillo).

El componente utiliza un técnica muy simple para saber que contoles pertenecen a un panel determinado y el único problema que le veo (y si hay otro aun no me he dado cuenta) es que como dejes los componentes sin nombre ¿Quien se convierte en el padre? pero por lo demás funciona bastante bien...

Saludos!
Responder Con Cita
  #8  
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
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


La franja horaria es GMT +2. Ahora son las 01:35:31.


Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi
Copyright 1996-2007 Club Delphi