FTP | CCD | Buscar | Trucos | Trabajo | Foros |
|
Registrarse | FAQ | Miembros | Calendario | Guía de estilo | Temas de Hoy |
|
Herramientas | Buscar en Tema | Desplegado |
#1
|
||||
|
||||
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; 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; 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. |
#2
|
||||
|
||||
¡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:
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. |
#3
|
||||
|
||||
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í: 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. |
#4
|
|||||||
|
|||||||
Hola .
Cita:
Costumbre. Suelo responder mucho más que preguntar (en otros foros, sobre otros temas) por eso he aprendido cómo preguntar :P. Cita:
Cita:
Gracias por responder, me ha sido útil. Cita:
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:
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:
Cita:
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! |
#5
|
||||
|
||||
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. |
#6
|
||||
|
||||
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:
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 |
#7
|
||||
|
||||
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. |
|
|
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 |
|