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
  #1  
Antiguo 24-01-2010
Avatar de DriverOp
DriverOp DriverOp is offline
Miembro
 
Registrado: ago 2007
Posts: 93
Poder: 17
DriverOp Va por buen camino
Ventana modal y cómo cerrarla por código.

Uso Delphi 6.0 con updates 1 y 2.

Mi problema trata sobre cómo es la forma correcta de cerrar y liberar por código ventanas modales ya que en mi aplicación de la forma que lo estoy haciendo a veces me lanza una excepción y a veces no.

Cuando sucede una excepción puede ser un clásico Access Violation y a veces un "Abstract Error" que nunca vi en mi vida.

La situación es esta. Le muestro al usuario una lista de datos, internamente la lista es un TObjectList. El usuario puede hacer doble clic en cualquier dato y le traerá una ventana modal donde puede manipular ese dato en particular. Como la ventana es modal no puede hacer otra cosa más que eso mientras la tenga abierta.

La ventana modal la creo dinámicamente así (simplificado):

Código:
if not Assigned(FrmModif) then
  FrmModif:=TFrmModif.Create(Self);
try
  FrmModif.Nodo:=Nodo; // 'Nodo' es el nodo de la lista que está por ser editada
  FrmModif.ShowModal;
finally
  FreeAndNil(FrmModif);
end;
Lo que me asegura que la ventana quede liberada.

Pero sucede que bajo ciertas circunstancias, la lista que está "detrás" puede cambiar, y me refiero a cambiar totalmente, debido a un evento externo al programa de forma tal que los datos mostrados en la lista dejan de ser válidos. Aquí la forma de proceder es cuando sucede el evento elimino la lista actual y la vuelvo a construir con los nuevos datos.

Como podrán darse cuenta el problema está en que si el usuario justo en ese momento tiene una ventana modal abierta está tratando de manipular un dato que ya no existe en la lista de datos. Lo que hago aquí es cerrarle la ventana modal por código[1]. Aquí es donde aparece el problema.

Lo que hago es esto:

Código:
if Assigned(FrmModif) then
  FrmModif.ModalResult:=MrCancel;
Es decir, es como si el usuario hubiese cancelado la ventana.
Supongo que esto causa que se ejecute el resto del código que está más arriba, es decir, que se ejecute el FreeAndNil().

Sin embargo como he mencionado, a veces me causa excepción por Access Violation y a veces por 'Abstract Error' pero a veces no causa ningún problema y el código funciona lo más bien. No he podido determinar bajo qué circunstancias ocurre una cosa o la otra y el debugger de Delphi no me ayuda en mucho porque cuando sucede la excepción me la marca en el procedimiento Application.Run;

Evidentemente algo estoy haciendo mal pero no sé qué es.

Gracias por su atención.

[1]En realidad el evento que invalida la lista entera es causado indirectamente por el usuario, el usuario 'sabe' que los datos actuales de mi programa no serán válidos pero eso no quita que el problema siga estando allí. No le puedo decir al usuario "antes de hacer A tienes que hacer B porque sino tendrás un feo error", es algo que tiene que manejar mi programa transparentemente.
Responder Con Cita
  #2  
Antiguo 24-01-2010
Avatar de Al González
[Al González] Al González is offline
In .pas since 1991
 
Registrado: may 2003
Posts: 5.604
Poder: 30
Al González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en bruto
¡Hola!

Tu planteamiento es muy claro en varios aspectos. Ojalá todos prestaran esa dedicación a la hora de escribir las preguntas. Te felicito.

Cita:
Empezado por DriverOp Ver Mensaje
No he podido determinar bajo qué circunstancias ocurre una cosa o la otra y el depurador de Delphi no me ayuda en mucho porque cuando sucede la excepción me la marca en el procedimiento Application.Run;
Te recomiendo que compiles la aplicación con la opción "Use debug DCUs" activa. Esto te permitirá conocer con mayor detalle la pila de llamadas (call stack) al momento de ocurrir la excepción y quizá con esto podrás inferir en dónde está el problema.

Si se complica, te agradeceríamos expusieras algo más del código involucrado. Y de paso que nos digas si estás haciendo uso de eventos como OnClose, OnCloseQuery, OnHide, OnDeactivate, etc. (muéstranos el código que contienen en caso dado).

Saludos.

Al González.
Responder Con Cita
  #3  
Antiguo 24-01-2010
Avatar de Lepe
[Lepe] Lepe is offline
Miembro Premium
 
