domingo, 17 de julio de 2011

[Winforms] Singleton - Pasar datos entre formularios

 

Introducción


En mas de una oportunidad surgió el hecho de comunicar formulario y pasar información entre los mismos, cuando estos se invocan uno a otra la solución es relativamente simple, pero que sucede cuando las distintas invocaciones deben capturar y conservar los datos en cada paso que realizan.

Es aquí donde entra en juego un patrón bien simple de aplicar pero no por eso menos potente, hago referencia al singleton.

Para el presente artículo platearemos un escenario nada complicado, simplemente algunos forms donde se capturan datos números, pero esta información deberá ser usada en la ultima pantalla, la cual realiza el calculo con los datos recolectados en los pasos previos.

image

 

1- Singleton,  Pasar datos entre formularios


Implementar el patrón es muy simple, solo se deben aplicar ciertas reglas que abrigan a tomar la misma instancia.

public class RecolectarDatos
{
    private static RecolectarDatos datos;

    private RecolectarDatos()
    {
    }

    public static RecolectarDatos Instance()
    {
        if (datos == null)
        {
            datos = new RecolectarDatos();
        }

        return datos;
    }

    public int Dato1 { get; set; }
    public int Dato2 { get; set; }
    public int Dato3 { get; set; }

    public int Dato4 { get; set; }
    public int Dato5 { get; set; }
}

El primer punto es definir una instancia static del propia clase singleton (línea 3)

Segundo, definir el constructor de la clase como privado (línea 5), de esta forma no se podrán crear instancia de la clase, salvo desde dentro de la propia clase.

Tercero, definir un método o propiedad static, la cual será el punto central por donde se podrá recuperar una instancia de la clase (línea 9), en este punto es donde se valida y crea la instancia de la variable static definida mas arriba, es por esto que siempre se retorna esta misma instancia.

El resto del código pueden ser método, propiedades, otras instancias de clases, o sea se puede aplicar cualquier concepto de POO sin problemas, porque el patron singleton devuelve una única instancia, pero esta tiene comportamiento como cualquier otro objeto.

 

2 – Eventos, Informar de cambios entre formulario


Al llegar al ultimo formulario era necesario informar al primero que las operaciones se realizaron correctamente, pero entre estos formulario no hay visibilidad alguna, no se aplico ninguna pasamano de instancias entre los formularios, o algo que los comunique.

Es por eso que la subscripción a un evento genérico resulta ideal para avisar de una determinada acción sin ser necesario pasar instancias.

 

public static class CompleteEvents
{
    public delegate void CompleteHandler(CompleteEventArgs args);
    public static event CompleteHandler Complete;

    public static void RaiseEvent(int calculo)
    {
        if(Complete != null)
            Complete(new CompleteEventArgs(calculo));
    }
}

public class CompleteEventArgs : EventArgs
{
    public CompleteEventArgs(int calculo)
    {
        this.Calculo = calculo;
    }

    public int Calculo { get; set; }
}

El formulario principal se adjunta al evento definido en la clase, mientras que en el ultimo formulario es invocado el método RaiseEvent() que lanzara la acción.

Adicionalmente se define un argumento del evento para informar el resultado del calculo, igualmente en este caso por aplicar singleton, quizás no era necesario, ya que se podría haber tomado directamente los valores de cada dato recolectado y realizar nuevamente el calculo.

 

Código del ejemplo


