Delegate aggiorna la UI, ma non non da un modulo

di il
13 risposte

Delegate aggiorna la UI, ma non non da un modulo

In FormMain ho il componente CefSharp (è chromium browser).
Quando l'utente fa click sulla mappa si scatena l'evento "JavascriptMessageReceived".
In questo evento viene aggiornata una RichTextBox (e qua nessun problema) e poi esegue una Sub che è in un modulo.

Intanto vi mostro il codice MessageReceived
Private Sub wwMap_JavascriptMessageReceived(sender As Object, e As JavascriptMessageReceivedEventArgs) Handles wwMap.JavascriptMessageReceived
        Dim Thread2 = New Thread(New ThreadStart(AddressOf SetText))
        Thread2.Start()
        mMapInit.jsMessageReceived(e)
End Sub
Ovviamente anche il codice Delegate e routine (da Doc MS: link [how to make thread safe calls to windows forms controls] )
Delegate Sub SafeCallDelegate(text As String)
    Private Sub SetText()
        WriteTextSafe("TEST testo.")
    End Sub
    Private Sub WriteTextSafe(text As String)
        If rtbNoaa.InvokeRequired Then
            Dim d As New SafeCallDelegate(AddressOf SetText)
            rtbNoaa.Invoke(d, New Object() {text})
        Else
            rtbNoaa.Text = text
        End If
    End Sub
