none
Usar controles desde eventos RRS feed

  • Pregunta

  • Buenos días. Soy nuevo en .NET. Mi consulta, que no he podido resolver buscando en las distintas ayudas, es la siguiente:

    En una prueba, tengo dos formularios: Form2, donde tengo la inicialización de un puerto serie, y su controlador de evento Recepción, y Form1 con diversas rutinas, entre ellas la llamada a inicializar() en el Form1_Load.  Por ahora es sólo una prueba muy simple.

    El problema surge cuando, dentro del procedimiento de control del evento recepción serie en Form2, intento acceder a un Label (por ejemplo) situado en el Form1. No tengo error, pero el Text del Label no se modifica. Sin embargo, esto funciona si el Label del Form1 se modifica desde inicializar() del Form2, que no es el controlador de eventos.

    Mi idea es llamar, dentro del controlador del evento de recepción, a un procediminto en otro formulario (o módulo) que ejecute diversas tareas (modificar Labels, etc). Pero se me complica....

    Muchas gracias!! 

    jueves, 19 de enero de 2017 13:17

Respuestas

Todas las respuestas

  • [...] dentro del procedimiento de control del evento recepción serie en Form2, intento acceder a un Label (por ejemplo) situado en el Form1 [...]

    Ojo, aqui hay un posible error de concepto. No puedes acceder a un Label situado en el Form1. Siempre tiene que ser un Label situado en UNA INSTANCIA del Form1. Asegurate de que la instancia que estas usando es la misma que se muestra en pantalla. Si haces un "new Form1" e intentas acceder al Label que hay en este new Form1, no se ve nada porque se trata de una nueva instancia que no se ve en pantalla porque no se le ha hecho el ".Show()" (y si le haces .Show() se vera una segunda ventana del tipo Form1 en la pantalla y se cambiara el Label en esta segunda ventana, no en la primera que abriste).

    Otra pega mas con la que te puedes encontrar es que el SerialPort realiza la recepcion de datos sobre un hilo de ejecucion separado. Esto implica que el evento de recepcion se dispara dentro de ese otro hilo, que no es el mismo hilo que usan los controles que hay en pantalla. En Windows Forms es ilegal manipular el estado de los controles desde un hilo distinto del que los creo, porque no estan preparados para funcionamiento en multihilo. Esto implica que si desde el evento de recepcion del SerialPort quieres manipular un Label (con independencia de que se encuentre en el mismo form o en otro), es necesario transportar la ejecucion entre hilos. Esto puede hacerse mediante una llamada al metodo Invoke de cualquier control.

    jueves, 19 de enero de 2017 13:39
  • Gracias Alberto. Estimo que el problema está en el segundo razonamiento. Esto no lo encontré por ningún lado. Y parece que cualquier procedimiento invocado desde el event handler de recepción se ejecuta sobre ese hilo separado. 

    Me gustaría saber en qué otros casos los eventos se ejecutan en un hilo separado. Haré alguna prueba con Invoke.

    De nuevo, gracias!

    jueves, 19 de enero de 2017 13:59
  • Hola de nuevo. Creo que, en mi caso, la mejor solución es usar un semáforo para indicar que se recibieron el número de caracteres esperados. Hay alguna forma de producir un evento cuando ese semáforo booleano cambie de False a True? Y que ese evento esté en el hiolo principal? Porque la otra opción es usar un Timer que chequee ese semáforo periódicamente.

    Gracias anticipadas!

     
    jueves, 19 de enero de 2017 18:57
  • Podrías construir el semáforo con una Propiedad booleana, y usar el Getter y el Setter de la propiedad para consultarlo y cambiarlo. En la clase que contiene esa propiedad puedes definir un evento público, y en el Setter disparar el evento, con lo que en resumidas cuentas tendrías un semáforo que dispara un evento.

    Pero ojo, el evento se disparará en el hilo de quien cambie el semáforo llamando al Setter. Si lo quieres disparar en el hilo principal, tendrás que meter el código de cambio de hilo dentro del propio semáforo. Con lo que al final no te libras de programarlo, simplemente logras un nivel de indirección encapsulando esa funcionalidad dentro del semáforo, pero al final tienes que programarla de todas maneras.

    Si te decides por la opción de usar el Timer para chequearlo periódicamente, recuerda que estás en multihilo y que esa variable estaría compartida entre los dos hilos, así que tienes que tomar las precauciones habituales que se aplican siempre que compartes un recurso en multihilo, es decir, pones un bloqueo (lock en C# o Locking en VB) al leer o grabar la variable. O usar un semáforo "de verdad", como por ejemplo la clase Threading.Mutex, que ya implementa el bloqueo internamente (y no, el Mutex no dispara ningún evento).


    Editado: Ah, y si usas un Timer, asegúrate de que es un System.Windows.Forms.Timer, que dispara su evento en el hilo de los Controles. Si usas un System.Timers.Timer o un System.Threading.Tmer, vuelves a tener el mismo problema porque estos últimos disparan su Tick en otro hilo.
    jueves, 19 de enero de 2017 19:40
  • Está claro Alberto. última pregunta: sabes de algún link donde se expliquen los temas relacionados con Visal Basic NET y los eventos de hardware? Normalmente, mi trabajo consiste en diseño de equipos con microcontroladores que interactúan con PCs. Hasta ahora me defendía con VB 6, pero los tiempos cambian... Y no he encontrado (evarios libros en los que busqué) el detalle que me comentaste amablemente. Nuevamente, muchas gracias.
    jueves, 19 de enero de 2017 23:42
  • Pues no, sobre eventos de hardware nunca he visto nada relacionado con VB.NET. Pero en cambio, una vez que sabes que el evento se dispara en otro hilo, sobre la programación en multihilo sí que hay bastante literatura. La mayor parte de los ejemplos suelen estar en C#, pero aunque la sintaxis del lenguaje sea distinta de la de VB, las recomendaciones que se hacen, y las librerías y clases que se emplean, son válidas en ambos casos. Por ejemplo, puedes mirar estos enlaces:

    http://visualbasic.about.com/od/usingvbnet/a/threadingevents.htm

    https://msdn.microsoft.com/en-us/library/aa289496(v=vs.71).aspx

    • Marcado como respuesta Daniel_Tango viernes, 20 de enero de 2017 14:45
    • Desmarcado como respuesta Daniel_Tango viernes, 20 de enero de 2017 14:46
    • Marcado como respuesta Daniel_Tango viernes, 20 de enero de 2017 14:46
    viernes, 20 de enero de 2017 7:53
  • Alberto, te comento que encontré ésto:

    http://www.me.umn.edu/courses/me2011/smartprodcourse/technotes/docs/serial-port-vb.pdf

    que tiene un ejemplo del método Invoke, que probé y es lo que estaba buscando.

    Nuevamente, muchas gracias!!


    viernes, 20 de enero de 2017 14:47
  • Alberto, te molesto porque tengo un problema:

    Usando Invoke (que me resultó muy interesante!) puedo llamar desde el evento DataReceived a cualquier otro procedimiento, aun en otros formularios. Pero desde este procedimiento invocado, no puedo influir sobre controles en el propio formulario. Así defino el delegado en la clase  Serie1

        Delegate Sub recibe1(datos() As Byte)
        Dim recibe As New recibe1(AddressOf destino.ProcesaRecepcion)  

    y ProcesaRecepcion está en otro formulario (destino, que es una propiedad de Serie1 y que la asigno después de crear la instancia).

    En ProcesaRecepción puedo hacer operaciones con datos() y llamar a Transmision() que está en la clase Serie1, pero no puedo hacer, por ejemplo Me.Text="hola" o cualquier otra operación sobre los controles de ese formulario.

    Agradecido de antemano

    Daniel

    jueves, 2 de febrero de 2017 19:59
  • No tiene nada que ver con el hecho de que el método esté en un formulario o en otro, es un problema de hilos. Ya sabemos que la interfaz de usuario es monohilo y no se puede llamar desde el evento de recepción de datos, que rueda en otro hilo distinto. El hilo de la interfaz de usuario es el mismo para todos los formularios. Así que para cambiar algo de la pantalla desde el evento de recepción de datos (o desde cualquier rutina llamada desde la recepción de datos, con independencia de la clase en que se ubique, tanto si la clase es un formulario como si no), tienes que pasar primero por el Invoke para saltar al hilo de la pantalla.
    jueves, 2 de febrero de 2017 20:17
  • Es que PASO POR EL INVOKE. Desde la clase Serie1, llamo en el evento DataReceived al método ProcesaRecepcion con Invoke. Pero es como si este último permaneciese en el hilo del evento. 

    Supongo que el problema es que desde frm_Main hago Dim miSerie as New Serie1, y luego le paso las propiedades. Entre ellas, el destino = Me.

    En Serie1

    Class Serie1

    Delegate Sub recibe1(datos() As Byte)
    Dim recibe As New recibe1(AddressOf destino.ProcesaRecepcion)

    Public Property destino as Object

    ......................................................... 

        Private Sub Recepcion(sender As Object, e As SerialDataReceivedEventArgs)
            Dim sp As SerialPort = CType(sender, SerialPort)
            Dim rx_bytes() As Byte
            ReDim rx_bytes(RXBytes - 1)
            sp.Read(rx_bytes, 0, RXBytes)
            sp.DiscardInBuffer()
            recibe.Invoke(rx_bytes) 'en frm_Main
        End Sub

    End Class

    Y en ProcesaRecepcion tengo el problema que comenté.

    Puede ser que sea el orden en que hago las cosas.

    Voy a probar poniendo todo en la misma clase (frm_Main)...



    jueves, 2 de febrero de 2017 21:11
  • Me pierdo un poco siguiendo "sobre el papel" lo que has hecho. Lo mejor es que lo sigas con el debugger paso a paso. Si pones un punto de interrupcion en las distintas rutinas por las que pasas, cuando se pare ahi el debugger en Visual Studio puedes pedirle que te muestre la ventana Threads (de forma predeterminada no esta visible, pero desde el menu puedes activarla). En esa ventana salen los hilos de ejecucion de tu programa, y haciendo doble-click en cada uno se te va actualizando en la ventana Stack Trace el stack que corresponde a ese hilo. Desde el Stack (como seguramente ya sabras) puedes ir haciendo click para ver en el fuente las distintas rutinas que llevaron la ejecucion hasta ahi dentro del mismo hilo donde inicialmente hiciste click. De esta manera, puedes ver que partes del codigo se ejecutaron desde que hilo, y como se llego hasta ahi. Esto te permitira depurar el problema.
    viernes, 3 de febrero de 2017 11:47
  • Estimado Alberto: Podrías darme el ejemplo más simple posible de como "invocar" un procedimiento en el hilo principal desde un evento DataReceived (por ejemplo). Algo en la sintaxis estoy haciendo mal. Y, te digo francamente, al no usar multithreading y venir de VB6, no quisiera tener que estudiar todo ese tema para salir del pantano.

    Muchas gracias por tus consejos!

    Daniel

     Editado: Ya lo encontré!! No debo usar Invoke(delegado_proc_en_hilo_ppal) sino frm_Main.Invoke(delegado_proc_en_hilo_ppal, parametros). Por lo menos, así funciona...

    viernes, 3 de febrero de 2017 12:58