![]() |
Creación de componentes
Estoy creando un componente que he derivado de TPanel. Lo único que hace es crear dos paneles dentro de él con las propiedades altop y alclient.
Cuando desde diseño se insertan componentes dentro de uno de los dos paneles que contiene mi nuevo componente al ejecutarlo pierdo estos componentes introducidos. Sé que es lógico que pase porque en el create del componente creo los dos paneles desde el inicio. Mi pregunta es: ¿Hay alguna manera de no perder estos componentes incrustados en diseño cuando ejecuto o de mantenerlos en algún sitio para luego añadirlos? |
Resp
Dame codigo
|
Lamentablemente, no se puede hacer lo que quieres. Una alternativa es, en vez de crear un componente, usar la clase TFrame y meter ahí los dos paneles que necesitas (mas otros componentes que pudieras necesitar). Por último, habría que añadir este frame en todos los formularios que lo vayan a utilizar, y con esto conseguimos la reutilización de código casi igual que si hubieramos creado un componente específico.
Saludos! |
Te agradezco la idea, pero he trabajado bastante con frames y para este caso no es lo que necesito.
Realmente lo que quiero es que mi componente funcione de contenedor al igual que lo hace un TPanel. Voy a explicarlo de otra manera, igual queda más claro: Si derivo un componente de TPanel (vamos a llamarlo MiPanel) y en tiempo de diseño le añado componentes (un label, por ejemplo), al ejecutarlo sigue conteniendo estos componentes añadidos (en nuestro caso, el label). Pero si a mi componente (MiPanel) le añado en el create otro panel (Panel1) y en tiempo de diseño le añado otros componentes al Panel2 (esta vez quien contiene el label no es mi componente sino Panel2), cuando lo ejecuto como en el create del componente de mi componente (MiPanel) se crea de nuevo el panel Panel1 pierdo estos componentes. Hay alguna manera de guardar estos componentes insertados en el Panel1 para poder añadirlos en el create y que no me desaparezcan. No se si queda más claro, pero más o menos es lo que quiero hacer. |
Hola,
Cita:
Sin embargo, si añades algún control en el panel "superior" que creaste dentro de tu componente (el panel principal) y vuelves a mirar el formulario "en modo de texto" verás que no se ha añadido ningún control al mismo, ni dentro de tu componente ni fuera del mismo. De hecho, cuando vuelvas a mostrar el fomulario "normalmente" el control que hubieras añadido ya no estará ahí: solamente quedará su declaración, que habrá de borrarse, si se quiere compilar... ¿Porqué pasa eso? No tengo idea. He estado toda la tarde dándole vueltas al asunto que te ocupa porque me llamó la atención. He probado de varias formas. Cuando jmariano respondió que era algo que no era posible, a punto estuve de responder para preguntarle porqué no era posible, es decir, si conocía el motivo por el cual no podía lograrse esto que trataba de hacerse... |
Como ya te comenté, no se puede hacer lo que quieres porque ese "Panel2" pierde la capacidad de almacenar "subcomponentes" de forma persistente. Verás que ni siquiera se almacena en el archivo de recursos del formulario, .dfm, las propiedades "published" de "Panel2" (como comenta dec), aunque esto se podría solucionar creando una propiedad "published" que apunte a "Panel2" e invocando, para dicho control, el método "SetSubComponent" (pasándole el parámetro "True") en el constructor de la clase, pero la capacidad de poseer subcomponentes y que estos sean persistentes se pierde (esto se debe a como se maneja el almacenamiento de las propiedades en el archivo de recursos del formulario .dfm).
Saludos! |
dec veo que has hecho las mismas pruebas que yo. Y estoy como tú, si es como dice jmariano que no se puede, por qué con un TPanel si que es posible?
|
Hola,
Bueno, supongo que no es posible por lo que jmariano dice: Cita:
Cita:
|
Cita:
Otra solución, relacionada, precisamente, con esto, es que tus panels internos tengan como propietario el mismo de tu componente (que podría ser un formulario, frame u otro panel) y que el padre sea tu componente (para que aparezcan dentro de tu componente). Es decir, el "Owner" de tus panels es el "Owner" de tu componente y el "Parent" de tus panels sería tu componente. (Aunque, asegúrate siempre de no volver a crear los panels si ya existen en el propietario). De esta forma, si conseguirías que se pudieran insertar controles en tus panels y, estos, serían persistentes (no se perderían al ejecutar la aplicación). El único problema es que los panels podrían ser modificados en tiempo de diseño y se podrían eliminar. Saludos! |
Hola,
Cita:
Cita:
|
Buenas!
Dónde tienes el código para la creación de los 2 paneles? En el onCreate del componente? Si es así, prueba a instanciar el Oncreate de tu componente, asegúrate que lleva la instrucción inherited para que llame al oncreate del padre y debuga para ver si en ésta instrucción entra a crear los 2 paneles que no te aparecen! Un saludo Edu |
Cita:
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 |
La verdad es que un compañero mío ha intentado crear el componente a partir de Frames y el problema es el mismo.
|
Cita:
Pongo un ejemplo que construí para la ocasión:
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:
Cita:
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! |
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 |
Cita:
(Sigo pensando que la mejor solución para CAOS está en utilizar los frames) Saludos! |
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. |
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 :p ).
// Saludos |
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! |
Hola,
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 ocurre, por ejemplo, con "TLabel"; ocurre, empero, con "TEdit" y/o "TMemo". He probado alguna que otra cosa, básicamente tratando de utilizar "Invalidate", "Repaint", "Refresh", pero, claro está, esto no ha dado resultados. El caso es que en tiempo de ejecución los componentes "escondidos" se muestran perfectamente. Incluso en el cambio del tiempo de ejecución al tiempo de diseño: basta, sin embargo, mover el componente para que los susomentados controles desaparezcan debajo del... ¿padre de los paneles? De todos modos es lo cierto que el componente se acerca mucho a lo pretendido, y además que no deja indiferente cómo lo hace: yo aún no lo comprendo del todo, tal vez no lo haga, porque, ciertamente, pensaba que algo como lo que hace sería bastante más complicado. |
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: TForm1La 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í:
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: TForm1Al 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: TForm1Cada 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 |
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 |
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! |
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. ;) |
Cita:
En realidad es IDesignNotification y está en la unidad DesignIntf. // Saludos |
Cita:
Saludos! |
Cita:
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 |
Hola,
Cita:
Cita:
Cita:
|
Cita:
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:
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! |
| La franja horaria es GMT +2. Ahora son las 05:48:43. |
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