En este documento se describe como diseñar un formulario para que funcione como un formulario de instancia única siguiendo el patrón singleton
Estaba yo haciendo un programilla que utilizaba formularios hijos y que tenían que tener la condición de que la instancia del formulario tenia que se siempre la misma, y solo tarde un poco ( en realidad una tarde entera) en darme cuenta de que lo que necesitaba era aplicar el patrón singleton a los formularios y asunto resuelto, así que lo primero que hice fue conectarme a google para buscar la cadena formularios con el patrón singleton y encontré algunas referencia una de ellas publicada en el guille [Ver Camilo Andrés] Y aunque es un documento que me ha acarado algún concepto, la verdad es que, además de haber sido copiado en un montón de sitios, no me resolvio el problema, así que me repase el documento fundamental (la Biblia) sobre el patrón singleton [Ver Patrón Singleton] y a continuación me puse a escribir el código que presento en este documento.
Teniendo en cuenta que un formulario es una clase, me puse a aplicar los conocimientos descritos en el documento anterior, y el primer tropiezo que tengo es que un formulario es una ventana que se puede cerrar por programa y/o a través del IDE (pulsando la ‘X’ de la barra de titulo), es decir el primer problema que hay que solucionar es evitar que el formulario se cierre. Para ello utilizo el evento [formClosing] y así evito que se cierre el formulario a través del entorno grafico y sobrescribiendo la función [close] de esta forma evito que se cierre el formulario a través de código. Estas dos funciones lo que hacen es evitar que se cierre el formulario y luego ocultarlo visualmente es decir la propiedad [visible] del formulario se establece con el valor False.
Por otra parte, me puede interesar saber que se ha intentado cerrar el formulario, por eso disparo un evento [FormularioSingletonCerradoPorElUsuario] que me avisa de este hecho.
A la hora de definir el constructor salía un error. El problema es que faltaba la instrucción [InitializeComponent()] (Otra vez se me olvidaba que es un formulario, no una clase normal).
El problema se resolvió solo de la forma más tonta, cuando utilice el Entorno de Visual Estudio, que escribiera en el código la función New
La forma de hacerlo es situarse en la pestaña del código del ‘form’ que estamos definiendo, y a continuación, usar el combo superior derecho donde figuran los métodos de la clase y seleccionar el método New para que se añada a nuestro formulario.
Private Sub New() ' Llamada necesaria para el Diseñador de Windows Forms. InitializeComponent() ' Agregue cualquier inicialización después de la llamada a InitializeComponent(). _FechaCreacion = DateTime.Now.ToString End Sub
El método básico es el siguiente:
'------------------------------------ ' Modelo basico Public Shared Function GetInstance() As FormFicherosDialog If _ObjSingleton Is Nothing Then _ObjSingleton = New FormFicherosDialog End If Return _ObjSingleton End Function '------------------------------------
Pero… como estamos trabajando con un formulario ocurre un hecho curioso: si cerramos el formulario, el proceso [Dispose] se realiza, con lo cual, en teoría, se pierden todas las referencias del objeto, y es verdad, pero curiosamente el objeto NO toma el valor nothing, por lo que la pregunta que tenemos que hacer para detectar si el objeto esta creado o no hay que modificarla ligeramente de la siguiente forma:
'------------------------------------ ' Modelo basico para un formulario Public Shared Function GetInstance() As FormFicherosDialog If _ObjSingleton Is Nothing OrElse _ _ObjSingleton.IsDisposed = True Then _ObjSingleton = New FormFicherosDialog End If Return _ObjSingleton End Function '------------------------------------
Cuando termine la clase, y empecé a hacer las pruebas, me di cuenta que desde el código que utilizaba el formulario singleton me hacia falta saber si el formulario singleton tenia el valor nothing o no, por eso, después de tener todo terminado, Cree la fundón [IsNothing] que básicamente utiliza la interrogación anterior para devolver un valor booleano.
Por ultimo queda por hablar del código de la función [GetInstance], en realidad no voy a hacerlo porque en la referencia que te he dado, se habla largo y tendido sobre ese tema, solamente indicarte que el código esta perfectamente documentado y que no debes tener ningún problema para leerla:
' /** ' ------------------------------------------ ' CLASE PARCIAL ' ---------------------------------------------- ' Fichero ..: FormFicherosSingleton_PatronSingleton.vb ' Clase ....: Partial Class FormFicherosSingleton ' Lenguaje .: Visual Basic NET (Version 2005) ' Autor ....: Joaquin Medina Serrano ' Emilio ...: joaquin@medina.name '------------------------------------ ' SUMARIO ' Esta clase proporciona la hablidad singleton a un formulario '------------------------------------ ' OBSERVACIONES ' Este código es Birraware, ' Es decir si lo usas estas OBLIGADO a ' invitar a una birrita fresquita al creador del mismo ' (o sea soy yo el que tiene que beberse la cerveza), ' si no eres de Zaragoza (España), me conformo con un Emilio, ' tengo curiosidad por saber por donde circula, (la curiosidad me puede) '------------------------------------ ' Referencias Bibliográficas 'http://www.elguille.info/colabora/puntoNET/caja911_SingletonFormulariosMDI.htm 'http://www.willydev.net/descargas/prev/Patrones.Aspx 'http://www.microsoft.com/spanish/msdn/comunidad/mtj.net/voices/MTJ_4081/default.aspx ' ---------------------------------------------- ' MODIFICACIONES ' 13/Oct/2007 : creacion ' 12/Ene/2009 : Lectura Grabacion en Settings ' ---------------------------------------------- ' */ Partial Public Class FormFicherosSingleton ''' <summary> ''' El evento LOAD del formulario ''' </summary> Private Sub FormSingleton_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load ' cargar los valores de Settings Call CargaDatosDeSettings() End Sub #Region "Guardar / leer datos en Settings" '---------------------------------------------- ' Lee del Settings los datos Private Sub CargaDatosDeSettings() '--------------------------------- ' Datos del form para Settings ' FormSingleton_Left ' FormSingleton_Top ' FormSingleton_Width ' FormSingleton_Height '--------------------------------------- Try 'Cargar los datos de la configuraci�n If My.Settings.FormSingleton_Left > 0 Then Me.Left = My.Settings.FormSingleton_Left Me.Top = My.Settings.FormSingleton_Top If My.Settings.FormSingleton_Width > 0 AndAlso _ My.Settings.FormSingleton_Height > 0 Then Me.Width = My.Settings.FormSingleton_Width Me.Height = My.Settings.FormSingleton_Height End If End If Catch ex As System.Configuration.ConfigurationException ' se produce porque no se encuentra el archivo de configuracion ' no hacer nada My.Application.Log.WriteException(ex, TraceEventType.Warning, _ "Ocurrio durante el proceso de Lectura de datos de 'Settings' ") Catch ex As Exception Throw End Try ' / FIN leer los datos de la configuraci�n ' --------------------------------------------------- End Sub '---------------------------------------------------------- ' Escribe en Settings los datos Private Sub GuardaDatosEnSettings() ' --------------------------------------------------- Try ' Guardar datos en Settings en la configuraci�n ' Si el formulario no est� en modo normal ' guardar los datos de la posici�n que ten�a ' antes de minimizar o maximizar '--------------------------------------- If WindowState <> FormWindowState.Normal Then My.Settings.FormSingleton_Left = Me.RestoreBounds.Left My.Settings.FormSingleton_Top = Me.RestoreBounds.Top Else My.Settings.FormSingleton_Left = Me.Left My.Settings.FormSingleton_Top = Me.Top My.Settings.FormSingleton_Width = Me.Width My.Settings.FormSingleton_Height = Me.Height End If '--------------------------------- ' En VB no es necesario, pero por si alguien cambia ' la forma predeterminada de que se auto guarden los datos My.Settings.Save() Catch ex As System.Configuration.ConfigurationException ' se produce porque no se encuentra el archivo de configuracion ' no hacer nada My.Application.Log.WriteException(ex, TraceEventType.Warning, _ "Ocurrio durante el porceso de guardado de datos en 'Settings' ") Catch ex As Exception Throw End Try ' / FIN guardar los datos en la configuraci�n ' --------------------------------------------------- End Sub #End Region #Region "Patron Singleton" '==================================================== 'Variables '==================================================== '------------------------------------ 'Objeto que contendrá la instancia compartida de esta clase Private Shared _ObjSingleton As FormFicherosSingleton = Nothing '------------------------------------ ' fecha de crecion, solo para informacion (si es necesaria) Private _FechaCreacion As DateTime = Nothing '------------------------------------ 'El Mutex es una clase que proporciona .NET FrameWork 'Con ella evitaremos que en un ambiente multi thread 'se cree algún tipo de conflicto Private Shared _Mutex As System.Threading.Mutex = Nothing '==================================================== ' Evento '==================================================== '------------------------------------ ''' <summary> ''' <para> Evento que se dispara cuando el usuario 'cierra' este formulario</para> ''' <para> Por ejemplo pulsando el boton 'X' de la barra de titulo</para> ''' </summary> ''' <remarks> ''' <para>La idea es poder responder por programa a esta situación (si hace falta)</para> ''' <para>Se dispara al interceptar el evento FormClosing</para> ''' </remarks> Public Event FormularioSingletonCerradoPorElUsuario( _ ByVal sender As Object, ByVal ev As System.EventArgs) '==================================================== ' NEW '==================================================== '------------------------------------ ''' <summary> ''' Constructor privado ''' </summary> ''' <remarks> ''' Se inicializan las varibles internas con sus valores ''' </remarks> Private Sub New() ' Llamada necesaria para el Diseñador de Windows Forms. InitializeComponent() ' Agregue cualquier inicialización después de la llamada a InitializeComponent(). _FechaCreacion = DateTime.Now End Sub '==================================================== ' Funciones '==================================================== ''' <summary> ''' El objeto ¿Tiene un valor Nothing? Si/No ''' </summary> ''' <value> ''' </value> ''' <returns> ''' <para>Un valor lógico que responde a la pregunta: ''' El objeto singleton ¿Tiene un valor Nothing? ''' </para> ''' <para> TRUE, tiene el valor Nothing</para> ''' <para> FALSE, El objeto esta instanciado</para> ''' </returns> ''' <remarks> ''' Esta pregunta se realiza en varios sitios del código, ''' y he decidido separarla en una función independiente porque ''' además me ha resultado útil en el código que usaba esta formulario '''</remarks> Public Shared ReadOnly Property IsNothing() As Boolean Get Dim resultado As Boolean = False ' Si no existe o ' la funcion dsiose se ha ejecutado entonces ' TRUE, tiene el valor Nothing, ' en caso contrario ' FALSE, El objeto esta instanciado ' fin si If _ObjSingleton Is Nothing OrElse _ _ObjSingleton.IsDisposed = True Then resultado = True End If Return resultado End Get '------------------------------------ End Property '------------------------------------ ' método estático ' controla que sólo exista una instancia del formulario. ' permite obtener la referencia de la clase ' siempre se devuelve la misma referencia '------------------------------------ ' Modelo basico de esta funcion 'Public Shared Function GetInstance() As FormFicherosDialog ' If _ObjSingleton Is Nothing Then ' _ObjSingleton = New FormFicherosDialog ' End If ' Return _ObjSingleton 'End Function '------------------------------------ ''' <summary> ''' Este será el método que proporcionara el acceso ''' a la instancia existente ''' </summary> ''' <returns> ''' <para>Devuelve la instancia de la clase</para> ''' <para>Si no existe, creara una nueva (método Singleton).</para> ''' </returns> ''' <exception cref='system.ObjectDisposedException'> ''' La instancia actual ya se ha eliminado. ''' </exception> ''' <exception cref='System.Threading.AbandonedMutexException'> ''' <para> La espera finalizó porque un subproceso se cerró ''' sin liberar una exclusión mutua. </para> ''' <para> Esta excepción no se genera en Windows 98 ni en Windows Millennium.</para> ''' </exception> ''' <exception cref='System.ApplicationException'> ''' El subproceso que realiza la llamada no posee la exclusión mutua. ''' </exception> ''' <remarks> ''' <para>Si no existe, creara una nueva (método Singleton).</para> ''' <para>Utilizo Mutex en lugar de Monitor, proque registra y ''' controla automaticamente varias llamadas sin necesidad de ''' crear multiples objetos ''' </para> ''' </remarks> Public Shared ReadOnly Property GetInstance() As FormFicherosSingleton Get Try '---------------------------------------------------------------- ' Si no existe la instancia de esta clase crearla ' observa que utilizo dos veces esta pregunta ' la razón es que bloquear consume muchos recursos, de forma ' que antes de boquear me aseguro de que es nothing. ' y una vez bloqueado antes de crear la instancia me vuelvo a ' asegurar de que no esta creada por algún otro proceso. '---------------------------------------------------------------- ' Cuando se cierra un formulario desde programa utilizando el ' método close o desde el entorno grafico, pulsando la "X" ' de la barra de titulo, se ejecuta el método dispose del ' formulario y aunque parezca extraño, si en este momento ' preguntamos si el formulario es nothing obtendremos el ' resultado false. ' Por esta razón, antes de hacer un NEW formulario, tengo que ' preguntar si el objeto es NOTHING y si se ha ejecutado o ' no el método dispose del formulario '---------------------------------------------------------------- If IsNothing = True Then If _Mutex Is Nothing Then _Mutex = New System.Threading.Mutex End If ' bloquea el subproceso actual _Mutex.WaitOne() ' si no existe (todavia) la instancia de esta clase crearla If IsNothing = True Then _ObjSingleton = New FormFicherosSingleton End If ' desloquear el subproceso _Mutex.ReleaseMutex() ' cargarme la clase mutex que ya no la voy a volver a emplear If Not (_Mutex Is Nothing) Then _Mutex = Nothing End If End If '----------------------------------------------------------- ' devolver la instancia de este objeto ' despues del End Try '----------------------------------------------------------- 'EXCEPCIONES Catch ex As System.ObjectDisposedException Throw New System.ObjectDisposedException( _ "La instancia actual ya se ha eliminado.", ex) Catch ex As System.Threading.AbandonedMutexException Throw New System.Threading.AbandonedMutexException( _ "La espera finalizó porque un subproceso se cerró sin " _ & "liberar una exclusión mutua.", ex) Catch ex As System.ApplicationException Throw New System.ApplicationException( _ "El subproceso que realiza la llamada no posee la exclusión mutua.", ex) Catch ex As Exception Using SW As New System.IO.StringWriter SW.WriteLine("ERROR EN: " & _ ">" & System.Reflection.MethodBase.GetCurrentMethod.Module.Name & _ ">" & System.Reflection.MethodBase.GetCurrentMethod.DeclaringType.Name & _ ">" & System.Reflection.MethodBase.GetCurrentMethod.Name) SW.WriteLine("FECHA (UTC) : " & Date.Now.ToUniversalTime.ToString) SW.WriteLine("FECHA (Local): " & Date.Now.ToLongDateString & _ " a las " & Date.Now.ToLongTimeString & " horas") SW.WriteLine("DESCRIPCION : ") SW.WriteLine("Ha ocurrrido un error al crear el objeto singleton" _ & System.Reflection.MethodBase.GetCurrentMethod.DeclaringType.Name) SW.WriteLine("MENSAJE DEL SISTEMA: ") SW.WriteLine(ex.Message) '----------------------------Fin del mensaje ' ESCRIBIR EL LOG My.Application.Log.WriteEntry(SW.ToString, TraceEventType.Error) ' DISPARAR EL ERROR Throw New Exception(SW.ToString, ex) End Using End Try '------------------------------------- ' Poner el form en primer termino ' Coloca el control al principio del orden Z. _ObjSingleton.BringToFront() ' Devolver la instancia de este objeto Return _ObjSingleton End Get End Property '------------------------------------ ''' <summary> ''' Propiedad que presenta la fecha y hora en que fue creada la instancia ''' Únicamente a efectos informativos y de control (si hace falta) ''' </summary> Public ReadOnly Property FechaCreacion() As String Get Return _FechaCreacion.ToString End Get End Property '------------------------------------ ''' <summary> ''' Este método eliminará la instancia ya existente ''' </summary> ''' <remarks></remarks> Public Shared Sub KillInstance() Try '----------- ' para evitar el error de referencias nulas If Not (_Mutex Is Nothing) Then _Mutex = Nothing End If '----------- ' la instancia shared del formulario (el objeto singleton) If Not (_ObjSingleton Is Nothing) Then ' Comprobar si se puede llamar al dispose del objeto If TypeOf _ObjSingleton Is IDisposable Then _ObjSingleton.Dispose() End If ' objeto a null _ObjSingleton = Nothing End If '------------ Catch ex As Exception Throw ex End Try End Sub '----------------------------------------------------------------- ' Evento FormClosing '-------------------- ' Al ser un formulario singleton hay que evitar que el objeto se cierre ' aunque el usuario pulse la 'X' de la barra de titulo ' o desde el programa con un close '----------------------------------------------------------------- ' Aqui se evita que el usuario pulse la 'X' de la barra de titulo '----------------------------------------------------------------- Private Sub FormFicherosDialog_FormClosing( _ ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) _ Handles Me.FormClosing ' ocultar el form haciendolo invisible Me.Visible = False If e.CloseReason = CloseReason.UserClosing Then ' Disparar el evento que indica que el usuario' ' ha 'intentado' cerrar el formulario RaiseEvent FormularioSingletonCerradoPorElUsuario( _ _ObjSingleton, New System.EventArgs) ' Imperdir que se cierre el form e.Cancel = True End If End Sub '----------------------------------------------------------------- ' Sub Close() '-------------------- ' Al ser un formulario singleton hay que evitar que el objeto se cierre ' aunque el usuario pulse la 'X' de la barra de titulo ' o desde el programa con un form.close '----------------------------------------------------------------- ' Aqui se evita que el form se cierre por programa usando un form.close '----------------------------------------------------------------- ''' <summary> ''' Cierra el formulario. ''' </summary> ''' <remarks> ''' <para> ''' Cuando se cierra un formulario, todos los recursos creados con el objeto ''' se cierran y se elimina el formulario. Se puede impedir el cierre de un ''' formulario en tiempo de ejecución controlando el evento Closing y ''' estableciendo la propiedad Cancel del CancelEventArgs que se pasa como ''' parámetro al controlador de eventos. Si el formulario que se va a cerrar ''' es el formulario de inicio de la aplicación, ésta se cierra. ''' </para> ''' <para> ''' La condición para que no se deseche un formulario cuando se llama a Close ''' es cuando forma parte de una aplicación MDI (interfaz de múltiples documentos) ''' y el formulario no es visible. En este caso, tendrá que llamar a Dispose ''' manualmente para marcar todos los controles del formulario para la ''' recolección de elementos no utilizados. ''' </para> ''' <para>Nota </para> ''' <para>Cuando se llama al método Close en un Form que se muestra como ''' ventana no modal, no se puede llamar al método Show para que el ''' formulario esté visible, porque ya se han liberado los recursos del ''' formulario. Para ocultar un formulario y volver a hacerlo visible, ''' use el método Control.Hide.</para> ''' <para>Precaución </para> ''' <para>No llame a Do not call M:System.Windows.Forms.Form.Close desde ''' de un controlador de eventos para los eventos Load o Activated. Ambas ''' condiciones pueden producir pérdidas de memoria. Para probar si debería ''' cerrar un formulario poco después de haberlo cargado, utilice en cambio ''' el evento Idle.</para> ''' <para>Precaución </para> ''' <para>No se provocan los eventos Form.Closed y Form.Closing cuando se ''' llama al método Application.Exit para salir de la aplicación. Si se ''' dispone de código de validación en cualquiera de estos eventos que ''' deben ejecutarse, hay que llamar al método Form.Close por cada formulario ''' abierto de forma individual antes de llamar al método Exit</para> ''' </remarks> Public Overloads Sub Close() ' ----------------------------------------------------------- ' Proceso de guardar datos en Settings Call GuardaDatosEnSettings() ' ----------------------------------------------------------- 'Evitar problemas de referencias nulas If FormVerEstructuraDialog.IsNothing = False Then '------------ ' Ocultar el form haciendolo invisible _ObjSingleton.Visible = False '------------ ' Disparar el evento que indica que el usuario ha 'intentado' cerrar el formulario RaiseEvent FormularioSingletonCerradoPorElUsuario(_ObjSingleton, New System.EventArgs) End If End Sub #End Region End Class
#Region "Cosumidor - Formulario singleton FormFicherosSingleton" '---------------------------------------------------- ' este código hay que copiarlo en el Form consumidor ' en el objeto que utilice el formulario singleton '-------------------------------------------------- '---------------------------- ' Definir el formulario Singleton como objeto 'contenido' ' Con la clausula WithEvents para interceptar eventos Dim WithEvents _ObjFormFicheros As FormFicherosSingleton '---------------------------- ' Mostrar el formulario ' En este caso es un form MDI y el form singleton es un form hijo Private Sub Accion_MostarFormFicherosSingleton() ' Obtener la instancia _ObjFormFicheros = FormFicherosSingleton.GetInstance ' Declarar que es un formulario hijo _ObjFormFicheros.MdiParent = Me ' Mostrar el formulario _ObjFormFicheros.Show() End Sub '---------------------------- ' Destruir el formulario singleton Private Sub Accion_KillInstanceFormFicherosSingleton() FormFicherosSingleton.KillInstance() End Sub '---------------------------- ' EVENTO ' interceptar el evento de formulario cerrado Private Sub _ObjFormFicheros_FormularioSingletonCerradoPorElUsuario( _ ByVal sender As Object, ByVal ev As System.EventArgs) _ Handles _ObjFormFicheros.FormularioSingletonCerradoPorElUsuario '------------------------------------------------------- ' Aquí puedes poner algún código que haga algo ' yo he puesto un mensaje [me ha parecido algo original :-)] ' si no lo vas a usar comenta el mensaje y fin del problema MessageBox.Show("FormularioSingletonCerradoPorElUsuario") End Sub #End Region
Supongamos un menú que muestra el formulario singleton
' Utilizar una opcion de menu para mostrar un formulario Private Sub FicherosToolStripMenuItem_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles FicherosToolStripMenuItem.Click ' mostrar el formulario singleton Call Accion_MostarFormFicherosSingleton() End Sub
Supongamos un botón que 'cierra' el formulario singleton, observa que usa la palabra reservada 'Me' porque la función close esta sobreecrita y todo funciona de manera 'natural'
Private Sub TerminarToolStripMenuItem_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles TerminarToolStripMenuItem.Click Me.Close() End Sub
Supongamos una opción de un menú que quiere 'destruir' el formulario singleton, es decir, la proxima vez que se le llame sera un formulario 'diferente'
Private Sub FormFicherosNothingToolStripMenuItem_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles FormFicherosNothingToolStripMenuItem.Click Call Accion_KillInstanceFormFicherosSingleton() End Sub
© 1.997- 2.009 - La Güeb de Joaquín | |||||
Joaquin Medina Serrano
|
|||||
|