Si nasce orfani, in Visual Basic

Tra ieri e oggi, mi sono imbattuto in una stranezza.

Naturalmente, non c'è alcunché di strano: sono solamente ignorante, ma stavolta mi piacerebbe anche capire, nello specifico, che cosa non so.

Il problema

Tra i messaggi, viene posto un problema che appare banale, dapprima, ma poi presenta delle difficoltà che mi hanno 'intrigato'.

Un controllo personalizzato dovrebbe 'cercare', tra i controlli della form su cui è posto, un controllo di uno specifico tipo onde assegnarne il riferimento a un proprio membro. In fase di progettazione sembra andare bene, in fase di esecuzione sorge una Exception che, in sostanza, è una NullReferenceException.

La mia superficialità

Rispondo a occhio, presumendo una banalissima questione di tempi: lo usercontrol è istanziato prima del controllo cui dovrebbe far riferimento (non è così, come vedremo in seguito).

La logica

Poiché è opportuno attendere che la form termini il proprio caricamento prima di fare la scansione dei suoi controlli, suggerisco di far acquisire allo usercontrol il riferimento alla propria form e di intercettare il suo evento Load.

Ma non capiscono niente, questi qui!

Il thread si sviluppa per un po' di messaggi in cui a me sembra di aver capito tutto e all'altro sembra di non aver capito niente. Io sono sicurissimo della validità di quanto ho suggerito e l'altro cade così tanto in preda all'ansia da non accorgersi di alcune evidenze (del tutto ininfluenti, relativamente alla sostanza del problema, come vedremo tra poco).

Ok, prepariamogli qualcosa

A un certo punto, poiché ho un po' di tempo, mi sono messo a buttar giù quel minimo di codice che serve alla prova di quel che dico.

  • Apro un nuovo progetto Windows Application
  • Aggiungo uno User Control di nome UnaLabel
  • Ci metto dentro (indovinate un po') una Label
  • Implemento il codice relativo al riferimento alla form
      Private WithEvents mOwnerForm As Form
    
      Public Property OwnerForm() As Form
        Get
          Return mOwnerForm
        End Get
        Set(ByVal value As Form)
          mOwnerForm = value
        End Set
      End Property
    
  • Implemento il finto metodo per trovare il controllo
      Private Sub FindControl(ByVal sender As Object, ByVal e As System.EventArgs)
        Stop
      End Sub
    
  • Completo l'inizializzazione dello User Control
      Public Sub New()
    
        ' This call is required by the Windows Form Designer.
        InitializeComponent()
    
        ' Add any initialization after the InitializeComponent() call.
        OwnerForm = Me.ParentForm
        AddHandler OwnerForm.Load, AddressOf FindControl
      End Sub
    
  • Quando provo ad aggiungere un UnaLabel alla frmMain, sorge una NullReferenceException;
    Ok, mi dico, sarà perché sono a design time. Basta gestire l'eccezione;
        Try
          ' Add any initialization after the InitializeComponent() call.
          OwnerForm = Me.ParentForm
          AddHandler OwnerForm.Load, AddressOf FindControl
        Catch ex As NullReferenceException
          'ignore
        Catch ex As SystemException
          '..
        End Try

Adesso posso aggiungere lo User Control alla form e non ci sono errori.
Quando lancio, però, il debugger non si ferma sullo Stop nel metodo FindControl.

Allora pongo un altro Stop nella Form_Load e rieseguo. Entrato nel debugger in quel punto, esamino le Variabili Locali, e vedo che Me ha lo User Control, che questo User Control ha la sua ParentForm correttamente riferita alla frmMain, ma che la sua proprietà OwnerForm è ancora Nothing.

Vale a dire: nel momento della creazione del controllo, non esiste l'oggetto form da assegnare a ParentForm.
Eppure, nel codice del Designer c'è scritto:

Me.UnaLabel1 = New OneUserControl.UnaLabel

In altre parole, in Visual Basic si nasce orfani. 

La forza della lista

Ecco allora che Paolo56 (annacarlotta@alice.it) trova la soluzione: si sfrutta l'evento ParentChanged dello User Control:

  Private Sub UnaLabel_ParentChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.ParentChanged
    OwnerForm = Me.ParentForm
    AddHandler OwnerForm.Load, AddressOf FindControl
  End Sub

 Così, tutto funziona come deve. E noi abbiamo imparato qualcosa di nuovo.

Print | posted @ mercoledì 31 ottobre 2007 16:26

Comments on this entry:

Gravatar # re: Si nasce orfani, in Visual Basic
by Marco Chillemi at 31/10/2007 17:11

Ed anche io ho imparato qualcosa di nuovo (e da studiare!) :o)