Registrado: may 2003
Posts: 7.424
Poder: 29
Lepe Va por buen camino
Para evitar los access violation, no le pases el "nodo" (que es un puntero al elemento de la lista que vas a eliminar), crea un nuevo objeto TNodo, copia el valor de sus propiedades, y ese es el que pasas a la ventana Modal.

El error Abstracto es más difícil de identificar y explicar. Atendiendo a su definición, estás llamando a un método de una clase que jamás debería ser llamado, en su lugar, debería llamarse al mismo método en sus clases hijas (Tform->TcustomForm->Tcomponent... / TObjectList-> TList -> TObject).

Dicho así vaya rollo, pero estoy casi seguro que el problema viene por la creación y destrucción de la ventana modal.

Yo abogo por una solución así:
Código Delphi [-]
with TFrmModif.Create(nil) do
try
  // nodo pertenece a TFrmModif, se crea y se destruye en TFmModif
  Nodo.Assign(NodoLista); // 'NodoLista' es el nodo de la lista que está por ser editada
   if ShowModal = mrYes then
   begin 
      .....
   end
finally
  Free;
end;
Como ves quito del escenario el Owner de la ventana Modal (al cual hay que notificar cuando se creaba la ventana FrmModif y también al liberarla), también elimino la variable temporal FrmModif que podría tener un valor no consistente... esto necesita explicación.

El hecho es que has puesto código simplificado, tu aplicación tendrá más lineas aquí y allá, de forma que podría darse el caso de asignar mrCancel a la ventana modal y después crearla por código de nuevo, sin que ésta se haya liberado del todo (o se esté liberando en ese momento), cosas más raras he visto...

Una cosa que no me ha gustado de tu código es que no compruebas el ModalResult (if showmodal = mrYes then) y eso hace se ejecute código que no hace falta ejecutar; no lo digo ya por eficiencia, es que puede ocasionar efectos colaterales con otra parte del código. Con mi código, si se le asigna mrCancel a la ventana modal, la siguiente instrucción será el TFrmModif.Free (sin código adicional en medio), además, al no usar la variable FrmModif, me aseguro que siempre se usa RAM nueva, no hay posibilidad de que se solapen una creación y destrucción de FrmModif.

Si ahora me dices que necesitas la variable FrmModif, entonces te responderé que tu diseño (de comunicación entre ventanas) podría mejorarse . Usa eventos creados por tí, para que ese evento externo de la aplicación informe directamente a TFrmModif que ha de cerrarse.

Dicho de otra forma: Cuando se crea la ventana de tipo TFrmModif, ella misma registra un evento para informarse de cuando la lista ha cambiado, y en ese caso, ella misma se destruye. La ventaja es que tienes la oportunidad de cancelar cualquier proceso que se esté haciendo en la la ventana modal, haciendo que pase a un "estado estable" antes de destruirla, algo imposible con la asignación "FrmModif.ModalResult:=MrCancel"

Saludos y suerte
__________________
Si usted entendió mi comentario, contácteme y gustosamente,
se lo volveré a explicar hasta que no lo entienda, Gracias.
Responder Con Cita
  #4  
Antiguo 24-01-2010
Avatar de DriverOp
DriverOp DriverOp is offline
Miembro
 
Registrado: ago 2007
Posts: 93
Poder: 17
DriverOp Va por buen camino
Thumbs up

Cita:
Empezado por Al González Ver Mensaje
¡Hola!
Hola .

Cita:
Empezado por Al González Ver Mensaje
Tu planteamiento es muy claro en varios aspectos. Ojalá todos prestaran esa dedicación a la hora de escribir las preguntas. Te felicito.
Gracias.
Costumbre. Suelo responder mucho más que preguntar (en otros foros, sobre otros temas) por eso he aprendido cómo preguntar :P.

Cita:
Empezado por Al González Ver Mensaje
Te recomiendo que compiles la aplicación con la opción "Use debug DCUs" activa. Esto te permitirá conocer con mayor detalle la pila de llamadas (call stack) al momento de ocurrir la excepción y quizá con esto podrás inferir en dónde está el problema.
Ah!, excelente dato, siempre se aprende algo nuevo .

Cita:
Empezado por Al González Ver Mensaje
Si se complica, te agradeceríamos expusieras algo más del código involucrado. Y de paso que nos digas si estás haciendo uso de eventos como OnClose, OnCloseQuery, OnHide, OnDeactivate, etc. (muéstranos el código que contienen en caso dado).
Si te refieres a la ventana modal (FrmModif) pues no, no hay código para ninguno de esos eventos.

