Nello specifico, il problema affrontato può essere identificato da quattro vincoli principali:
- Il software utilizzato dal 'nostalgico' permette di navigare tra le funzioni disponibili mediante un MenuStrip.
- Il menù si divide in sotto-menù e le funzioni vengono richiamate dall'evento CLICK sulle sottovoci. Non c'è uno schema fisso quindi ogni sottovoce potrà proporre un numero arbitrario di altre sottovoci prima di raggiungere quella sulla quale fare click.
- Le procedure avviate dopo il click vengono visualizzate in Form figli ... il form padre ha isMdiContainer impostato a true.
Il framework .NET ci offre un grande aiuto per la risoluzione di questo problema... Gli unici controlli che useremo, per raggiungere il risultato mostrato nella figura precedente, sono:
- 1 Textbox per leggere la scelta dell'utente.
Il form che andremo a disegnare sarà come quello mostrato nella figura successiva... (il Panel ha BackGround grigio ma alla fine lo imposteremo a nero). Durante il disegno del form ricordatevi di impostare la proprietà Anchor in modo da fargli mantenere le proporzioni durante il resize.
Una volta disegnato il form passiamo ad analizzare il codice che si occuperà di emulare il terminale. Principalmente, dovranno essere gestite solo tre caratteristiche:
- Uno stack contenente la gerarchia dei menù esplorati (per permettere di tornare indietro tramite F12)
- Il menù corrente per determinare se una voce di menù aprirà un ulteriore sottomenù oppure avvierà una routine.
- La generazione automatica degli item nel PANEL.
Come al solito preferisco incollare tutto il codice necessario e spiegare le parti importanti tramite commenti.
'# Albero delle chiamate.
'#Serve per tornare indietro nella struttura del Menù
Private TreeOfCalledMenu As New Stack(Of Object)
'# Lista dei sottomenù disponibili per il menù attualmente visualizzato.
'# E' un dizionario INTEGER,Object nel senso all'intero i corrisponde il sottomenù Object figlio del menù visualizzato a
'# video.
Private ListOfSubMenu_ForActiveMenu As New Dictionary(Of Integer, Object)
'# Riferimento al menù corrente. Va salvato nello stack quando si cambia menù.
Private CurrentMenu As Object
'# Lista dei sottomenù non espanbili che potrebbero essere ESEGUITI.
'# Questa lista va intesa come all'intero i corrisponde un sottomenù Object figlio del menù corrente.
'# Se sulla barra di scelta richiamo i ed il sottomenù definisce l'evento click allora eseguirò il codice in Click.
'# altrimenti non faccio nulla.
Private ListOfSubMenu_WithRaisableEvent As New Dictionary(Of Integer, Object)
'# Variabili di servizio. Servono alla routine di stampa del menù nel Panel.
Private _cury As Integer = 0
Private _callID As Integer = 1
Private it As Object = Nothing '# lo uso per iterare i figli del menù corrente.
Private PanelItem_Label As Label
'# La routine che crea a schermo la visualizzazione stile TN5250 :D
Public Sub __createMenuExplorer(ByVal _root As Object)
'# Pulisce la lista dei Sottomenù disponibili
'# e degli eventi Raisable. Stiamo stampando un nuovo menù quindi ci servono puliti :D
ListOfSubMenu_ForActiveMenu.Clear()
ListOfSubMenu_WithRaisableEvent.Clear()
'# Pulitura variabili per nuova esplorazione del menù. In realtà reimposto alla coordinata y da dove inizierò
'# a stampare. _callID = 1 implica che il primo numero accettato sulla textbox sarà 1.
_cury = 70
_callID = 1
'# Lista degli ITEM esplorabili (se c'è ne sono)
'# per il menù in fase di stampa.
Dim ItemsToExplore = Nothing
'# Quale tipo di item stiamo esplorando? ToolStrip o MenuStrip (Ci serve poiché non ho trovato come si castava da
'# Toolstrip a MenuStrip e viceversa). Inoltre i menù figli li contengono in membri dai nomi diversi...
If _root.GetType.Equals(GetType(MenuStrip)) Then
ItemsToExplore = _root.Items
ElseIf _root.GetType.Equals(GetType(ToolStripMenuItem)) Then
ItemsToExplore = _root.DropDownItems
End If
'# Se il menù corrente non contiene sottomenù... non stampare nulla
If Not ItemsToExplore.count > 0 Then
Exit Sub
End If
'# Riferimento Il menù corrente. Lo impostiamo dopo aver mostrato a video il menù
CurrentMenu = _root
'# Pulisce il PANEL per visualizzare il nuovo menù.
p1.Controls.Clear()
'# Crea intestazione fissa per il Menù... Ovviamente cambiate il nome ^_^
PanelItem_Label = New Label
PanelItem_Label.Text = "DB A2CS00 - TN5250 Emulator - By Luigi Di Biasi"
PanelItem_Label.Width = p1.Width
PanelItem_Label.ForeColor = Color.White
PanelItem_Label.Height = PanelItem_Label.Font.Height + 6
PanelItem_Label.Location = New Point(30, 10)
PanelItem_Label.Visible = True
p1.Controls.Add(PanelItem_Label)
'# Crea intestazione per il menù... In realtà va a stampare a video la proprietà Text dell'oggetto
'# passato alla routine. (la variabile _root)
PanelItem_Label = New Label
PanelItem_Label.Text = _root.text
PanelItem_Label.Width = p1.Width
PanelItem_Label.ForeColor = Color.White
PanelItem_Label.Height = PanelItem_Label.Font.Height + 6
PanelItem_Label.Location = New Point(30, 30)
PanelItem_Label.Visible = True
p1.Controls.Add(PanelItem_Label)
'# Crea il corpo del menù. Quei due IF sono messi così perché non sono abituato con la sintassi del VB
'# a miscelare AND e OR...
For Each Me.it In ItemsToExplore
'#Qui vanno ByPassati tutti gli elementi che non devono essere considerati come item. (ad esempio le linee di
'# di separazione tra i menù... Non facciamo cose complicate noi ...
If it.GetType.Equals(GetType(ToolStripMenuItem)) Then
GoTo ok
End If
If it.GetType.Equals(GetType(MainMenu)) Then
GoTo ok
End If
'# esclude
Continue For
ok:
'# Prepara l'ITEM. Se non è chiaro perché uso il membro Tag mandatemi una mail ma nel post verrebbe
'# troppo lungo da spiegare.
PanelItem_Label = New Label
PanelItem_Label.Text = "&" & _callID & ") " & it.Text
PanelItem_Label.Location = New Point(100, _cury)
PanelItem_Label.Width = p1.Width
PanelItem_Label.ForeColor = Color.LightGreen
PanelItem_Label.Height = PanelItem_Label.Font.Height + 6
'# Salviamo i riferimenti al menù da richiamare all'interno della Label che stiamo stampando in modo
'# da poterli sempre recuperare durante la gestione del menù a video. (OnKeyUp sulla textbox)
PanelItem_Label.Tag = it
PanelItem_Label.Name = _callID
If it.GetType.Equals(GetType(MenuStrip)) Then
If it.Items > 0 Then
ListOfSubMenu_ForActiveMenu.Add(PanelItem_Label.Name, PanelItem_Label)
Else
ListOfSubMenu_WithRaisableEvent.Add(PanelItem_Label.Name, PanelItem_Label)
End If
ElseIf it.GetType.Equals(GetType(ToolStripMenuItem)) Then
If it.DropDownItems.count > 0 Then
ListOfSubMenu_ForActiveMenu.Add(PanelItem_Label.Name, PanelItem_Label)
Else
ListOfSubMenu_WithRaisableEvent.Add(PanelItem_Label.Name, PanelItem_Label)
End If
End If
p1.Controls.Add(PanelItem_Label)
_callID += 1
_cury += PanelItem_Label.Font.Height + 5
Next it
End Sub
'# Qui gestiamo la textbox!
Private Sub TextBox1_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles TextBox1.KeyUp
If e.KeyCode = Keys.Enter Then
Try '# determina se il numero inserito corrisponde ad un sottomenù
If ListOfSubMenu_ForActiveMenu.ContainsKey(TextBox1.Text) Then
'# Aggiunge il menù in stampa alla lista delle chiamate fatte
TreeOfCalledMenu.Push(CurrentMenu) ' qui viene usato lo stack!
'# esplora un sottomenù
__createMenuExplorer(ListOfSubMenu_ForActiveMenu(TextBox1.Text).tag)
TextBox1.Text = ""
'# determina se il numero inserito corrisponde ad un sottomenù con CLICK, in caso positivo esegue
'# il codiec
ElseIf ListOfSubMenu_WithRaisableEvent.ContainsKey(TextBox1.Text) Then
'# Richiama la routine di gestione associata al menù scelto
ListOfSubMenu_WithRaisableEvent(TextBox1.Text).tag.PerformClick()
TextBox1.Text = ""
End If
Catch
End Try
ElseIf e.KeyCode = Keys.F12 Then '## qui viene gestito F12 torna indietro... semplicemente chiediamo alla
'# routine di stampare il menù precedente sullo stack
If TreeOfCalledMenu.Count > 0 Then
__createMenuExplorer(TreeOfCalledMenu.Pop)
End If
End If
End Sub
'# Siccome ho usato questo codice in un altro software non potevo permettere che la console venisse chiusa ... potete togliere se volete questa routine.
Private Sub testform_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
' Non può essere chiuso implicitamente
e.Cancel = True
End Sub
Conclusioni
Dove potrebbe essere utile questo codice? Bhe, in tutte quelle applicazione che fanno uso di MenuStrip e che vogliamo arricchire con un funzione di navigazione testuale
. Credo che la forza di questo piccolo progetto stia nell'adattarsi dinamicamente ai cambi del MenuStrip (anche a runtime se avvengono) quindi chissà... può tornar utile in qualche situazione.
Per scaricare un progetto dal quale poter iniziare potete far
click qui. Vi prego di inviare commenti, suggerimenti, segnalazione bug o altro poiché questo progetto credo potrà evolvere...
Nel progetto ho incluso un po' di pubblicità per la mia azienda (prima o poi un ROI dovrà portarlo questo blog!)... se non vi dà disturbo il menù da visitare è il primo, voci 1 e 5