Grazie Diego, Grazie Paolo56!

Approfitto di questo Tuo intervento per aggiungere 2 cose:
da quando sono entrato a far parte di questa "Comunity" ho trovato non solo dei validi programmatori, ma soprattutto delle persone che si mettono a disposizione degli altri.
Questa è una cosa bellissima!

Quindi "oltre al codice" c'è, come dici Tu, "La Forza della lista" ....
Gravatar # re: Si nasce orfani, in Visual Basic
by Paolo56 at 31/10/2007 21:23

Ci ho studiato un po' e credo che l'origine della stranezza sia la seguente:

Nell'istruzione:

Me.UnaLabel1 = New OneUserControl.UnaLabel

prima viene eseguita la parte destra, come è ovvio che è la sub New dell'User Control, e poi la parte sinistra, cioè l'assegnazione alla variabile oggetto, ma anche il settaggio della proprietà Parent e lo scatenamento dell'evento ParentChanged.

In effetti analizzando l'istruzione col Disassembler, questa si spezza in due: prima la creazione e poi l'assegnazione.

Quindi forse si potrebbe dire che non si nasce orfani, ma il babbo (sarà maschio il form?) glielo fanno vedere dopo un po'! Solite discriminazioni verso i padri!!

Ciao
Gravatar # re: Si nasce orfani, in Visual Basic
by Diego per Paolo56 (e tutti) at 01/11/2007 15:13

della serie:
tanto avanti nella programmazione a oggetti da dimenticarsi il vecchio (primordiale, direi :o)) particolare dei membri di una assegnazione.

Magari, si potrebbe ovviare con una New dotata di parametro

Me.UnaLabel1 = New OneUserControl.UnaLabel(Me)

ma mi piace di più l'evento ParentChanged, è più 'incapsulato'.
Gravatar # re: Si nasce orfani, in Visual Basic
by Antonio "tdj" Catucci at 05/11/2007 12:32

L'errore "Null reference" è dovuto al fatto che i controlli aggiunti in un container (il form ad esempio) vengono, nell'ordine:

1) istanziati
2) configurati
3) aggiunti al container

quindi, fino al punto 3) i controlli non fanno parte del container quindi tutti i rifermenti ad esso (compresa la proprietà Parentform sono null).

Il problema si risolve utilizzando, nei propri controlli, l'interfaccia ISupportInitialize. Questa interfaccia serve per l'inizializzazione batch di un controllo ed è proprio questo caso:

Public Class MyControl
Inherits UserControl
Implements ISupportInizialize

Public Sub BeginInit()
End Sub

Public Sub EndInit()
Me.OwnerForm = Me.ParentForm

If Me.Owner IsNot Nothing
AddHandler Me.OwnerForm.Load, AddressOf FindContro()
End If
End Sub
End Class

Il metodo BeginInit viene chiamato dopo il punto (1) mentre EndInit dopo il (3).

Il dubbio che ho però è un altro: nel post il problema è cercare un controllo nel form. Se ho capito bene, una soluzione semplice consiste nell'aggiungere una proprietà del tipo che si vuole gestire ed automaticamente VS visualizza una combo con l'elenco di tutti i controlli di quel tipo a cui è possibile agganciarsi.
Gravatar # re: Si nasce orfani, in Visual Basic
by Antonio "tdj" Catucci at 05/11/2007 12:44

Nella fretta ho dimenticato di aggiungere "Implelements ISupportInizialize.BeginInit()" nelle firme dei metodi

Sorry :)
Comments have been closed on this topic.