Cita:
Empezado por Al González Ver Mensaje
Saludos.
Gracias por responder, me ha sido útil.

Cita:
Empezado por Lepe Ver Mensaje
Para evitar los access violation, no le pases el "nodo" (que es un puntero al elemento de la lista que vas a eliminar), crea un nuevo objeto TNodo, copia el valor de sus propiedades, y ese es el que pasas a la ventana Modal.
Me has abierto los ojos, gracias por el truco.

Cita:
Empezado por Lepe Ver Mensaje
Yo abogo por una solución así:
Al ver tu código me ha parecido ver la solución pero tengo un problema. Si lo hago tal como dices cuando tengo que cerrar la ventana por código no tengo ninguna referencia a esa ventana (no tengo un puntero a la ventana). Quizá no quedó claro en los códigos que he puesto en mi primer mensaje. El segundo código se ejecuta en un evento totalmente diferente del primero.

En cambio al tener en la ventana principal la variable FrmModif puedo determinar si esa ventana se está mostrando actualmente o no.

En tu versión al copiar la información del Nodo que se está editando en otro tipo TNodo que pertenece a TFrmModif a simple vista parecería que mi problema se soluciona pero desde el punto de vista del usuario la ventana sigue allí con datos que ya no se corresponden a ninguno de los nodos de la lista de datos. ¿Se comprende lo que digo?. (Hay más comentario sobre esto más abajo).

Cita:
Empezado por Lepe Ver Mensaje
Una cosa que no me ha gustado de tu código es que no compruebas el ModalResult...
Tienes razón.
Pero en realidad no necesito comprobar ese valor en la ventana principal (tal como está en el código que he puesto yo en mi primer mensaje) ya que como le paso a la ventana modal una referencia (y no una copia como propones tú) cualquier cambio que haga en TFrmModif.Nodo se reflejará inmediatamente en el Nodo de la lista (concretamente esto lo hago solamente en el evento OnClick del botón 'Aceptar' de la ventana modal).

De implementar tu solución, por supuesto que tengo que comprobar el valor de retorno de ModalResult, eso sí lo tengo claro.

Cita:
Empezado por Lepe Ver Mensaje
Si ahora me dices que necesitas la variable FrmModif, entonces te responderé que tu diseño (de comunicación entre ventanas) podría mejorarse .
No puedo estar más de acuerdo con esto .

Cita:
Empezado por Lepe Ver Mensaje
Usa eventos creados por tí, para que ese evento externo de la aplicación informe directamente a TFrmModif que ha de cerrarse.
He visto la luz
Excelente. Eso voy a hacer. No necesitas explicarme nada más, ya entendí tu idea. Todavía no he implementado la solución propuesta pero con lo que me has dicho confío en que funcionará.

Gracias Lepe!
Responder Con Cita
  #5  
Antiguo 26-01-2010
Avatar de DriverOp
DriverOp DriverOp is offline
Miembro
 
Registrado: ago 2007
Posts: 93
Poder: 17
DriverOp Va por buen camino
Hola de nuevo.

Primero debo decir que los tips de Lepe me sirvieron de mucho, he hecho lo que ha propuesto él y todo funciona bien en esa parte.

Sin embargo lamento decir que el problema de fondo no se resolvió. Sucede que lo que yo creía que era el problema en realidad es solo un síntoma de un problema más profundo. En otras palabras el problema no está en la destrucción de la ventana modal como yo creía. El problema parte de un evento anterior a la creación de esa ventana.

Paso a explicar lo que sucede.
Como había dicho en mi primer mensaje, el programa presenta una lista de datos, esa lista es un TObjectList, uso esta clase porque necesito que cada nodo de la lista contenga un componente TLabel (que es el encargado de mostrar visualmente los datos). Para darle la opción al usuario a editar esos datos el nodo debe capturar el evento click de ese TLabel, en la clase TNodo he creado un evento nuevo que debe ejecutarse cuando se hace clic en el TLabel y que además pasa como parámetro el índice del nodo en la lista, así puedo saber en qué nodo se hizo clic. En el formulario principal asigno código a ese evento y allí creo la ventana modal para editar ese nodo.

