Luigi Di Biasi


XP Programming Style
Sondaggio
Argomento Spider e WebCrawler

Interessante
Non interessante

Generare un menu stile TN5250 da un controllo MenuStrip

TN5250 Style in .NET

Solo per nostalgici ...
Recentemente ho affrontato una richiesta molto 'curiosa' posta da un 'nostalgico' dei menù gerarchici ed a caratteri del sistema operativo OS/400 versione 4 (e non so' se successive).

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 nostalgico odia il mouse... 
- 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.

Le immagini che seguono dovrebbero dare l'idea del MenuStrip e del problema che vogliamo risolvere...

Da cosi... (Il software originale)

Vorrebbe che diventasse così...

In realtà arriveremo a fare una cosa del genere...


.NET Power!!!
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 Form con borderStyle=NONE
- 1 Panel 
- 2 Label (SCELTA e F12=Indietro)
- 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.


Implementazione
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 

 
Categoria: Come fare a ...
venerdì, 15 ott 2010 Ore. 15.56
Statistiche
  • Views Home Page: 26.458
  • Views Posts: 50.253
  • Views Gallerie: 0
  • n° Posts: 41
  • n° Commenti: 33
Archivio Posts
Anno 2012

Anno 2011

Anno 2010

Anno 2009

Anno 2008
Copyright © 2002-2007 - Blogs 2.0
dotNetHell.it | Home Page Blogs
ASP.NET 2.0 Windows 2003