Il RichTextBox viene aggiornato e nessun problema (anche se a dire il vero ho dovuto impostare Strict su off temporaneamente, ma poi dovrà essere reimpostaro su on - per adesso lasciamo stare e ci ritorneremo dopo aver risolto il problema dell'aggiornamento della UI).

Come vedete l'evento JavascriptMessageReceived richiama una routine in "mMapInit", e cioè:
mMapInit.jsMessageReceived(e)

- * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _

Ora parte del codice della routine in "mMapInit"

Imports System.Threading
Imports CefSharp
Module mMapInit

Public Sub jsMessageReceived(e As JavascriptMessageReceivedEventArgs)
    Dim message As List(Of Single) = e.ConvertMessageTo(Of List(Of Single))
    System.Diagnostics.Debug.WriteLine("STEP 1")

    Select Case message(0)
        Case mK.typeOfEventMouseOnMap.contextmenu 'values: type, id, lat, lng

            Select Case message(1)
                Case 0, 1 'Click on Map or on Airspace
                    If mK.flagBtnNoaa Then 'NOAA is on ?
                        If message(3) > 90 Or message(3) < -90 Or message(4) > 180 Or message(4) < -180 Then Exit Sub 'Clicked off map? Let's out
                        System.Diagnostics.Debug.WriteLine("STEP DELEGATEEEE !!!")
                        ' Dim messageNoaa As String = mNoaa.requestToNoaa(message(3), message(4)).Result **Per adesso non do noia al server noaa**
                        Dim Thread3 = New Thread(New ThreadStart(AddressOf SetText2))
                        Thread3.Start()
                    End If
                'Altri Case .....
            End Select
            'Altri Case .....
    End Select
End Sub
Ed ovviamente non poteva mancare il Delegate e routine
Delegate Sub SafeCallDelegate2(text As String)
Public Sub SetText2()
    WriteTextSafe("messageNoaa")
End Sub

Public Sub WriteTextSafe(text As String)
    If FormMain.rtbNoaa.InvokeRequired Then
        Dim d As New SafeCallDelegate2(AddressOf SetText2)
        FormMain.rtbNoaa.Invoke(d, New Object() {text})
    Else
        FormMain.rtbNoaa.Text = text
    End If
End Sub
- * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _ - * _

QUESITO
Mi sarei aspettato che quando chiamo Thread3 con Thread3.Start() la RichTextBox mi si aggiornase, mentre non si aggiorna e silenzio totale da Visual Studio (cioè nemmeno una eccezione).
Thread3.Start sostanzialmente va a richiamare "WriteTextSafe" dove esplicitamente controllo se il thread attuale è lo stesso che gestisce la UI e con Invoke risalgo fino a trovare il thread giusto per poi aggiornare il testo della RichTextBox.

Be, appunto, mi aspettavo, ma ho aspettato male..
Mentre nel FormMain questa tecnica funziona, nel modulo non funziona..

Qualcuno sa illuminarmi ?


[IMPORTANTE ADD]
Ho modificato WriteTextSafe del modulo come segue:
Public Sub WriteTextSafe(text As String)
    System.Diagnostics.Debug.WriteLine("STEP WriteTextSafe - 1")
    If FormMain.rtbNoaa.InvokeRequired Then
        System.Diagnostics.Debug.WriteLine("STEP WriteTextSafe - 2")
        Dim d As New SafeCallDelegate2(AddressOf SetText2)
        FormMain.rtbNoaa.Invoke(d, New Object() {text})
    Else
        System.Diagnostics.Debug.WriteLine("STEP WriteTextSafe - 3")
        FormMain.rtbNoaa.Text = text
    End If
End Sub
ed ho notato che FormMain.rtbNoaa.InvokeRequired non fa eseguire il delegate !
Cioè la riga: System.Diagnostics.Debug.WriteLine("STEP WriteTextSafe - 2") non viene mai scritta a console, bensì avviene questo:

STEP 1
STEP DELEGATEEEE
STEP WriteTextSafe - 1
Il thread 0x3544 è terminato con il codice 0 (0x0).
STEP WriteTextSafe - 3
Il thread 0x4274 è terminato con il codice 0 (0x0).


Come si nota, STEP WriteTextSafe - 2 non viene mai scritto in console,
ma viene eseguito STEP WriteTextSafe - 1 e STEP WriteTextSafe - 3 !!

Forse sbaglio a richiamare InvokeRequired utilizzando FormMain.rtbNoaa ??

13 Risposte

  • Re: Delegate aggiorna la UI, ma non non da un modulo

    Il fatto che sia necessario riportare ben tre spezzoni enormi di codice per spiegare il problema denota un po' di confusione nella scrittura nel codice, nel senso che sarebbe opportuno operare un refactoring affinché ogni operazione sia meglio distinta dalle altre, con la possibilità di isolarla e discuterne in separata sede.

    Così come esposta, con questa marea di dettagli e così tante righe di codice, affrontare il tuo problema e diagnosticarne la causa vuol dire sobbarcarsi una indagine che richiede un sacco di tempo.

    Magari trovi qualcuno disposto a farlo. Il mio è solo un consiglio riguardo una forma più sintetica con cui potresti interessare un maggior numero di sviluppatori dividendo la problematica e affrontando una cosa alla volta.

    Ciao!
  • Re: Delegate aggiorna la UI, ma non non da un modulo

    Ciao, ok troppe informazioni hanno un effetto opposto. Hai ragione.
    Semplifico così:
    +----------+
    | FormMain | -> FMain ha un componente RichTextBox
    |          |
    | CefSharp | -> FMain ha un componente browser CefSharp
    |   \_ MessageEvent -> L'utente clicca sul browser e
    +----------+           scatena l'evento (JavascriptMessageReceived),
                           il quale chiama la Sub Module1.Noaa
    
    +-----------+
    | Module1   |
    |           |
    | Sub Noaa()|
    |           |
    +-----------+
    
    Sub Noaa()
    If FormMain.rtbNoaa.InvokeRequired Then
            Dim d As New SafeCallDelegate2(AddressOf SetText2) |||MAI ESEGUITA ! E infatti non aggiorna il RichTextBox
            FormMain.rtbNoaa.Invoke(d, New Object() {text})    |||
        Else
            FormMain.rtbNoaa.Text = text
        End If
    
    Come capire dove sbaglio e/o cosa succede ?
    Grazie.
  • Re: Delegate aggiorna la UI, ma non non da un modulo

    Quello che io onestamente non capisco è perché da un evento si debba avviare un thread che poi va a chiamare una funzione in un modulo che poi ne richiama un'altra la quale alla fine torna a fare diretto riferimento al Form principale da cui tutto è iniziato.

    Qual è l'utilità di usare così tanti strumenti, dargli un nome poco significativo (SetText2?), fare una catena di chiamate per giunta in un modulo (che dovrebbe essere una entità separata e indipendente) nel quale si fa riferimento a FormMain, quindi di nuovo alla finestra principale.

    Tra l'altro, FormMain è il nome della classe? Spero di no...
  • Re: Delegate aggiorna la UI, ma non non da un modulo

    Buondì

    Sinceramente mi sono perso un po' nel flusso che vuoi ottenere, però provo a darti un suggerimento su come di norma implemento la gestione dei thread nelle UI (anche se di solito lavoro con WPF e C#, però credo che la base sia la stessa).

    In linea di principio il problema è che nell'applicazione stanno girando più thread:
    • quello grafico: è di fatto il thread che ha creato l'interfaccia grafica ed è l'unico che pertanto può aggiornarla
    • quello/i di elaborazione: di fatto sono thread creati per fare elaborazioni più o meno pesanti senza però bloccare l'interfaccia grafica
    Il problema nasce nel momento in cui un thread di elaborazione deve aggiornare il contenuto di un componente grafico, poichè non può accedervi direttamente essendo stato creato da un alto thread (quello grafico appunto).

    Guardando il tuo codice mi pare di capire che FormMain sia accessibile a tutti i thread, sia grafico che non.
    In tal caso, secondo me il thread di elaborazione può aggiornare i componenti in questo modo:
    
    FormMain.rtbNoaa.Invoke((MethodInvoker)delegate {
        FormMain.rtbNoaa.Text = newText;
    });
    

    Con questo codice invochi un metodo all'interno del thread grafico, da cui puoi quindi accedere (e aggiornare) i componenti grafici.

    Prova a vedere se in questo modo riesci nel tuo scopo
  • Re: Delegate aggiorna la UI, ma non non da un modulo

    Non è come tu la semplifichi. Ora ti dimostro. In primis ti lascio il link del mio programma funzionante scritto con Xojo (periodo covid lockdown) e che sto convertendo in VBnet - non occorre che tu lo scarichi, basta che tu veda le immagini per capire su cosa sto lavorando.
    In questo modo capisce questi eventi mappa da dove vengono..
    Tra l'altro, FormMain è il nome della classe? Spero di no..
    No, è il nome del Form principale, cioè il Form di avvio.
    Se dal file di Progettazione clicco F7, mi appare il codice:
    Imports ...
    
    Public Class FormMain
        Private Sub FormMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ...
    End Class
    
    Ma FormMain è il form di avvio, per cui anche il nome della Classe del Form di avvio della applicazione.
    Se è questo che intendi.

    ----------------------------------------------------------------------------------------------------------------------------------------------------------
    Ora pazienta un attimo e leggi: ti sto spiegando la logica seguita per determinate iterazioni da parte dell'utente
    ----------------------------------------------------------------------------------------------------------------------------------------------------------

    Il componente CefSharp visualizza una mappa (utilizzo Leaflet fare per ciò) e visualizza svariati dati (come aeroporti, navaids, aerei, pianificatore di volo, ..). Inoltre permette di fare interagire l'utente con il programma stesso che può creare piani di volo, visualizzare informazioni, .. non mi divulgo ulteriormente.
    MA TI FA CAPIRE che l'utente può fare tante cose sulla mappa. E per fare ciò utilizza sia il click destro che sinistro del mouse.
    Quando ad esempio clicca con il mouse destro su un aeroporto deve essere eseguito una serie di azioni.
    Se clicca su uno spazio aereo deve essere eseguito altre serie di azioni.
    Ecc.. ecc..

    Per cui abbiamo, appunto, l'evento JavascriptMessageReceived. Questo evento dice: << eih, è stato cliccato sulla mappa. Questi sono i dati: {svariati dati} >>
    Ecco che nasce, a mio avviso, la necessità di un modulo su cui posso lavorarci anche in futuro senza toccarmi il form principale.
    E non occorre scomodare una classe: tanto non devo istanziare niente.

    Per cui l'evento CefSharp chiamerà la Sub che si trova nel modulo. Segue la vera SUB.
    Private Sub wwMap_JavascriptMessageReceived(sender As Object, e As JavascriptMessageReceivedEventArgs) Handles wwMap.JavascriptMessageReceived
        mMapInit.jsMessageReceived(e)
    End Sub
    Noterai che nel codice qua sopra non c'è Thread2, mentre negli esempi iniziali di questo PM c'è: è un test per capire come si aggiorna la GUI quando sono in un altro thread. Sono alle "prime" armi per cui devo capire.

    Nel Modulo ho:
    Public Sub jsMessageReceived(e As JavascriptMessageReceivedEventArgs)
    Qua devo controllare cosa desidera fare l'utente: ho "Select Case" annidati perché le possibilità sono molteplici (circa 20+  possibili decisioni dell'utente)
    End Sub
    A sua volta i Case richiamano altre Sub che eseguono le azioni che si necessitano.

    Il concetto è semplice. Ci vuole più a spiegarlo che programmarlo.
    Per quanto riguarda il modulo: mi serve per lasciare il più pulito il Form di avvio oltre a facilitarmi le future modifiche.

    ADESSO NON MOLLARE e continua a leggere :

    In questo messaggio (questo PM nel forum), si parla in specifico di un
    Select Case message(1)
    Case 0, 1

    Per facilitarti, "Case 0, 1" leggilo così: Case Noaa

    Noaa è un server ufficiale del governo degli Stati Uniti che ha anche una funzione di calcolo del geomagnetismo: gli si passa la latitudine, longitudine, ecc.. ed il server Noaa ritorna dei dati.
    QUESTI DATI DI RITORNO LI DEVO VISUALIZZARE NELLA TEXTRICHBOX.
    A questo punto la logica vuole che aggiorni il TextRichBox all'interno di questo Select Case (sempre da Module, non dimenticare)
    e non dal Form principale (cioè FormMain) da dove l'evento è partito (da CefSharp ? JavascriptMessageReceived) !

    Per cui se da questo Select Case faccio semplicemente
    FormMain.rtbNoaa.Text = "..datiFromNoaa.."
    non visualizza un bel niente nella RichTextBox.

    Ecco perché utilizzo FormMain.rtbNoaa.InvokeRequired : forse il controllo non fa parte della thread UI.

    Ma ironia della sorte, se seguo step by step il codice WriteTextSafe mi accorgo che InvokeRequired da come risultato false, ed esegue subito: FormMain.rtbNoaa.Text = "..text.." ma non visualizza appunto nulla nella RichTextBox !


    Ora sono stato esaustivo, no ?
  • Re: Delegate aggiorna la UI, ma non non da un modulo

    jockerfox ha scritto:


    Ma FormMain è il form di avvio, per cui anche il nome della Classe del Form di avvio della applicazione.
    Se è questo che intendi.
    Certo che era questo quello che intendevo.

    Infatti, già in un altro thread analogo ti avevo detto che è bene studiare la OOP prima di cimentarsi nell'uso approfondito di C# e/o VB.NET.

    Se avessi seguito il mio consiglio, leggere questa istruzione dovrebbe insospettirti:
    FormMain.rtbNoaa.Text = "..datiFromNoaa.."

    Quando si lavora con le classi, è necessario creare una istanza prima di poterle utilizzare:
    
    Dim frm As New FormMain()
    
    Solo dopo aver fatto questa operazione è possibile accedere a quelli che sono i "membri dell'istanza", di cui fa parte la famosa RichTextBox "rtbNoaa":
    
    frm.rtbNoaa.Text = "..."
    
    Tu stai usando invece il nome della classe FormMain per accedere a quello che è un "membro di istanza" dell'oggetto che appartiene a quella classe, ossia la RichTextBox. Come è possibile questo?

    VB.NET adotta un "trucchetto" per essere affine al modo di usare i Form nel passato, quando tu avevi un nome (es. Form2) e con quel nome identificavi tutto: la classe e anche l'unica istanza possibile della stessa.

    Questo trucchetto prevede che, se usi il nome della classe del Form e chiami un metodo che richiede un oggetto di quel tipo, VB.NET crea automaticamente un oggetto per te.

    Riassumendo tutto quanto, stai usando una sintassi di tipo vecchio, da evitare assolutamente, con la quale scrivi una cosa che secondo la logica OOP (che devi studiare) non avrebbe senso, con l'effetto di andare ad agire dal modulo a una nuova istanza della finestra che è diversa da quella originale.

    In pratica, dalla Form principale tu chiami una funzione del modulo, che usando l'identificatore FormMain fa sì che VB crei "dietro le quinte" una nuova Form principale, in cui vai a valorizzare il testo del RichTextBox, ma quella finestra non sarà visibile ed è un oggetto diverso da quella iniziale.

    Quando aggiorni la RichTextBox, prova ad aggiungere qualcosa del tipo FormMain.Show() dopo il codice che aggiorna il testo, e vedrai che ti appariranno due finestre.

    Prima di usare .NET, le sue classi e i suoi oggetti, bisogna conoscere .NET, le classi e gli oggetti.
  • Re: Delegate aggiorna la UI, ma non non da un modulo

    Corretto Marco !
    FormMain.Show() apre una nuova finestra fantasma.
    Mi era venuto il dubbio ma la avevo scartata perché la ritenevo impossibile.
    E comunque: << Si, provengo dal vecchio metodo di programmare ! E' vero. >>

    -> e devi studiare: non massacrarmi troppo in anticipo.. dammi tempo invece di capire i nuovi meccanismi di programmazione. Hai a che fare con uno che per + di 30 anni ha programmato nella vecchia maniera (ma bellissimo assembly e visual basic di un tempo !). Please...

    Vi aggiornerò dell'esito finale tra qualche giorno (programmo nel tempo libero - oramai lo sapete).

  • Re: Delegate aggiorna la UI, ma non non da un modulo

    jockerfox ha scritto:


    Corretto Marco !
    FormMain.Show() apre una nuova finestra fantasma.
    Non è tecnicamente un fantasma, ma una finestra del tutto identica a quella che già vedi, solo è un'altra istanza, una copia.

    jockerfox ha scritto:


    Mi era venuto il dubbio ma la avevo scartata perché la ritenevo impossibile.
    Come diceva Franchino di Fantozzi, "perché ti manca la cultura".
    Scherzi a parte, c'è una commistione tra automatismi (anche brutti a dire il vero) operati dal runtime di VB e l'uso di pattern strani che, a mio avviso, complicano una implementazione che ritengo abbastanza complessa per chi ha conoscenze di base.

    jockerfox ha scritto:


    E comunque: << Si, provengo dal vecchio metodo di programmare ! E' vero. >>
    Non c'è nulla di male e non è mio intento massacrare nessuno, né togliere il tempo di capire questi nuovi meccanismi: il mio avvertimento sta solo a indicare che prima di cimentarsi con determinate cose, se sono sconosciute, è necessario studiarle un minimo, altrimenti il rischio è appunto quello di impazzire descrivendo scenari anche complessi e imbastire strutture complicatissime per poi trovarsi in difficoltà per una inezia.

    Un saluto!
  • Re: Delegate aggiorna la UI, ma non non da un modulo

    Joke..

    Nel tuo ultimo post (che stranamente ho capito), dici che facendo FormMain.rtbNoaa.Text = ...tuoTesto... l'applicazione non visualizza nulla.
    Dal mio punto di vista doveva addirittura darti errore perchè accedi ad un componente grafico da un thread non adibito.
    Hai provato a mettere quella istruzione dentro alla "Invoke" come c'era nel pezzo di codice che ti avevo girato?

    Solo per sapere... Perchè a me funziona sempre (ma può anche essere perchè lavoro con WPF e pattern MVVM)
  • Re: Delegate aggiorna la UI, ma non non da un modulo

    PiGi78 ha scritto:


    Dal mio punto di vista doveva addirittura darti errore perchè accedi ad un componente grafico da un thread non adibito.
    Forse non da errore perché in quel caso il thread chiamante è anche lo stesso che, per un effetto collaterale, crea l'oggetto "indesiderato" della MainForm duplicata. Ma è solo una ipotesi.

    Una cosa da tener presente è che non sempre l'accesso multithreading fatto erroneamente produce un errore visibile e immediato: a volte può non accadere nulla, a volte possono esserci risultati imprevedibili.
  • Re: Delegate aggiorna la UI, ma non non da un modulo

    Ciao PiGi78,
    ho risolto come segue nella FormMain
    public partial class FormMain : Form
        {
            public static FormMain? instance;
            public static FormMain Instance
            {
                get
                {
                    return instance;
                }
            }
    
            public FormMain()
            {
                instance = this;
    ... ecc. ...
    Al di fuori di FormMain, uso instance.componente.Text = "testo"

    P.S.: cmq mi sono fermato a codare ed ho acquistato un libro C#10 (non faccio nomi per evitare..).
    E mano a mano che leggo mi accorgo di quanti casini facevo (aimè ho lasciato VisualBasic per C#. Devo dire cmq che C# non dispiace una volta entrato nel meccanismo; VB mi aveva portato fuori rotta: resettare tutto!).

    Ci risentiamo tra un bel pò..
  • Re: Delegate aggiorna la UI, ma non non da un modulo

    jockerfox ha scritto:


    ho risolto come segue nella FormMain
    Meglio che non commento...

    jockerfox ha scritto:


    E mano a mano che leggo mi accorgo di quanti casini facevo (aimè ho lasciato VisualBasic per C#. Devo dire cmq che C# non dispiace una volta entrato nel meccanismo; VB mi aveva portato fuori rotta: resettare tutto!).
    Il linguaggio Visual Basic o C# c'entrano poco: anche VB restituisce gli stessi risultati se utilizzato nel modo corretto.
    Il problema è conoscere .NET, conoscere come funziona il runtime e la libreria di classi, e i rudimenti della programmazione orientata agli oggetti (OOP).

    Il linguaggio (in questo frangente) è del tutto indifferente.

    Ciao!
  • Re: Delegate aggiorna la UI, ma non non da un modulo

    jockerfox ha scritto:


    ...ho risolto come segue nella FormMain...
    Se cercavi di applicare il pattern Singleton, non ti è riuscito.
    Leggi bene il tuo libro, poi passa al link ed eventualmente approfondisci ancora.
Devi accedere o registrarti per scrivere nel forum
13 risposte