[C#] 
[VB.NET] 

30 comentarios:

  1. muy buen aporte leandro,lo de patrones siempre es un tema interesante, .No sabia como usar una una clase como parametro del delegado pero este ejemplo es ilustratvo, y me gusto tener el evento, el delegado y el raise event dentro de una clase estatica (en CompleteEvents )
    la clase CompleteEventArgs porque hereda de EventArgs porque es para datos ?
    public class CompleteEventArgs : EventArgs

    Se puede hacer una clase similar , si tengo un metodo de esta forma btnATodas_Command(object sender, CommandEventArgs e)

    ResponderEliminar
  2. hola

    hereda de EventArgs porque esa clase se usara en la firma del evento que se define


    con respecto al btnATodas_Command, este evento ya define una forma en sus parametros, porque alli usa CommandEventArgs, alli simplemente lo usas porque ya existe, el otro lo habai creado porque es un evento que se define completo, no es existente


    saludos

    ResponderEliminar
  3. gracias leandro. Esta muy bueno tu blog tiene notas bastantes interesantes

    ResponderEliminar
  4. HEY LEANDRO como stas espero que todo marche de maravilla, yo aqui con un asunto que no me da descanzo, resulta que mis crystal en la pc de desarrollo funciona y al intalar la aplicacion en otra me pide id y passwrod como soluciono eso de una vez por todas trabajo en vb.net 2010 y sql server 2008 express ayudame porfavor estoy por cerrar mi codigo y aun no puedo por eso..gracias de antemano Leandro

    ResponderEliminar
  5. hola MINDCORRUPTER

    el reporte que has creado lo estas conectando directamente a la base de datos o usas un dataset tipado ?

    porque si usas un dataset tipado quizas se deba a que anteriormente habias conectado el reporte pero luego cambiaste al este dataset, esto hace que crystal aun se haya quedado con los datos de la conexion por eso los solicita

    valida las conexiones que puediera tener el reporte y si estas asignando de forma correcta el dataset como origen de datos

    saludos

    ResponderEliminar
  6. hola leandro gracias por todos tus aportes tenia una consulta:

    como puedo hacer para un datatable usarlo en toda la aplicacion aplicando singleton no se mucho de singleton pero es lo que necesito

    .... mi profesor me dijo de que no puedo crear un datatable cada vez que yo quiera por que tendria muchas copias en memoria y me aconsejo usar singleton me podrias ayudar estaria en deuda contigo

    ResponderEliminar
  7. hola francisco

    por lo del consumo de memoria depende, si creas el datatable en el ambito correcto dodne lo usas no deberia ahber problema, o sea si creas datatable a nivel del formulario y haces esto en varios de seguro esto consumira memoria teniendo replcias de los mismos datos

    pero si los creas solo en el metodo dodne lo usas y solo alli, eso no genera consumo de memoria, todo esta en definir el ambito en que lo creras, siempre deberia ser el menor posible

    podrias usar este concepto si se trata de info global que suaras en varios lados, la tecnica es la misma dentro del singleton define una variable con el datatable

    aunque la verdad recomendaria que no uses datatable porque no defines una List<> de una clase con propiedades

    saludos

    ResponderEliminar
  8. es que el datatable que ocupare lo necesitan varias clases para comparar datos.... gracias ya pude resolverlo con singleton..

    si me ayudaras con otra duda...

    tengo una aplicacion web de productos de la BD northwind

    dos campos

    idproducto
    cantidad


    simplemente los lleno y eso llama a una funcion llamada

    realizarventa(idproducto,cantidad)

    esta realiza la coneccion y el command y todo el rollo para guardar ..... tengo otra aplicacion de escritorio que pide lo mismo

    idproducto
    cantidad

    para guardar eso llamo al servico web y a la funcion realizarventa(idproducto,cantidad)

    y le paso los campos

    y tengo un datagrid donde tengo toda la tabla products

    la pregunta es... como puedo hacer para que si hago una venta en la aplicacion web se actualizen los datos del datagrid teniendo la aplicacion abierta claro


    perdon si no le entiendes a lo que escribo de antemano gracias

    ResponderEliminar
  9. hola francisco

    vas a tener que investigar sobre SignalR

    http://social.msdn.microsoft.com/Forums/es-ES/netfxwebes/thread/11e35154-cea7-41d4-b414-813e5e52d2dd

    este permite desde el server notificar a los clientes para que actualicen

    eso si necesitas .net 4 para que funcione

    saludos

    ResponderEliminar
  10. Me recomiendas usar un object pooling de datatables para mi aplicacion implementando singleton???
    gracias.

    ResponderEliminar
  11. hola francisco

    hasta donde se no existe ningun pooling en los datatable, existe ese concepto en ado.net para administrar las conexiones a la db, pero esto no tene nada que ver con singletos o el objetivo que aqui se plantea en la comunicacion de formularios

    saludos

    ResponderEliminar
  12. hola d nuevo. primero que nada debo felicitarte por cada una de tus entradas que son excelentes, quisiera pedirte ayuda pues tengo un form padre y varios hijos a los que accedo por medio de un treeview y un panel y los form hijos se muestran en dicho panel contenido en el form padre, el problema que tengo es que solo tengo un botón de guardar nada mas y esta localizado en el padre pero no se como guardar los datos introducidos en las cajas de texto de los form hijos en el padre.

    ResponderEliminar
  13. hola

    podrias desde el form padre enviar una accion al forma hijo
    si defines una interfaz que especifique el nombre, podrias invocar cualqueir accion en el form hijo, siempre y cuando tengas la instancia del mismo

    podrias aplicar una logica como esta

    [WinForms] Realizar Acciones en formularios hijo

    saludos

    ResponderEliminar
  14. Hola Leandro esta muy educativo tu ejemplo gracias, pero si yo quiero usar string no me funciona el tryparse cual debo usar?. Gracias

    ResponderEliminar
  15. hola Richard

    no entendi, que es lo que intentas parsear ?

    saludos

    ResponderEliminar
  16. ok te explico veo que la mayoria te ha consultado como obtener y guardar la informacion adicional del usuario logueado, bueno yo estoy desarrollando en capas (basado en tus ejemplos) y todo funciona bien, no se como lo siguiente: quiero hacer un query(store procedure) con inner join para tener el usuario, password, codEmpleado, NombreEmpleado. Pero como estoy usando entidades no se como hacerlo esto en mi capa de acceso, y despues guardar la informacion en la Clase ObtenerDatos() ya que tu ejemplo de siglenton() usas integer y yo tengo tipo string y el tryparse me da error con strig

    ResponderEliminar
  17. hola Richard

    en tu capa de datos podrias hacer como explico aqui

    [WinForms] Edición Empleados

    para cargar una entidad

    saludos

    ResponderEliminar
  18. Hola Leandro disculpa necesito tu ayuda, sigo intentando guardar en mi clas RecolectarDatos.vb pero no lo condigo. En dicha clase tengo 3 datos de los cuales 1 es string, bien en mi formulario Login tengo esto: a nivel de formulario private usuario as usuariosEntity = Nothing
    private Intentos as integer = 0

    en el boton iniciar:
    '
    usuario = UsuarioBOL.GetByUsuariosId(TxtUsuario.Text)

    Dim codrol As Integer = Convert.ToInt32(usuario.cod_rol)
    Dim codempleado As Integer = Convert.ToInt32(usuario.cod_empleado)
    Dim users As String = usuario.cod_usuario

    Dim datos As RecolectarDatos = RecolectarDatos.Instance()

    Dim temp As Integer = 0



    I If StrConv(users, temp) Then
    datos.Dato1 = temp
    End If

    If Integer.TryParse(codrol, temp) Then
    datos.Dato2 = temp
    End If

    If Integer.TryParse(codempleado, temp) Then
    datos.Dato3 = temp
    End If

    Pero me sale un error: La conversion de la cadena "admin" en el tipo Boolean no es valida.

    No se que hacer

    ResponderEliminar
  19. hola

    estas seguro que en cod_rol devuelves un valor numerico en lugar de la descripcion "admin" ?

    si pones un breakpoint en el codigo y pasas el mouse por sobre las propiedades para evaluar que contenido presentan cuando conviertes en int asignas un valor numerico

    saludos

    ResponderEliminar
  20. ya lo revise y es un string aqui esta el codigo en la capa de acceso:
    Public Shared Function GetUsuarioById(CodUsuario As String) As Usuarios
    Dim usuario As Usuarios = Nothing
    Using conn As New SqlConnection(ConfigurationManager.ConnectionStrings("default").ToString())
    conn.Open()

    Dim cmd As New SqlCommand("SP_Listar_Usuarios", conn)
    cmd.CommandType = CommandType.StoredProcedure
    cmd.Parameters.AddWithValue("@criterio", CodUsuario)

    Dim reader As SqlDataReader = cmd.ExecuteReader()

    If reader.Read() Then
    usuario = LoadComboUsuarios(reader, True)
    End If
    End Using
    Return usuario
    End Function

    ResponderEliminar
  21. hola Richard

    si es un string ahi esta le problema no debe serlo, debes asegurarte que el campo devuelve un valor numerico

    si es texto alfanumerico este no se puede convertir a bool, el campo deberia ser del tipo "bit" en la base de datos

    saludos

    ResponderEliminar
  22. ¿Cómo pasar un valor de un WinForm a una página HTM?

    Hola, necesito pasar el texto de un Textbox de un formulario windows a un textbox de una página HTM dentro de mi proyecto... cómo lo hago por favor?

    Muchas gracias,

    Paris

    ResponderEliminar
  23. hola Paris

    pero la pagina html la cargas donde ?

    si usas un control WebBrowser dentro del form podrias navegasd la pagina y luego mediante la funcionalidad del control acceder al html he interactuar con el mismo

    pero no se si es que haces esto o como seria el contexto del problema

    saludos

    ResponderEliminar
  24. Yo seteo las variable en el segundo formulario , luego cierro con close, pero el primer formulario esta abierto, en que momento le asigno el valor, osea en que evento del primer formulario utilizo las variables.

    ResponderEliminar
  25. hola Luis

    es que si cierras el form sin nada mas es logico que no suceda nada
    debes informar al form1 que el otro form se cerro

    para esto usas eventos, si el form1 es quien lanza el form2 podrias usar el evento Closing para detectar el cierre

    o sea desde el form1 usarias

    Form2 frm2 = new Form2();
    frm2.FormClosing += from2_Formclosing;
    frm2.Show();

    de esta forma definiendo el evento desde el form1 puede controlar el cierre

    saludos

    ResponderEliminar
  26. Hola excelente post, solo me queda una duda, en la parte de
    public int Dato1 { get; set; }

    obvimanete adquiere el valor que se se escribió en el form, pero qué pasaría si hay un form extra donde necesita que las variables no valgan nada ?
    ¿cómo se haría un método universal que limpie todas las variables ?
    En tu ejemplo nada mas son pocas, pero si fueran muchísimas?

    Gracias por la respuesta, saludos

    ResponderEliminar
  27. Hola gracias por el ejemplo pero tengo una duda me podrías ayudar, por ejemplo:
    doy clic en comenzar e ingreso los 3 primeros dígitos y confirmo.
    en la siguiente pantalla ingreso los 2 números restantes.
    pero si doy clic en comenzar de nuevo e ingreso los 3 números se sobrescriben los que ya avía puesto hay alguna manera de que esto no suceda para iniciar varias veces la aplicación y realizar distintas operaciones sin que los datos se sobrescriban.
    Gracias de antemano

    ResponderEliminar
  28. hola kymcha

    podrias utilizar una base de datos o quizas tener una lista que serialices a xml para poder ir agregando registros o items para ir conservandolos y que no se pisen con los nuevos

    si solo mantienes un unico item es logico que solo podras pisar ese, pero si vas agregando sobre una lista podrias trabajar con la misma en caso de querer acceder a la historia de datos ingresados

    saludos

    ResponderEliminar
  29. Super como siempre Leadro, muchas gracias por la aportación

    ResponderEliminar