En circunstancias normales todo funciona bien. Pero cuando la lista se destruye debido a que todos los datos son invalidados (como expliqué en mi primer mensaje), el contexto del evento click en el nodo no ha terminado aún cuando la ventana modal está abierta. En ocaciones, y supongo que se debe a cómo Delphi administra la sicronización de eventos, el contexto del evento termina antes que se destruya la lista y entonces no ocurre ningún error, pero a veces termina después de que la lista ha sido destruida y por lo tanto se trata de finalizar un evento de un componente que ya no existe, el resultado es un 'Access Violation'.

Cuando tengo que destruir la lista y volver a crearla con datos nuevos procedo de esta manera.

1.- Ocurre el evento de sicronización de datos.
2.- Verifico si hay una ventana modal abierta.
3.- Si la hay procedo a cerrarla.
4.- Llamo al método Clear de TObjectList eliminando todos los nodos incluyendo los TLabels.

Pero si hay una ventana modal abierta es porque ha sucedido lo siguiente.

1.- El usuario hace clic en el TLabel del nodo.
2.- Ejecuto el evento.
3.- Se crea la ventana modal lista para editar los datos del nodo.
4.- (1) Ocurre el evento de sicronización de datos.
5.- (2) Verifico si hay una ventana modal abierta.
6.- (3) Si la hay procedo a cerrarla.
7.- (4) Llamo al método Clear de TObjectList.
8.- El evento clic del TLabel del nodo debe terminar.
9.- Tal TLabel no existe = Access Violation.

Para complicar las cosas el OnClick del TLabel no es el único evento que se ejecuta, Delphi llama a tres eventos en total: OnClick, OnMouseDown y OnMouseUp. Es este último donde aparece el Access Violation aún cuando no tengo ningún código para ese evento Delphi aún así tiene que verificar eso.

Poner un Application.ProcessMessage; junto antes de destruir la ventana modal no soluciona el problema.

Revolviendo el código de la VCL llegué a la línea 4759 del Controls.pas, es una llamada al procedimiento DoMouseUp() de los TControls. Me siento muy inclinado a encerrar esa llamada en un try ... except para que no aparezca ante el usuario el mensaje de error :P. Pero sé que esa no es solución.

¿Alguna idea?.

Si es necesario que ponga código no tengo problemas en escribir uno que reproduzca el problema, aunque quedará medio largo.

Gracias por su atención.
Responder Con Cita
  #6  
Antiguo 26-01-2010
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
El problema, según lo veo, no es en sí la ventana modal, sino el hecho de que la etiqueta se destruye antes de terminar el Click. Este problema he podido reproducirlo con relativamente poco código y una solución que parece buena es esta:

Cuando ocurra la sincronización de datos, en lugar de cerrar la ventana y destruir la lista, sólo cierras la ventana, y mandas un mensaje personalizado a la ventana principal:

Código Delphi [-]
CerrarModal;
PostMessage(Handle, CM_CANCELARDIALOGO, 0, 0);

En el manejador del mensaje CM_CANCELARDIALOGO es donde destruyes la lista. Observa que tienes que mandar el mensaje con PostMessage y no con SendMessage.

// Saludos
Responder Con Cita
  #7  
Antiguo 26-01-2010
Avatar de DriverOp
DriverOp DriverOp is offline
Miembro
 
Registrado: ago 2007
Posts: 93
Poder: 17
DriverOp Va por buen camino
roman:
Sí, es así. Como he explicado en mi mensaje anterior. El problema estaba en que estoy destruyendo un objeto que tiene un evento pendiente.

Como la aplicación la tengo que entregar hoy lo he resuelto de forma parecida a como lo dices tú, con mensajes personalizados.

En el evento OnClick del Nodo en vez de abrir la ventana modal envio un mensaje personalizado CM_blablabla hacia la ventana principal que al recibirlo abre la ventana modal. De esa forma se termina el evento OnClick inmediatamente.
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

Temas Similares
Tema Autor Foro Respuestas Último mensaje
Ventana modal nenufer Varios 4 25-04-2006 22:02:55
problema con ventana Modal ingel Varios 2 19-12-2005 23:52:54
Cerrar componente con ventana modal elcigarra OOP 7 12-10-2005 13:17:53
Resultado de una ventana modal (CLX) salvica OOP 1 11-02-2005 14:20:31
No consigo cerrar una ventana modal eliasterrero Varios 5 11-11-2003 23:55:48


La franja horaria es GMT +2. Ahora son las 17:50:16.


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