lunedì 29 settembre 2014

Scrittura in rosso dei turni straordinari

Ora devo individuare come fare per catturare i multipli di tre, nei punteggi che figurano nella tabella.

Sembra di aver risolto...
Sub calcola()
    Dim x As Range
    Set x = Range("ListaNomiMese").Find(lastcaption)
    If x Is Nothing = False Then
        x.Offset(0, 2).Formula = x.Offset(0, 2).Formula - 1
        Straordinario x
    End If
    Set x = Range("ListaNomiMese").Find(Bersaglio.Formula)
    If x Is Nothing = False Then
        x.Offset(0, 2).Formula = x.Offset(0, 2).Formula + 1
        Straordinario x
    End If
End Sub

Private Sub Straordinario(x As Range)
    If x.Offset(0, 2).Formula Mod 4 = 0 Then
        colore = vbRed
    Else
        colore = vbBlack
    End If
    Bersaglio.Font.Color = colore
End Sub
Sì, funziona! Praticamente ogni tre turni ne scrive uno, sul Calendario, in rosso, a significare un turno straordinario.

La routine che calcola i turni.

Non ci perdiamo in nebulosità mentali, altrimenti non andiamo avanti!

Calcoliamo la quantità di turni fatti nel mese.
A quanto ho capito, non interessa sapere quanti turni di giorno o di notte siano stati fatti, e questo facilita le cose.
Possiamo risistemare il modulo formattaFoglio togliendo la tabella dei turni di giorno e nominando soltanto i turni fatti di notte (la quantità relativa di diurni e notturni sarà un problema che dovranno scornarsi a livello di reparto).

Bene.
Mi salvo il modulo, però...

Ecco la routine copiaLista rimaneggiata:
Private Sub copiaLista()
    Dim elemento As Range
    Dim k As Integer, z As Integer
    z = 15 '(posizione iniziale della lista dei nomi del mese)
    For k = 1 To Range("ListaNomi").Rows.Count
        ActiveSheet.Cells(z, 6).FormulaR1C1 = Range("ListaNomi").Cells(k, 1).FormulaR1C1
        ActiveSheet.Cells(z, 7).FormulaR1C1 = Range("ListaNomi").Cells(k, 2).FormulaR1C1
        z = z + 1
    Next k
    ActiveWorkbook.Names.Add Name:="ListaNomiMese", RefersTo:="=" & Range(Cells(15, 6), Cells(z - 1, 6)).Address(True, True)
    Griglia Range("ListaNomiMese")

    ActiveWorkbook.Names.Add Name:="ListaReparti", RefersTo:="=" & Range(Cells(15, 7), Cells(z - 1, 7)).Address(True, True)
    Griglia Range("ListaReparti")
    
    ActiveWorkbook.Names.Add Name:="NumeroTurni", RefersTo:="=" & Range(Cells(15, 8), Cells(z - 1, 8)).Address(True, True)
    Griglia Range("NumeroTurni")
    For Each elemento In Range("NumeroTurni")
        elemento.HorizontalAlignment = xlCenter
        elemento.Formula = 0
    Next
    
    
    With ActiveSheet
        With .Cells(Range("ListaNomiMese").Row - 1, 6)
            .FormulaR1C1 = "NOME"
            .HorizontalAlignment = xlCenter
        End With
        Griglia Cells(Range("ListaNomiMese").Row - 1, 6)
        With .Cells(Range("ListaNomiMese").Row - 1, 7)
            .FormulaR1C1 = "REP."
        End With
         Griglia Cells(Range("ListaNomiMese").Row - 1, 7)
        With .Cells(Range("ListaNomiMese").Row - 1, 8)
            .FormulaR1C1 = "GIORNO"
        End With
        Griglia Cells(Range("ListaNomiMese").Row - 1, 8)
        .Columns(6).ColumnWidth = 26
        Exit Sub
        With .Columns(7)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
        
       
        
        With .Columns(8)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
    End With
End Sub
Praticamente ho tolto la colonna dei turni di mattina, rinominando la colonna NumeriNotti in NumeroTurni e basta.

Inoltre ho aumentato un po' l'altezza delle righe.


Adesso cerchiamo di calcolare il numero di turni come avevo fatto precedentemente.

Il principio che avevo usato era:
Conservare in una variabile la stringa eliminata dalla cella;
Considerare la nuova stringa della cella come Bersaglio.Formula;
Cercare nella lista la stringa eliminata dalla cella e ridurre di 1 il numero dei turni;
Cercare nella lista la stringa nuova della cella e aumentare di 1 il numero dei turni.


In fondo è semplicissimo!

Proviamo...
Sub calcola()
    Dim x As Range
    Set x = Range("ListaNomiMese").Find(lastcaption)
    If x Is Nothing = False Then x.Offset(0, 2).Formula = x.Offset(0, 2).Formula - 1
    Set x = Range("ListaNomiMese").Find(Bersaglio.Formula)
    If x Is Nothing = False Then x.Offset(0, 2).Formula = x.Offset(0, 2).Formula + 1
End Sub
Eccola: dovrebbe essere perfetta.

Se vado su una cella vuota, lastcaption è vuota, quindi il range Find di lastcaption è Nothing.
In questo caso, però, non succede nulla perché si agisce sui punteggi solo se il range Find non è Nothing. Quindi si provvererà soltanto a sistemare il punteggio della nuova caption di Bersaglio.
Analogamente, se io vado su una cella con un nome e ci voglio trascrivere il menu vuoto, prima trovo lastcaption, e aggiusto il suo punteggio, quindi il range Find della stringa vuota è Nothing, quindi non succede niente al punteggio.

Dovrebbe funzionare alla perfezione. Molto semplice ed evita i casini di contare ogni volta i nomi, che è di un lento e di un dispendioso pazzesco!

sabato 27 settembre 2014

Ripristino dell'aggiunta intelligente di fogli calendario e creazione di menu personalizzati.

Adesso cosa mi rimane da fare?
La signora della direzione sanitaria invia lo scheletro dei turni, con l'attribuzione dei reparti, mentre l'attribuzione dei medici la fa il primario di ogni singolo reparto.
Una volta che ogni reparto ha stabilito i medici per ogni turno, la signora li trascrive e calcola i turni totali per l'attribuzione anche dei turni straordinari.
Predisponiamo i menu, dunque.

Ma prima devo ripristinare quel meccanismo del caricamento di nuovi fogli solo se sono del mese successivo, senza ripetizioni.
Vediamo la routine nel programma vecchio...

Sub main()
    Dim d As Date, nomeFoglio As String
    
    d = DateAdd("m", 1, Date)      'attribuisce alla variabile locale d il valore del mese successivo alla data attuale
    nomeFoglio = MonthName(Month(d)) & " " & Year(d)    'attribuisce alla stringa nomeFoglio mese e anno della variabile locale d
    
    Sheets.Add          'aggiunge un nuovo foglio
    On Error GoTo x     'gestisce l'errore di tentata rinominazione del foglio
    ActiveSheet.Name = nomeFoglio   'rinomina il foglio con la stringa nomeFoglio.
    ActiveSheet.Move after:=Sheets(Sheets.Count) 'sposta alla fine della cartella il nuovo foglio

        
    formattaFoglio Month(d), Year(d)  'formatta il foglio secondo lo schema
    attribuzioneTurni
    Exit Sub
x:     'gestione dell'errore di tentata rinominazione del foglio: elimina il foglio senza suscitare avvisi di eliminazione.
    Application.DisplayAlerts = False
    ActiveSheet.Delete
    Application.DisplayAlerts = True
End Sub
Mi sembra ben congegnata.
Ho fatto l'aggiunta della chiamata alla routine attribuzioneTurni(), e funziona egregiamente!
Bene! In questo modo, in un paio di secondi, la signora si troverà i turni predisposti, da mandare ai reparti perché essi li compilino con i nomi dei singoli medici.

Ora mi devo occupare dei menu.
Per prima cosa, devo definire di quali reparti i medici fanno parte.
E' bene che mi crei un elenco di medici con l'appartenenza ai relativi reparti.

Mario Sistoletti CAR
Giovanni Mitrali CAR
Arnaldo Coronari CAR
Luigi Di Astoli         CAR
Fernando Ventricoli CAR
Lucio Interni         MED
Marco Medici         MED
Luciano Luminari MED
Antonio Scienziatoni MED
Anselmo Nuvoloni MED
Ovviamente, nomi fittizi, stando qui in ambiente pubblico.

Ecco, ho trasferito dal vecchio abbozzo del programma tutto il blocco di codice che gestisce i menu e mi sono trovato anche qui perfettamente rappresentati i menu.
Ma ora devo intervenire per visualizzare solo i cardiologi o solo gli internisti a seconda del reparto che ha il turno.

Devo intervenire su questa routine del modulo gestoreMenu.
Private Sub caricaMenu(menu As CommandBar, rigaIniziale As Integer, col As Integer)
    Dim button As CommandBarButton
    For k = 1 To Range("ListaNomiMese").Rows.Count
        Set button = menu.Controls.Add(Type:=msoControlButton)
        button.Caption = Range("ListaNomiMese").Cells(k, 1).FormulaR1C1
        button.OnAction = "'scrivi """ & button.Caption & """, """ & button.Tag & """'"
    Next k
    Set button = menu.Controls.Add(Type:=msoControlButton)
    button.Caption = ""
    button.OnAction = "'scrivi """ & button.Caption & """, """ & button.Tag & """'"
End Sub
In particolare su quella riga di codice evidenziata.

Mi chiedo se sia possibile selezionare le celle in relazione a un criterio...

Ecco: sembra che sia riuscito a produrre una routine efficiente:
Private Sub caricaMenu(menu As CommandBar, rigaIniziale As Integer, col As Integer)
    Dim SiglaReparto As String
    Dim button As CommandBarButton
    If Intersect(Bersaglio, Range("TurnoNotte")) Is Nothing = False Then
            SiglaReparto = Right(Range("Reparto").Cells(Bersaglio.Row - Range("Reparto").Row + 1, 1).Formula, 3)
        Else
            SiglaReparto = Left(Range("Reparto").Cells(Bersaglio.Row - Range("Reparto").Row + 1, 1).Formula, 3)
        End If
    For k = 1 To Range("ListaNomiMese").Rows.Count
        If Range("ListaNomiMese").Cells(k, 1).Offset(0, 1).Formula = SiglaReparto Then
            Set button = menu.Controls.Add(Type:=msoControlButton)
            button.Caption = Range("ListaNomiMese").Cells(k, 1).FormulaR1C1
            button.OnAction = "'scrivi """ & button.Caption & """, """ & button.Tag & """'"
        End If
    Next k
    Set button = menu.Controls.Add(Type:=msoControlButton)
    button.Caption = ""
    button.OnAction = "'scrivi """ & button.Caption & """, """ & button.Tag & """'"
End Sub
Con questa, se il turno è attribuito alla cardiologia appare solo un menu di cardiologi, mentre se è attribuito alla medicina appare un menu solo di internisti.

Aggiunta dei turni festivi, con la mia funzione che individua se una data è festiva o no.

Ho ripreso la funzione festivo(valore as String) As Boolean che, data una stringa, ne prende il valore numerico e testa se esprime una data.
Function festivo(valore As String) As Boolean
    If Weekday(CDate(Val(valore) & " " & ActiveSheet.Cells(4, 1).Formula)) = 1 Then festivo = True
End Function
Con questo, individuo nel Calendario quali sono i giorni festivi (per il momento le domeniche, ma la funzione va ampliata a seconda dei giorni festivi diversi dalle domeniche), e vi inserisco anche il reparto che fa il turno festivo.
Mi sembra adeguato inserire il reparto diverso da quello che fa la notte.
La funzione completa ha questo codice:
Dim reparti(1) As String
Dim n As Integer

reparti(0) = "CAR"
reparti(1) = "MED"
 
n = Int(Rnd() * 2)
Range("Reparto").Cells(1, 1).Formula = reparti(n)

For k = 2 To Range("Reparto").Rows.Count
    If n = 0 Then
        n = 1
    Else
        n = 0
    End If
    Range("Reparto").Cells(k, 1).Formula = reparti(n)
    If festivo(Range("Reparto").Cells(k, 1).Offset(0, -2).Formula) Then
        If Range("Reparto").Cells(k, 1).Formula = "MED" Then
            Range("Reparto").Cells(k, 1).Formula = "CAR - MED"
        Else
            Range("Reparto").Cells(k, 1).Formula = "MED - CAR"
        End If
    End If
Next k
E questo è il risultato:

venerdì 26 settembre 2014

Algoritmi di rimescolamento: Knuth.

Vediamo questo "algoritmo di Knuth"...

Ecco: sono riuscito a costruire una routine che, credo, operi secondo questo algoritmo:
Sub funzione()
    Dim carte(3) As Integer
    carte(0) = 123
    carte(1) = 234
    carte(2) = 345
    carte(3) = 456
    Dim i As Integer, j As Integer
    Dim tampone As Integer
    i = 3
    Do While i <> 0
        j = Int(Rnd() * i)
        tampone = carte(j)
        carte(j) = carte(i)
        carte(i) = tampone
        i = i - 1
    Loop
        
    For k = 0 To 3
        Debug.Print carte(k)
    Next k
    Debug.Print "----------------------"
End Sub
Ed ecco una serie di "rimescolamenti"
 345 
 456 
 234 
 123 
----------------------
 234 
 456 
 123 
 345 
----------------------
 456 
 123 
 234 
 345 
----------------------
 456 
 123 
 234 
 345 
----------------------
 234 
 456 
 123 
 345 
----------------------
 234 
 456 
 123 
 345 
----------------------
 456 
 345 
 123 
 234 
----------------------
 456 
 345 
 123 
 234 
----------------------
 456 
 123 
 234 
 345 
----------------------
 456 
 123 
 234 
 345 
----------------------
 456 
 123 
 234 
 345 
----------------------
 456 
 345 
 123 
 234 
----------------------
 234 
 456 
 123 
 345 
----------------------
 234 
 345 
 456 
 123 
----------------------
 234 
 345 
 456 
 123 
----------------------
 456 
 345 
 123 
 234 
----------------------
 234 
 456 
 123 
 345 
----------------------
 234 
 345 
 456 
 123 
----------------------
Sembra che siano abbastanza "casuali"!

Numeri casuali e algoritmo per alternare i due membri di un array con inizio casuale.

Devo attribuire i turni non festivi con un meccanismo casuale, alla medicina o alla cardiologia.
Inizio con uno, quindi alterno i due reparti.

Quindi prima formatto il foglio, quindi sul range Reparto metto il nome del reparto con questo meccanismo casuale.
Ma vediamo come fare una sequenza casuale...

Forse mi conviene fare un array con i nomi dei due reparti.
 Sub main()
    Dim reparti(1) As String
    reparti(0) = "MED"
    reparti(1) = "CAR"
 End Sub
...semplicissimamente elementare e immediato...

Adesso rivediamo un po' come si fanno i numeri casuali...

Sì: ecco un codice che mi permette di vedere la quantità di 1 e di 0 che sono stati estratti a sorte con 50 estrazioni:
 Sub main()
 Dim n As Integer
 Dim nzero As Integer, nuno As Integer
 
 For k = 1 To 50
    n = Int(Rnd() * 2)
    If n = 0 Then nzero = nzero + 1
    If n = 1 Then nuno = nuno + 1
 Next k
 
 Debug.Print nzero
 Debug.Print nuno
 Debug.Print "-----"
 End Sub
Ed ecco i risultati, che mi pare ci stiano piuttosto bene con la "casualità" della cosa:
 26 
 24 
-----
 24 
 26 
-----
 29 
 21 
-----
 24 
 26 
-----
 25 
 25 
-----
 22 
 28 
-----
 27 
 23 
-----
 25 
 25 
-----
 30 
 20 
-----



Fatto questo, estraiamo a sorte dagli elementi del piccolo array:
 Sub main()
 Dim reparti(1) As String
 Dim n As Integer

 
 reparti(0) = "CAR"
 reparti(1) = "MED"
 
 
 For k = 1 To 50
    n = Int(Rnd() * 2)
    Debug.Print reparti(n)
 Next k

 End Sub
...che però mi dà lunghe sequenze sia dell'uno che dell'altro...
CAR
CAR
CAR
MED
MED
CAR
CAR
CAR
CAR
MED
CAR
CAR
MED
CAR
MED
CAR
CAR
MED
CAR
CAR
MED
CAR
MED
CAR
CAR
MED
MED
MED
CAR
CAR
MED
MED
MED
MED
CAR
MED
MED
CAR
MED
MED
MED
CAR
CAR
CAR
CAR
CAR
CAR
MED
CAR
CAR



Meglio ancora, invece, è rendere casuale solo la prima estrazione e successivamente andare per alternanza:
Sub main()
Dim reparti(1) As String
Dim n As Integer

 
reparti(0) = "CAR"
reparti(1) = "MED"
 
n = Int(Rnd() * 2)
Debug.Print reparti(n)

For k = 1 To 50
    If n = 0 Then
        n = 1
    Else
        n = 0
    End If
    Debug.Print reparti(n)
Next k
debug.print "-----"
End Sub
Ed ecco alcune estrazioni ripetute:
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
-----
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
-----
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
-----
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
-----
Sì: inizia casualmente, quindi si alternano regolarmente.

Calcolo domeniche del mese.

Ho creato dunque, sulla base di un esempio trovato in rete, la funzione che trova il numero di giorni del mese.
Adesso ho elaborato io la funzione che trova il numero delle domeniche.
Function sundaysInMonth(anno As Integer, mese As Integer) As Integer
    Dim dt As Date
    Dim numero As Integer
    dt = DateSerial(anno, mese, 1)
    Do While Month(dt) = mese
        If WeekdayName(Weekday(dt), , 1) = "domenica" Then numero = numero + 1
        dt = dt + 1
    Loop
    sundaysInMonth = numero
End Function
L'ho sperimentato con vari mesi del calendario, e funziona.
Bene.

Adesso ho creato un modulo contenente le funzioni per i calcoli relativi alle date, e l'ho esportato in una cartella.
Ci aggiungo anche questa funzione.

Stabilite le domeniche del mese, facciamo i turni totali, semplicemente:
 Sub main()
    Dim turniTotali As Integer, anno As Integer, mese As Integer
    anno = 2014
    mese = 9
    turniTotali = daysInMonth(mese, anno) + sundaysInMonth(mese, anno)
    MsgBox turniTotali
 End Sub
che, ovviamente, dà il numero giusto.

Adesso mi resta da attribuire ai due reparti i turni...

Funzione per ottenere il numero di giorni del mese

Mi chiedo se la funzione DateSerial funzioni anche aumentando mese e anno qualora si vada oltre il limite dell'anno con i calcoli sulle date, ossia se, aggiungendo 1 al mese, nel caso in cui sia dicembre, si vada regolarmente a gennaio dell'anno successivo o se l'anno resti invariato.
Proviamo...
 Sub main()
    Dim mese As Integer, anno As Integer
    Dim dt As Date
    mese = 12
    anno = 2014
    
    dt = DateSerial(anno, mese, 1)
    Debug.Print "questo è il mese di Dicembre: " & dt
    dt = DateSerial(anno, mese + 1, 1)
    Debug.Print "questo è ciò che si ottiene sommando 1 al mese: " & dt
 End Sub
0questo è il mese di Dicembre: 01/12/2014
questo è ciò che si ottiene sommando 1 al mese: 01/01/2015


Sì! Funziona!

Quindi determiniamo quanti giorni ci sono nel mese:
Function DaysInMonth(mese As Integer, anno As Integer) As Integer
    DaysInMonth = DateSerial(anno, mese + 1, 1) - DateSerial(anno, mese, 1)
End Function
Ecco: proviamo questa funzione
 Sub main()
    Dim mese As Integer, anno As Integer
    Dim dt As Date
    mese = 2
    anno = 2012
    Debug.Print DaysInMonth(mese, anno)
 End Sub
 29 

Funziona anche per il mese di febbraio degli anni bisestili! Perfetto.

Funzione DateSerial per ottenere una data fornendo giorno, mese e anno

E' tutto da rifare.
Non avevo capito il meccanismo, ma mi è servito per fare tanta di quella esperienza con Excel...

E inoltre ho costruito un modulo che è sempre valido, con tutti i campi ben nominati, che può tornare utile successivamente, essendo perfettamente riciclabile.

Quello che devo fare innanzitutto è calcolare il numero dei turni.
Ci sono tanti turni quante sono le notti, e quindi i giorni della settimana, più altri uno per ogni domenica.

Cos'è la funzione DateSerial?

Restituisce la data in formato Date, dati l'anno, il mese, il giorno.
Io lo facevo con un altro metodo.
Mettiamoli a confronto tutti e due (sicuramente quello con la funzione DateSerial sarà il migliore).

Provo ad abbozzare il mio metodo.
I parametri mese e anno sono di tipo Integer.
    Dim mese As Integer, anno As Integer
    Dim dt As Date
    mese = Month(Date) + 1
    anno = Year(Date)
    
    dt = CDate("1/" & mese & "/" & anno)
    Debug.Print "Questo è il metodo mio: " & dt
    
    dt = DateSerial(anno, mese, 1)
    Debug.Print "Questo è il metodo con DateSerial: " & dt
Questo è il metodo mio: 01/10/2014
Questo è il metodo con DateSerial: 01/10/2014


Bene: funziona lo stesso, ma DateSerial ovviamente è più comodo e sicuramente più efficiente.

Lo adotto!

giovedì 25 settembre 2014

Il calendario torna bianco quando si va a scrivere una nuova cella

Ho notato che è antipatico se si va a cambiare un nome con le celle del calendario ancora colorate a causa della routine che cerca le occorrenze dei turni.
Si potrebbe fare in modo che la routine scrivi del gestore menu automaticamente ricolori di bianco tutto il calendario.

Potrei prendere quel codice per ricolorare di bianco il calendario, toglierlo dal menu cartella e metterlo da un'altra parte in modo da essere richiamato.

Proviamo...

Ecco: ho messo questo codice sotto forma di una routine togliColore nello stesso modulo dove si trovava già la sola funzione aggiungiFoglio, e ho rinominato il modulo routinesOperative:
Sub togliColore(campo As Range)
    For Each elemento In campo.Cells
        elemento.Interior.Color = vbWhite
    Next
End Sub
e modifico la chiamata da parte della routine dell'evento doppio click del foglio, situata nel modulo della cartella, che già prima doveva fare uso di un codice per "sbiancare" nuovamente il calendario.
Private Sub Workbook_SheetBeforeDoubleClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
    Cancel = True
    If Intersect(Target, Range("ListaNomiMese")) Is Nothing = False Then
        If DoubleClickFlag = False Then
            evidenziaOccorrenze Range("TurnoGiorno")
            evidenziaOccorrenze Range("TurnoNotte")
            DoubleClickFlag = True
        Else
            togliColore Range("TurnoGiorno")
            togliColore Range("TurnoNotte")
            DoubleClickFlag = False
        End If
    End If
End Sub

Ora devo far chiamare questa routine anche dalla routine scrivi in modo che all'atto della scrittura di un nuovo nome, da menu, su una cella, il calendario torni comunque bianco.
Sub scrivi(testo As String, etichetta As String)
    lastCaption = Bersaglio.Formula
    Bersaglio.FormulaR1C1 = testo
    togliColore Range("TurnoGiorno")
    togliColore Range("TurnoNotte")
    conta Range("TurnoGiorno"), 2
    conta Range("TurnoNotte"), 3
End Sub
Vediamo se funziona perché non l'ho ancora sperimentato...

Sì, pare che funzioni!!!

mercoledì 24 settembre 2014

Evidenziare le occorrenze di tutti i turni fatti da una persona.

Ecco, il codice per evidenziare tutti i turni fatti da una stessa persona è stato confezionato, perlomeno nella sua linea base.
Adesso bisogna soltanto andare ad aggiustarlo.
Eccolo:
Sub evidenziaOccorrenze()
    Dim c As Range
    Dim firstAddress As String
    Set c = Range("TurnoGiorno").Find(ActiveCell)
    firstAddress = c.Address
    Do
        Set c = Range("TurnoGiorno").FindNext(c)
        c.Interior.Color = vbGreen
    Loop While c.Address <> firstAddress And c Is Nothing = False
End Sub
e viene chiamato dall'evento doppio click sulla cella della quale si deve cercare il contenuto in tutto il calendario, evento gestito nel modulo della cartella:
Private Sub Workbook_SheetBeforeDoubleClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
    Cancel = True
    evidenziaOccorrenze
End Sub

Ora però lo devo aggiustare: innanzitutto limitare la sua funzione al solo doppio click sulle celle dei turni, o forse, meglio ancora, a quelle della Lista Nomi Mese.
Proviamo:
Sub evidenziaOccorrenze()
    Dim c As Range
    Dim firstAddress As String
    Set c = Range("TurnoGiorno").Find(ActiveCell.Formula)
    If c Is Nothing = False Then
        firstAddress = c.Address
        Do
            c.Interior.Color = vbGreen
            Set c = Range("TurnoGiorno").FindNext(c)
        Loop While c.Address <> firstAddress And c Is Nothing = False
    End If
End Sub
Questa sembra essere la forma definitiva della routine adatta a cercare le occorrenze di un contenuto, dato che quella prima presentava diversi problemi.

Subordinando la sua esecuzione al solo doppio click sulle celle di ListaNomiMese nel modulo della cartella:
Private Sub Workbook_SheetBeforeDoubleClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
    Cancel = True
    If Intersect(Target, Range("ListaNomiMese")) Is Nothing = False Then
        evidenziaOccorrenze
    End If
End Sub
che chiama la routine nel modulo RoutinesDiCalcolo:
Sub evidenziaOccorrenze()
    Dim c As Range
    Dim firstAddress As String
    Set c = Range("TurnoGiorno").Find(ActiveCell.Formula)
    If c Is Nothing = False Then
        firstAddress = c.Address
        Do
            c.Interior.Color = vbGreen
            Set c = Range("TurnoGiorno").FindNext(c)
        Loop While c.Address <> firstAddress And c Is Nothing = False
    End If
End Sub
sembra che la cosa riesca piuttosto bene.

Adesso risistemiamo la ricerca in tutto il calendario e non solo nei turni di giorno...

Ecco: nella routine dell'evento doppio click:
Dim DoubleClickFlag As Boolean
Private Sub Workbook_SheetBeforeDoubleClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
    Cancel = True
    If Intersect(Target, Range("ListaNomiMese")) Is Nothing = False Then
        If DoubleClickFlag = False Then
            evidenziaOccorrenze Range("TurnoGiorno")
            evidenziaOccorrenze Range("TurnoNotte")
            DoubleClickFlag = True
        Else
            For Each elemento In Range("TurnoGiorno").Cells
                elemento.Interior.Color = vbWhite
            Next
            For Each elemento In Range("TurnoNotte").Cells
                elemento.Interior.Color = vbWhite
            Next
            DoubleClickFlag = False
        End If
    End If
End Sub
in modo che al ripetersi del doppio click il foglio calendario ritorni tutto bianco. E la routine, che viene chiamata due volte viene così aggiustata:
Sub evidenziaOccorrenze(campo As Range)
    Dim c As Range
    Dim firstAddress As String
    Set c = campo.Find(ActiveCell.Formula)
    If c Is Nothing = False Then
        firstAddress = c.Address
        Do
            c.Interior.Color = vbGreen
            Set c = campo.FindNext(c)
        Loop While c.Address <> firstAddress And c Is Nothing = False
    End If
End Sub
C'è qualcosa che, a naso, non mi piace nella routine dell'evento doppio click... magari me la rivedo dopo... Comunque funziona egregiamente, è solo una di quelle mie paranoie che a volte sono motivate e a volte no...

Trovare le occorrenze di un contenuto di cella con Find e FindNext

Sub prova()
Set C = Range("insieme").Find("ciccio")
firstaddress = C.Address
Do While C Is Nothing = False And C.Address <> firstaddress
Set C = Range("insieme").FindNext(C)
C.Interior.Color = vbRed
Loop
End Sub
Trova il primo indirizzo che è A3.
Non ne trova più altri perché il Do While impone di uscire dal ciclo quando C.Address sia uguale a firstaddress.

Con questa modifica, invece, sembra funzionare, perché il Do While esce dal ciclo dopo che sia stato individuata la successiva ricorrenza del contenuto della cella.
Sub prova()
Set c = Range("insieme").Find("ciccio")
firstaddress = c.Address
Do
Set c = Range("insieme").FindNext(c)
c.Interior.Color = vbRed
Loop While c Is Nothing = False And c.Address <> firstaddress
End Sub
colora di rosso tutte le celle dell'insieme in cui vi sia il contenuto "ciccio".

Questa tecnica si può usare per il mio programma.

martedì 23 settembre 2014

Nuova routine di calcolo dei turni

Ho creato un'altra variabile pubblica, lastCaption, che memorizza il valore "uscente" dalla cella Bersaglio.
Public Bersaglio As Range
Public lastCaption As String
Public Const Centralina = "Foglio1"
e la testo modificando la routine scrivi:
Sub scrivi(testo As String, etichetta As String)
    lastCaption = Bersaglio.Formula
    Bersaglio.FormulaR1C1 = testo
    MsgBox lastCaption
End Sub
ottenendo una messageBox con il nome "uscente": quindi il procedimento funziona.

Abbiamo quindi un valore uscente e un valore entrante.
Su questi bisognerà fare operazioni opposte: per il valore uscente, trovandolo sulla ListaNomiMese, bisognerà ridurre di 1 il valore dei turni, mentre per il valore entrante bisognerà aumentarlo di 1.
Abbozziamolo: Trovare il valore uscente nella lista nomi. Solito test dei colori...
Sub conta()
    Dim trovato As Range
    Set trovato = Range("ListaNomiMese").Find(lastCaption)
    trovato.Interior.Color = vbCyan
End Sub
che dimostra il corretto funzionamento della cosa.

Una migliore denominazione:
Sub conta()
    Dim vecchioTesto As Range
    Set vecchioTesto = Range("ListaNomiMese").Find(lastCaption)
    vecchioTesto.Interior.Color = vbCyan
End Sub
Bene.

... Ho elaborato tutto! Ecco la routine definitiva (in fase di sperimentazione, speriamo che funzioni)
Sub conta()
    Dim vecchioTesto As Range, nuovoTesto As Range
    If lastCaption <> "" Then
        Set vecchioTesto = Range("ListaNomiMese").Find(lastCaption)
        vecchioTesto.Offset(0, 2).Formula = vecchioTesto.Offset(0, 2).Formula - 1
    End If
    If Bersaglio.Formula <> "" Then
        Set nuovoTesto = Range("ListaNomiMese").Find(Bersaglio.Formula)
        nuovoTesto.Offset(0, 2).Formula = Val(nuovoTesto.Offset(0, 2).Formula) + 1
        Set vecchioTesto = Nothing
        Set nuovoTesto = Nothing
        lastCaption = ""
    End If
End Sub
Molto più rapida!

E ce la analizzeremo per bene...

Prima soluzione per il calcolo: troppo lenta.

Adesso ricreo ex novo la routine conta che pongo nel modulo RoutinesDiCalcolo.
Iniziamo col far leggere dalla routine il contenuto della casella cliccata nella quale è stato scritto il nome, che figura nella variabile globale Bersaglio:
Sub conta()
    MsgBox Bersaglio.FormulaR1C1
End Sub
...giusto per sincerarsi che lo legga, e non potrebbe essere altrimenti, ovviamente!

Adesso dobbiamo trovare questo nome nella ListaNomiMese. Ripassiamo la sintassi del metodo Find.
Sub conta()
    Range("ListaNomiMese").Find(Bersaglio.FormulaR1C1).Interior.Color = vbGreen
End Sub
L'ho testato colorando di verde le celle del range ListaNomiMese in cui figura quel nome, e funziona egregiamente!

Adesso, invece di colorare, dobbiamo mettere un numero 1 nella corrispondente cella del range NumeriGiorni o NumeriMotti.
Per far questo bisogna distinguere se la cella Bersaglio fa parte del range TurnoGiorno o TurnoNotte.
Proviamo.
Possiamo raggiungere la colonna apposita mediante il metodo Offset dell'oggetto Range, anziché tirando in ballo il range NumeroGiorni e NumeroNotti, a patto che i rapporti spaziali fra i ranges ListaNomiMese e questi siano sempre uguali (e non c'è ragione di ritenere che non lo siano).
Testo ancora con i colori l'uso di Offset.
Sub conta()
    Range("ListaNomiMese").Find(Bersaglio.FormulaR1C1).Offset(0, 2).Interior.Color = vbGreen
End Sub
...e funziona.
Adesso facciamo la differenza fra il range di origine TurnoGiorno o TurnoNotte.
Sub conta()
    If Intersect(Bersaglio, Range("TurnoGiorno")) Is Nothing = False Then _
    Range("ListaNomiMese").Find(Bersaglio.FormulaR1C1).Offset(0, 2).Interior.Color = vbGreen
    If Intersect(Bersaglio, Range("TurnoNotte")) Is Nothing = False Then _
    Range("ListaNomiMese").Find(Bersaglio.FormulaR1C1).Offset(0, 3).Interior.Color = vbGreen
End Sub
Perfetto! I colori sono al loro posto. Il codice con l'uso di Offset è senz'altro meno cervellotico di quello che sarebbe il codice che usa i nomi dei ranges.

Adesso al posto dei colori bisogna mettere i numeri.

Ho elaborato qualcosa che funziona, ma è lento, perché passa in rassegna tutte le celle del range, e questa lentezza è fastidiosa.
Sub conta()
Dim cellaTrovata As Range, elemento As Range, foundCell As Range
    For Each elemento In Range("NumeriGiorni").Cells
        elemento.Formula = ""
    Next
        
    For Each elemento In Range("TurnoGiorno").Cells
        If IsEmpty(elemento) = False Then
            Set foundCell = Range("ListaNomiMese").Find(elemento.Formula).Offset(0, 2)
            nome = Val(foundCell.Formula)
            nome = nome + 1
            foundCell.Formula = nome
        End If
    Next
End Sub
Voglio qualcosa di più rapido che non passi in rassegna tutte le celle ma vada direttamente alla cella che serve. Per far questo, devo vedere cosa c'è scritto nella cella prima e dopo.
Credo di dover rivedere prima la routine scrivi...

Revisione della routine copiaLista del modulo formattazioneFoglio.

Una piccola revisione della routine copiaLista del modulo formattazioneFoglio: avevo dimenticato di copiare il reparto di appartenenza dei medici nella tabella ListaReparti accanto alla ListaNomiMese.
Rimedio subito:
Private Sub copiaLista()
    Dim k As Integer, z As Integer
    z = 15 '(posizione iniziale della lista dei nomi del mese)
    For k = 1 To Range("ListaNomi").Rows.Count
        ActiveSheet.Cells(z, 6).FormulaR1C1 = Range("ListaNomi").Cells(k, 1).FormulaR1C1
        ActiveSheet.Cells(z, 7).FormulaR1C1 = Range("ListaNomi").Cells(k, 2).FormulaR1C1
        z = z + 1
    Next k
    ActiveWorkbook.Names.Add Name:="ListaNomiMese", RefersTo:="=" & Range(Cells(15, 6), Cells(z - 1, 6)).Address(True, True)
    Griglia Range("ListaNomiMese")
    
    ActiveWorkbook.Names.Add Name:="ListaReparti", RefersTo:="=" & Range(Cells(15, 7), Cells(z - 1, 7)).Address(True, True)
    Griglia Range("ListaReparti")
    Range("ListaReparti").Interior.Color = RGB(100, 255, 200)

    ActiveWorkbook.Names.Add Name:="NumeriGiorni", RefersTo:="=" & Range(Cells(15, 8), Cells(z - 1, 8)).Address(True, True)
    Griglia Range("NumeriGiorni")
    Range("NumeriGiorni").Interior.Color = RGB(200, 255, 255)
    
    ActiveWorkbook.Names.Add Name:="NumeriNotti", RefersTo:="=" & Range(Cells(15, 9), Cells(z - 1, 9)).Address(True, True)
    Griglia Range("NumeriNotti")
    Range("NumeriNotti").Interior.Color = RGB(200, 100, 255)
    
    
    With ActiveSheet
        With .Cells(Range("ListaNomiMese").Row - 1, 6)
            .Interior.ColorIndex = 6
            .FormulaR1C1 = "NOME"
            .HorizontalAlignment = xlCenter
        End With
        With .Cells(Range("ListaNomiMese").Row - 1, 7)
            .Interior.ColorIndex = 8
            .FormulaR1C1 = "REPARTO"
        End With
        With .Cells(Range("ListaNomiMese").Row - 1, 8)
            .Interior.ColorIndex = 8
            .FormulaR1C1 = "GIORNO"
        End With
        With .Cells(Range("ListaNomiMese").Row - 1, 9)
            .Interior.ColorIndex = 5
            .Font.ColorIndex = 2
            .FormulaR1C1 = "NOTTE"
        End With
        .Columns(6).ColumnWidth = 26
        Exit Sub
        With .Columns(7)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
        
        With .Columns(8)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
    End With
End Sub

Gestione dei menu della mia applicazione Excel

Adesso vengo alla parte del codice che mostra i menu: la routine showMenu.

Eccone il codice:
Public Sub showMenu()
Dim cbr As CommandBar
Dim ctr1 As CommandBarButton
cancellaMenu
Set cbr = Application.CommandBars.Add(Name:="MyCustomToolbar", _
                            Position:=MsoBarPosition.msoBarPopup, _
                            MenuBar:=False, _
                            Temporary:=True)
caricaMenu cbr, ctr1, ActiveSheet, 15, 6 'carica il menu

Application.CommandBars("MyCustomToolbar").ShowPopup    'mostra il menu
End Sub
La routine cancellaMenu serve a cancellare il menu precedente all'atto della creazione di un nuovo menu, altrimenti risulta un errore:
Private Sub cancellaMenu()
On Error GoTo fine
Application.CommandBars("MyCustomToolbar").Delete
fine:
End Sub
La routine showMenu dichiara un oggetto di tipo CommandBar e uno di tipo CommandBarButton.
Quindi crea immediatamente una comandBar con il codice relativo (che non ho approfondito più di tanto, ancora, ma lo prendo per buono).
E quindi chiama la routine caricaMenu fornendo come parametro cbr come CommandBar, ctr1 come CommandBarButton, ActiveSheet come foglio, 15 e 6 come righe iniziale cui troverà il menu de caricare.
Queste routines vanno riscritte.
Innanzitutto, mi sembra piuttosto ridicolo fornire come parametro una variabile oggetto dichiarata e non istanziata.
Vediamo...
Public Sub showMenu()
Dim cbr As CommandBar

cancellaMenu
Set cbr = Application.CommandBars.Add(Name:="MyCustomToolbar", _
                            Position:=MsoBarPosition.msoBarPopup, _
                            MenuBar:=False, _
                            Temporary:=True)
caricaMenu cbr, 15, 6 'carica il menu

Application.CommandBars("MyCustomToolbar").ShowPopup    'mostra il menu
End Sub

Sub caricaMenu(menu As CommandBar, rigaIniziale As Integer, col As Integer)
    Dim button As CommandBarButton
    Dim k As Integer
    k = rigaIniziale
    Do While Cells(k, col).FormulaR1C1 <> ""
        Set button = menu.Controls.Add(Type:=msoControlButton)
        button.Caption = Cells(k, col).FormulaR1C1
        button.Tag = Cells(k, col + 1).FormulaR1C1
        button.OnAction = "'scrivi """ & button.Caption & """, """ & button.Tag & """'"
        k = k + 1
    Loop
    Set button = menu.Controls.Add(Type:=msoControlButton)
        button.Caption = " "
        button.Tag = ""
        button.OnAction = "'scrivi """ & button.Caption & """, """ & button.Tag & """'"
End Sub
Ho eliminato due parametri della funzione caricaMenu: il bottone, che ho dichiarato nella seconda routine direttamente col nome di button, e il foglio, che è ovvio essere il foglio calendario.
Funziona lo stesso, passando come parametri soltanto il menu su cui caricare i bottoni e le coordinate del range da cui attingere i nomi.
Ma alla luce delle mie nuove acquisizioni sulla denominazione dei ranges, posso anche sostituire le coordinate del range Lista Nomi Mese con il nome del range stesso...
Sub caricaMenu(menu As CommandBar, rigaIniziale As Integer, col As Integer)
    Dim button As CommandBarButton
    For k = 1 To Range("ListaNomiMese").Rows.Count
        Set button = menu.Controls.Add(Type:=msoControlButton)
        button.Caption = Range("ListaNomiMese").Cells(k, 1).FormulaR1C1
        button.OnAction = "'scrivi """ & button.Caption & """, """ & button.Tag & """'"
    Next k
End Sub
Sì, così funziona.
Da ultimo, la routine scrivi, che scrive il testo del bottone nella cella che è stata cliccata col destro:
Sub scrivi(testo As String, etichetta As String)
    Bersaglio.FormulaR1C1 = testo
    conta 2, 8
    conta 4, 9
End Sub
e chiama per due volte con parametri diversi la routine conta che fa parte delle routines di calcolo, che analizzerò successivamente.

Faccio una sintesi di tutto il modulo gestoreMenu, che, una volta stabilito che il foglio sia un foglio Calendario, si occupa di individuare in quale campo si debba mostrare un menu e ne gestisce il funzionamento:
Sub avvioMenu(bers As Range)
    If campoConMenu(bers) = True Then
        Set Bersaglio = bers
        showMenu
    End If
End Sub


Function campoConMenu(campo As Range) As Boolean
    If Intersect(campo, Range("TurnoNotte")) Is Nothing = False Then campoConMenu = True
    If Intersect(campo, Range("TurnoGiorno")) Is Nothing = False Then
        If festivo(campo.Offset(0, -1).FormulaR1C1) Then campoConMenu = True
    End If
End Function

Function festivo(valore As String) As Boolean
    If Weekday(CDate(Val(valore) & " " & ActiveSheet.Name)) = 1 Then festivo = True
End Function

Public Sub showMenu()
Dim cbr As CommandBar
cancellaMenu
Set cbr = Application.CommandBars.Add(Name:="MyCustomToolbar", _
                            Position:=MsoBarPosition.msoBarPopup, _
                            MenuBar:=False, _
                            Temporary:=True)
caricaMenu cbr, 15, 6 'carica il menu
Application.CommandBars("MyCustomToolbar").ShowPopup    'mostra il menu
End Sub

Sub caricaMenu(menu As CommandBar, rigaIniziale As Integer, col As Integer)
    Dim button As CommandBarButton
    For k = 1 To Range("ListaNomiMese").Rows.Count
        Set button = menu.Controls.Add(Type:=msoControlButton)
        button.Caption = Range("ListaNomiMese").Cells(k, 1).FormulaR1C1
        button.OnAction = "'scrivi """ & button.Caption & """, """ & button.Tag & """'"
    Next k
End Sub

Private Sub cancellaMenu()
On Error GoTo fine
Application.CommandBars("MyCustomToolbar").Delete
fine:
End Sub

Sub scrivi(testo As String, etichetta As String)
    Bersaglio.FormulaR1C1 = testo
    conta 2, 8
    conta 4, 9
End Sub
Okay!

...dimenticavo l'ultimo bottone muto del menu, per cancellare un nome da una cella.
Rimaneggiamo un po' la caricaMenu.
Sub caricaMenu(menu As CommandBar, rigaIniziale As Integer, col As Integer)
    Dim button As CommandBarButton
    For k = 1 To Range("ListaNomiMese").Rows.Count
        Set button = menu.Controls.Add(Type:=msoControlButton)
        button.Caption = Range("ListaNomiMese").Cells(k, 1).FormulaR1C1
        button.OnAction = "'scrivi """ & button.Caption & """, """ & button.Tag & """'"
    Next k
    Set button = menu.Controls.Add(Type:=msoControlButton)
    button.Caption = ""
    button.OnAction = "'scrivi """ & button.Caption & """, """ & button.Tag & """'"
End Sub
Presto fatto!

lunedì 22 settembre 2014

Gestione del menu popup, prima sezione: le routines che decidono per la comparsa del menu al click destro su una cella

Ho aggiustato le routines per la gestione dei Menu.
Ho lasciato nel modulo della cartella soltanto la routine che gestisce l'evento click destro sul foglio, che ovviamente non può stare da nessun'altra parte, e ho messo tutto il resto in un modulo chiamato gestoreMenu.
Questo il codice del modulo ThisWorkbook:
Private Sub Workbook_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
    If IsDate(Sh.Name) = False Then Exit Sub
    Cancel = True
    avvioMenu Target
End Sub
La riga:
If IsDate(Sh.Name) = False Then Exit Sub
fa sì che se il nome del foglio non corrisponde a una data, ossia se non è uno dei fogli calendario dei mesi, la gestione finisce qui.

Cancel = True
serve a evitare che compaia il menu contestuale di default.

Quindi viene chiamata la routine avvioMenu con il parametro Target, ossia il range che è stato cliccato col destro:
avvioMenu Target


Il resto si svolge nel modulo gestoreMenu:
Questa è la routine avvioMenu che accetta il range per parametro, alla quale viene passato Target.
Sub avvioMenu(bers As Range)
    If campoConMenu(bers) = True Then
        Set Bersaglio = bers
        showMenu
    End If
End Sub
Se il campo è uno di quelli per i quali al click destro deve apparire un menu, allora nella variabile oggetto Bersaglio viene copiato il range Target passato come parametro alla routine (ossia la cella cliccata, sulla quale deve apparire la scritta), e viene chiamata la routine showMenu.
Se il campo è di quelli per cui deve apparire un menu, viene stabilito dalla funzione campoConMenu
Function campoConMenu(campo As Range) As Boolean
    If Intersect(campo, Range("TurnoNotte")) Is Nothing = False Then campoConMenu = True
    If Intersect(campo, Range("TurnoGiorno")) Is Nothing = False Then
        If festivo(campo.Offset(0, -1).FormulaR1C1) Then campoConMenu = True
    End If
End Function
Questa stabilisce due condizioni per cui il campo debba possedere un menu che appare al click destro:
1) Il campo appartine al campo TurnoNotte;
If Intersect(campo, Range("TurnoNotte")) Is Nothing = False Then campoConMenu = True


2) Il campo appartiene al range TurnoGiorno e il valore della data che appare alla colonna 1 in corrispondenza della riga del campo rappresenta un giorno festivo
    If Intersect(campo, Range("TurnoGiorno")) Is Nothing = False Then
        If festivo(campo.Offset(0, -1).FormulaR1C1) Then campoConMenu = True
    End If


Se il giorno è festivo, viene stabilito da questa funzione:
Function festivo(valore As String) As Boolean
    If Weekday(CDate(Val(valore) & " " & ActiveSheet.Name)) = 1 Then festivo = True
End Function

Quindi, in sintesi, abbiamo tre routines che decidono se al click destro su una cella debba apparire il menu tramite la routine showMenu:
Sub avvioMenu(bers As Range)
    If campoConMenu(bers) = True Then
        Set Bersaglio = bers
        showMenu
    End If
End Sub


Function campoConMenu(campo As Range) As Boolean
    If Intersect(campo, Range("TurnoNotte")) Is Nothing = False Then campoConMenu = True
    If Intersect(campo, Range("TurnoGiorno")) Is Nothing = False Then
        If festivo(campo.Offset(0, -1).FormulaR1C1) Then campoConMenu = True
    End If
End Function

Function festivo(valore As String) As Boolean
    If Weekday(CDate(Val(valore) & " " & ActiveSheet.Name)) = 1 Then festivo = True
End Function

Determinazione dell'appartenenza di una cella a un range: metodo Intersect.

Bene.
Ora passiamo ai menu, dopo la lunga digressione.

Avevo visto che il far comparire o meno un menu dipende dalla funzione campoConMenu
'Al servizio della precedente: identifica se un campo debba dare origine a un menu

Function campoConMenu(campo As Range) As Boolean
    Dim valore As String    'determina il valore che c'è nella prima cella della fila
    valore = Cells(campo.Row, 1).FormulaR1C1
    If campo.Row > 3 And campo.Row < 35 Then
        If campo.Column = 4 And valore <> "" Then campoConMenu = True
        On Error Resume Next
        If campo.Column = 2 And festivo(valore) Then campoConMenu = True
    End If
End Function
Come la si può modificare in relazione alle denominazioni di campi che abbiamo fatto?

E qui mi serve il modo di determinare se una cella appartiene a un range o no.

Trovo la funzione Match...

La devo studiare.
Eccola! Intersect
La sintassi è
Intersect(range, range)
che restituisce un range, che sarebbe il range di intersezione fra i due ranges.
In caso di non intersezione restituisce Nothing.
E quindi io ci scrivo, per determinare se una cella cliccata appartiene a un range, questa funzione:
Function CellaDiRange(cella As Range, campo As Range) As Boolean
    If Intersect(cella, campo) Is Nothing = False Then CellaDiRange = True
End Function
che funziona perfettamente! L'ho testata così:
Private Sub Workbook_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
    If IsDate(Sh.Name) = False Then Exit Sub
    Cancel = True
    MsgBox CellaDiRange(Target, Range("TurnoNotte"))
    Exit Sub
    If campoConMenu(Target) Then
        Set Bersaglio = Target
        showMenu
    End If
End Sub
(l'Exit Sub, che uso spesso per testare, interrompe il flusso del programma al punto che mi interessa senza rischiare di confondermi le idee con quello che viene dopo).

Denominazione di tutti i campi del programma

Ho fatto tutta una rivoluzione per quanto riguarda la denominazione dei ranges nel foglio Calendario.
Con questa nomino i campi del Calendario e la Lista Nomi del foglio Nascosto:
Sub nominaCampi()
    ActiveWorkbook.Names.Add Name:="ListaNomi", RefersTo:="=Nascosto!" & NomeRangeMisurato(1, 1, 1, 2, Sheets("Nascosto"), "")
    ActiveWorkbook.Names.Add Name:="TurnoGiorno", RefersTo:="=" & NomeRangeMisurato(5, 1, 2, 2, ActiveSheet, "")
    ActiveWorkbook.Names.Add Name:="Reparto", RefersTo:="=" & NomeRangeMisurato(5, 1, 3, 3, ActiveSheet, "")
    ActiveWorkbook.Names.Add Name:="TurnoNotte", RefersTo:="=" & NomeRangeMisurato(5, 1, 4, 4, ActiveSheet, "")
End Sub


Con questa, poi, una volta copiati i nomi dalla Lista Nomi del foglio Nascosto, nomino i ranges Lista Nomi Mese, Lista Reparti, Numeri Giorni, Numeri Notti (ossia, per gli ultimi due, le celle dove vengono segnati i turni diurni (solo festivi) e quelli notturni)
Sub copiaLista()
    Dim k As Integer, z As Integer
    z = 15 '(posizione iniziale della lista dei nomi del mese)
    For k = 1 To Range("ListaNomi").Rows.Count
        ActiveSheet.Cells(z, 6).FormulaR1C1 = Range("ListaNomi").Cells(k, 1).FormulaR1C1
        z = z + 1
    Next k
    ActiveWorkbook.Names.Add Name:="ListaNomiMese", RefersTo:="=" & Range(Cells(15, 6), Cells(z - 1, 6)).Address(True, True)
    Griglia Range("ListaNomiMese")
    
    ActiveWorkbook.Names.Add Name:="ListaReparti", RefersTo:="=" & Range(Cells(15, 7), Cells(z - 1, 7)).Address(True, True)
    Griglia Range("ListaReparti")
    Range("ListaReparti").Interior.Color = RGB(100, 255, 200)

    ActiveWorkbook.Names.Add Name:="NumeriGiorni", RefersTo:="=" & Range(Cells(15, 8), Cells(z - 1, 8)).Address(True, True)
    Griglia Range("NumeriGiorni")
    Range("NumeriGiorni").Interior.Color = RGB(200, 255, 255)
    
    ActiveWorkbook.Names.Add Name:="NumeriNotti", RefersTo:="=" & Range(Cells(15, 9), Cells(z - 1, 9)).Address(True, True)
    Griglia Range("NumeriNotti")
    Range("NumeriNotti").Interior.Color = RGB(200, 100, 255)
    
    
    With ActiveSheet
        With .Cells(Range("ListaNomiMese").Row - 1, 6)
            .Interior.ColorIndex = 6
            .FormulaR1C1 = "NOME"
            .HorizontalAlignment = xlCenter
        End With
        With .Cells(Range("ListaNomiMese").Row - 1, 7)
            .Interior.ColorIndex = 8
            .FormulaR1C1 = "REPARTO"
        End With
        With .Cells(Range("ListaNomiMese").Row - 1, 8)
            .Interior.ColorIndex = 8
            .FormulaR1C1 = "GIORNO"
        End With
        With .Cells(Range("ListaNomiMese").Row - 1, 9)
            .Interior.ColorIndex = 5
            .Font.ColorIndex = 2
            .FormulaR1C1 = "NOTTE"
        End With
        .Columns(6).ColumnWidth = 26
        Exit Sub
        With .Columns(7)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
        
        With .Columns(8)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
    End With
End Sub


Mi rendo conto che per il foglio calendario, nella routine nominaCampi, non ho denominato il range dei giorni del mese.
Presto fatto:
Sub nominaCampi()
    ActiveWorkbook.Names.Add Name:="GiorniDelMese", RefersTo:="=" & NomeRangeMisurato(5, 1, 1, 1, ActiveSheet, "")
    ActiveWorkbook.Names.Add Name:="ListaNomi", RefersTo:="=Nascosto!" & NomeRangeMisurato(1, 1, 1, 2, Sheets("Nascosto"), "")
    ActiveWorkbook.Names.Add Name:="TurnoGiorno", RefersTo:="=" & NomeRangeMisurato(5, 1, 2, 2, ActiveSheet, "")
    ActiveWorkbook.Names.Add Name:="Reparto", RefersTo:="=" & NomeRangeMisurato(5, 1, 3, 3, ActiveSheet, "")
    ActiveWorkbook.Names.Add Name:="TurnoNotte", RefersTo:="=" & NomeRangeMisurato(5, 1, 4, 4, ActiveSheet, "")
End Sub
Mi rendo conto che con il codice che ho creato risulta facilissimo denominare ranges dopo aver preso le misure, a tutto vantaggio della chiarezza della programmazione e della leggibilità del codice.

domenica 21 settembre 2014

Creazione di una routine separata per mettere una griglia alle tabelle.

Dato che il codice per fare una griglia coi bordi delle celle è fortemente ripetitivo e pesante, l'ho inglobato in una routine a parte da usare con il range da "grigliare" come parametro:
Private Sub Griglia(campo As Range)
        With campo.Borders(xlEdgeLeft)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With campo.Borders(xlEdgeTop)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With campo.Borders(xlEdgeBottom)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With campo.Borders(xlEdgeRight)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With campo.Borders(xlInsideVertical)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With campo.Borders(xlInsideHorizontal)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
End Sub
e funziona perfettamente, chiamato in questo modo, in due occasioni diverse:
Private Sub creazioneGriglia()
'CREAZIONE DELLA GRIGLIA COI BORDI
Griglia Range(Cells(3, 1), Cells(Riga, 4))
End Sub
...questa è per "grigliare" il Calendario;

Griglia Range("ListaNomiMese")
...e questa è per "grigliare" la lista dei nomi del mese.

Funziona!

Denominazione di ranges con le liste dei nomi.

Devo riorganizzare un po' la questione della denominazione dei ranges.
Riprendiamo la routine copiaLista
Sub copiaLista()
    Dim k As Integer, z As Integer
    k = 1
    z = 1
    Do While Range("ListaNomi").Cells(k, 1) <> ""
        Range("ColonnaNomiMese").Cells(z, 1).FormulaR1C1 = _
        Range("ListaNomi").Cells(k, 1).FormulaR1C1
        Range("ColonnaNomiMese").Cells(z, 2).FormulaR1C1 = _
        Range("ListaNomi").Cells(k, 2).FormulaR1C1
        k = k + 1
        z = z + 1
    Loop
    With ActiveSheet
        With .Cells(3, 6)
            .Interior.ColorIndex = 6
            .FormulaR1C1 = "NOME"
            .HorizontalAlignment = xlCenter
        End With
       
        With .Cells(3, 8)
            .Interior.ColorIndex = 8
            .FormulaR1C1 = "GIORNO"
        End With
        With .Cells(3, 9)
            .Interior.ColorIndex = 5
            .Font.ColorIndex = 2
            .FormulaR1C1 = "NOTTE"
        End With
        .Columns(6).ColumnWidth = 26
        
        With .Columns(7)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
        
        With .Columns(8)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
    End With
End Sub
La vorrei modificare nel senso che prima vengano copiate le voci dalla Lista Nomi del foglio Nascosto alla colonna 6 del foglio Calendario, procedendo soltanto dopo alla denominazione del range ListaNomiMese.
Ci provo...
Isolo la parte della routine che serve all'effettiva copiatura della lista:
    Dim k As Integer, z As Integer
    k = 1
    z = 15
    Do While Range("ListaNomi").Cells(k, 1) <> ""
        Range("ColonnaNomiMese").Cells(z, 1).FormulaR1C1 = _
        Range("ListaNomi").Cells(k, 1).FormulaR1C1
        Range("ColonnaNomiMese").Cells(z, 2).FormulaR1C1 = _
        Range("ListaNomi").Cells(k, 2).FormulaR1C1
        k = k + 1
        z = z + 1
    Loop
E mi rendo conto che il conosco già la lunghezza della Lista Nomi, trovata per mezzo della routine NomeRangeMisurato.
Quindi posso riorganizzare la cosa in modo diverso, spostando la denominazione della Lista Nomi Mese successivamente, quando questa sarà stata riempita.


Mentre cercavo di riscrivere il codice, mi sono accorto che la routine NomeRangeMisurato presentava un difetto, che ho corretto:
Function NomeRangeMisurato(rigaIniziale As Integer, colonnaIniziale As Integer, colonnaFinale As Integer, stringaLimite As String) As String
    Dim k As Integer
    k = rigaIniziale
    Do While Cells(k, colonnaIniziale).FormulaR1C1 <> stringaLimite
        k = k + 1
    Loop
    NomeRangeMisurato = Range(Cells(rigaIniziale, colonnaIniziale), Cells(k - 1, colonnaFinale)).Address(True, True)
End Function
In questa viene definita la colonna in cui cercare soltanto con Cells, che senza specificare il foglio mi usa le celle del foglio attivo, cosicché quando vado a misurare un range situato sul foglio nascosto mi dà un valore diverso.
L'ho corretto specificando fra i parametri il foglio e usandolo prima di Cells, e il problema mi risulta ora corretto:
Function NomeRangeMisurato(rigaIniziale As Integer, colonnaIniziale As Integer, colonnaFinale As Integer, foglio As Worksheet, stringaLimite As String) As String
    Dim k As Integer
    k = rigaIniziale
    Do While foglio.Cells(k, colonnaIniziale).FormulaR1C1 <> stringaLimite
        k = k + 1
    Loop
    NomeRangeMisurato = Range(Cells(rigaIniziale, colonnaIniziale), Cells(k - 1, colonnaFinale)).Address(True, True)
End Function
chiamata con:
    ActiveWorkbook.Names.Add Name:="ListaNomi", RefersTo:="=Nascosto!" & NomeRangeMisurato(1, 1, 2, Sheets("Nascosto"), "")


Corretto questo, vado a riscrivere la routine copiaLista
    Dim k As Integer, z As Integer
    z = 15 '(posizione iniziale della lista dei nomi del mese)
    For k = 1 To Range("ListaNomi").Rows.Count
        ActiveSheet.Cells(z, 6).FormulaR1C1 = Range("ListaNomi").Cells(k, 1).FormulaR1C1
        z = z + 1
    Next k
...che funziona egregiamente, e si basa, rispetto a quella di prima, sul fatto che conosciamo già la quantità di righe del range ListaNomi.

Adesso nomino il nuovo range ListaNomiMese, di cui conosco parimenti la lunghezza.
Sub copiaLista()
    Dim k As Integer, z As Integer
    z = 15 '(posizione iniziale della lista dei nomi del mese)
    For k = 1 To Range("ListaNomi").Rows.Count
        ActiveSheet.Cells(z, 6).FormulaR1C1 = Range("ListaNomi").Cells(k, 1).FormulaR1C1
        z = z + 1
    Next k
    ActiveWorkbook.Names.Add Name:="ListaNomiMese", RefersTo:="=" & Range(Cells(15, 6), Cells(z - 1, 9)).Address(True, True)
...e poi, selezionando questo, procedo a tutta la costruzione di una griglia e agli aspetti "estetici" (lavoro bruto)...
    
    Range("ListaNomiMese").Select
    With Selection.Borders(xlEdgeLeft)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With Selection.Borders(xlEdgeTop)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With Selection.Borders(xlEdgeBottom)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With Selection.Borders(xlEdgeRight)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With Selection.Borders(xlInsideVertical)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With Selection.Borders(xlInsideHorizontal)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
  
    
    With ActiveSheet
        With .Cells(Range("ListaNomiMese").Row - 1, 6)
            .Interior.ColorIndex = 6
            .FormulaR1C1 = "NOME"
            .HorizontalAlignment = xlCenter
        End With
        With .Cells(Range("ListaNomiMese").Row - 1, 7)
            .Interior.ColorIndex = 8
            .FormulaR1C1 = "REPARTO"
        End With
        With .Cells(Range("ListaNomiMese").Row - 1, 8)
            .Interior.ColorIndex = 8
            .FormulaR1C1 = "GIORNO"
        End With
        With .Cells(Range("ListaNomiMese").Row - 1, 9)
            .Interior.ColorIndex = 5
            .Font.ColorIndex = 2
            .FormulaR1C1 = "NOTTE"
        End With
        .Columns(6).ColumnWidth = 26
        Exit Sub
        With .Columns(7)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
        
        With .Columns(8)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
    End With
End Sub

Funzione per la delimitazione di un range con successiva denominazione.

Bene.
Ho elaborato su una cartella indipendente una funzione che delimiti un range ricercando in una colonna una "stringa limite", ossia una cella che contenga una determinata stringa, che nella fattispecie è la stringa vuota.
Così facendo una lista può essere tutta compresa in un range, fino alla fine, in quanto la cella successiva conterrà una stringa vuota.
Ecco la funzione:
Function misuraRange(rigaIniziale As Integer, colonnaIniziale As Integer, colonnaFinale As Integer, stringaLimite As String) As Range
    Dim k As Integer
    k = rigaIniziale
    Do While Cells(k, colonnaIniziale).FormulaR1C1 <> stringaLimite
        k = k + 1
    Loop
    Set misuraRange = Range(Cells(rigaIniziale, colonnaIniziale), Cells(k - 1, colonnaFinale))
End Function
Ed ecco come l'ho testata:
Sub Denomina()
    Names.Add Name:="CampoDiCelle", RefersTo:="=" & misuraRange(1, 1, 2, "").Address(True, True)
    Range("CampoDiCelle").Select
End Sub
Mettendo una lista di nomi nella colonna 1 (A), la funzione misuraRange mi restituisce il range limitato in basso dalla stringa vuota nella colonna 1. Quindi lo denomino.
Potrei fare in modo che la funzione mi restituisca direttamente la descrizione del range in notazione A1?
Proviamo
Però vorrei denominare meglio questa funzione, perché misuraRange non mi ispira molto.

RangeMisurato forse è un po' meglio.
Function RangeMisurato(rigaIniziale As Integer, colonnaIniziale As Integer, colonnaFinale As Integer, stringaLimite As String) As Range
    Dim k As Integer
    k = rigaIniziale
    Do While Cells(k, colonnaIniziale).FormulaR1C1 <> stringaLimite
        k = k + 1
    Loop
    Set RangeMisurato = Range(Cells(rigaIniziale, colonnaIniziale), Cells(k - 1, colonnaFinale))
End Function
Ed ecco un'altra funzione che restituisce il nome del range nella giusta notazione:
Function NomeRangeMisurato(rigaIniziale As Integer, colonnaIniziale As Integer, colonnaFinale As Integer, stringaLimite As String) As String
    Dim k As Integer
    k = rigaIniziale
    Do While Cells(k, colonnaIniziale).FormulaR1C1 <> stringaLimite
        k = k + 1
    Loop
    NomeRangeMisurato = Range(Cells(rigaIniziale, colonnaIniziale), Cells(k - 1, colonnaFinale)).Address(True, True)
End Function
e funziona con questo codice:
Names.Add Name:="CampoDiCelle", RefersTo:="=" & NomeRangeMisurato(1, 1, 2, "")
Le due funzioni accettano come parametri:
  • la riga iniziale, che è quella nella quale viene ricercata la stringa limite;
  • la colonna iniziale;
  • la colonna finale;
  • la stringa limite che segna la fine del range in senso verticale
Okay!

Creazione dei menu e problemi nuovi da studiare...

E veniamo alla creazione di menu al click destro sulle celle del Calendario.

Il codice che governa questo è inserito nel modulo ThisWorkbook, in quanto parte dalla gestione dell'evento click destro sul foglio di lavoro, che viene gestito in questa sede.
Ecco la routine dell'evento:
Private Sub Workbook_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
    If IsDate(Sh.Name) = False Then Exit Sub
    Cancel = True
    If campoConMenu(Target) Then
        Set Bersaglio = Target
        showMenu
    End If
End Sub
Innanzitutto bisogna accertarsi che il nome del foglio sia quello di una data, perché in caso contrario il codice funzionerebbe per qualunque foglio, incluso il foglio base e quello nascosto (qualora non fosse nascosto), o comunque qualunque foglio volessimo per qualunque motivo inserire nella cartella.
Questo accertamento viene fatto con la funzione IsDate:
If IsDate(Sh.Name) = False Then Exit Sub
che significa: "Se il nome del foglio non è una data, esci dalla routine", concludendo la gestione dell'evento in un nulla di fatto.

Cancel = True
Questo qui sopra serve per evitare che appaia anche il menu popup di default per il click destro sul foglio di lavoro, che altrimenti apparirebbe dopo il menu da me desiderato.

    If campoConMenu(Target) Then
        Set Bersaglio = Target
        showMenu
    End If
Questo subordina le istruzioni Set Bersaglio=Target e showmenu al fatto che il campo cliccato sia uno di quelli dal quale vogliamo ottenere un menu, altrimenti il menu verrebbe fuori da qualunque cella del foglio.

Bersaglio è una variabile oggetto globale, definita nel modulo delle variabili globali, che assume il valore della cella cliccata, in modo da averla come obiettivo nello scrivere poi ciò che figura sul menu nella giusta cella.
Potrei anche tentare di passarla come parametro, e forse ci proverò.
Ho preferito al momento usarla come variabile globale anziché passarla come parametro solo perché la sintassi del passaggio di parametri nella gestione dell'evento click su menu di Excel è strana e complicata, e non sono riuscito a trovare alcun modo per passare un parametro di tipo oggetto, leggendo addirittura su alcuni forum che non ne esisterebbe proprio la possibilità! Per questo motivo lo uso in questo modo, altrimenti non ne vengo più fuori!


La funzione campoConMenu(Target) identifica se il campo cliccato è uno di quelli su cui si desidera appaia un menu.
Eccola:
Function campoConMenu(campo As Range) As Boolean
    Dim valore As String    'determina il valore che c'è nella prima cella della fila
    valore = Cells(campo.Row, 1).FormulaR1C1
    If campo.Row > 3 And campo.Row < 35 Then
        If campo.Column = 4 And valore <> "" Then campoConMenu = True
        On Error Resume Next
        If campo.Column = 2 And festivo(valore) Then campoConMenu = True
    End If
End Function
Ma non potremmo rinominare le varie sezioni del Foglio Calendario, in modo da rendere più leggibile e maneggevole la cosa?

Proviamo...

Però dobbiamo ritornare al modulo formattazioneFoglio.
La sequenza delle routines chiamate dalla routine principale del modulo è questa:
scriviIntestazioni
    
aggiuntaDate

creazioneGriglia

scrittaDiCoda

copiaLista

scorri

impostazioniStampa
Quella marcata in rosso è quella in cui vengono rinominati la Lista Nomi del foglio Nascosto e la Lista Nomi del Mese del foglio Calendario.
E se inserissi prima di questa una routine che rinomina tutti i campi una volta per tutte, nel momento in cui tutto il foglio è stato formattato?

Ci provo...

Sub nominaCampi()
    ActiveWorkbook.Names.Add Name:="ListaNomi", RefersToR1C1:="=Nascosto!C1:C2"
    ActiveWorkbook.Names.Add Name:="ListaNomiMese", RefersToR1C1:="=C6:C7"
End Sub




Sub copiaLista()


    
    Dim k As Integer, z As Integer
    k = 1
    z = 15
    Do While Range("ListaNomi").Cells(k, 1) <> ""
...
Ecco, prima della routine copiaLista ho inserito la routine nominaCampi spostando in essa il codice per nominare i due campi che avevo già nominato, e nella quale potrò aggiungere il codice per nominare gli altri campi.
Vediamo se funziona, inserendo la chiamata di quest'altra routine in quella principale...
scriviIntestazioni
    
aggiuntaDate

creazioneGriglia

scrittaDiCoda

nominaCampi

copiaLista

scorri

impostazioniStampa
Sì, funziona perfettamente.

Allora rinominiamo il resto agendo sulla nominaCampi.
    ActiveWorkbook.Names.Add Name:="GiorniDelMese", RefersToR1C1:="=C1"
    ActiveWorkbook.Names.Add Name:="TurniFestivi", RefersToR1C1:="=C2"
    ActiveWorkbook.Names.Add Name:="TurniNotturni", RefersToR1C1:="=C4"
    ActiveWorkbook.Names.Add Name:="NomiReparti", RefersToR1C1:="=C3"
    
    ActiveWorkbook.Names.Add Name:="ListaNomi", RefersToR1C1:="=Nascosto!C1:C2"
    ActiveWorkbook.Names.Add Name:="ListaNomiMese", RefersToR1C1:="=C6:C7"
Ecco, ho rinominato le colonne del calendario del Foglio Calendario.
Ma invece di rinominare le colonne potrei delimitare anche i campi a un certo numero di righe, almeno per alcuni.
Provo a limitare il numero di righe del campo Lista Nomi, basandomi sulla ricerca della prima cella vuota andando dall'alto in basso.
E qui mi si apre tutta una serie di problemi...

Denominazione di ranges per un codice più leggibile.

Bene.
Ho analizzato due parti del mio programma:
  • Caricamento di un nuovo foglio
  • Formattazione del nuovo foglio.
Adesso abbiamo il foglio pronto per le operazioni che vi si possono fare sopra.

Il passo successivo è quello di far apparire dei menu che riportino i nomi scritti nella lista, in modo da inserirli nei turni e fare i calcoli.

Voglio però provare a rifare le procedure che caricano i nomi della lista alla luce delle mie acquisizioni sulla denominazione dei Ranges di Excel.
Riprendo la routine:
Sub copiaMenu()

    Dim k As Integer, z As Integer
    k = 1
    z = 4
    Do While Sheets("Nascosto").Cells(k, 1) <> ""
        ActiveSheet.Cells(z, 6).FormulaR1C1 = _
        Sheets("Nascosto").Cells(k, 1).FormulaR1C1
        ActiveSheet.Cells(z, 7).FormulaR1C1 = _
        Sheets("Nascosto").Cells(k, 2).FormulaR1C1
        k = k + 1
        z = z + 1
    Loop
    With ActiveSheet
        With .Cells(3, 6)
            .Interior.ColorIndex = 6
            .FormulaR1C1 = "NOME"
            .HorizontalAlignment = xlCenter
        End With
       
        With .Cells(3, 8)
            .Interior.ColorIndex = 8
            .FormulaR1C1 = "GIORNO"
        End With
        With .Cells(3, 9)
            .Interior.ColorIndex = 5
            .Font.ColorIndex = 2
            .FormulaR1C1 = "NOTTE"
        End With
        .Columns(6).ColumnWidth = 26
        
        With .Columns(7)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
        
        With .Columns(8)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
    End With
End Sub
Innanzitutto la denominazione della routine stessa è confusiva, in quanto quello che io copio non è un menu ma una lista, un elenco.
Rivediamo un po' di convenzioni di denominazione, per evitare la confusione...

Il foglio "Nascosto" dove sta memorizzata la lista di nomi lo chiamiamo foglio nascosto o anche archivio;
La lista di nomi la chiamiamo elenco dei nomi o lista dei nomi;
Il foglio con il calendario, dove la lista dei nomi viene copiata dal foglio nascosto, lo chiamiamo foglio calendario;
La lista dei nomi ricopiata sul foglio calendario la chiamiamo lista nomi del mese.


Ora rinominiamo la lista dei nomi!

Non ricordo bene la sintassi dell'aggiunta di nomi: carico una cartella di lavoro per ricavarmela da una macro...
    ActiveWorkbook.Names.Add Name:="CampoDiCelle", RefersToR1C1:= _
        "=Foglio1!R1C6:R10C6"
Cerchiamo di analizzarla per ricordarla meglio.
Innanzitutto bisogna aggiungere un nome alla lista dei nomi
ActiveWorkbook.Names.Add
Quindi si specifica il nome:
Name:="CampoDiCelle"
E poi c'è la parte relativa al campo di celle selezionato:
RefersToR1C1:="=Foglio1!R1C6:R10C6".
Di questo bisogna stare attenti a mettere un altro segno uguale prima del nome del foglio, fra virgolette.
Inseriamo il procedimento!
Ho rinominato più correttamente la routine copiaLista anziché copiaMenu.
Sub copiaLista()
    
    Dim k As Integer, z As Integer
    k = 1
    z = 4
    Do While Sheets("Nascosto").Cells(k, 1) <> ""
        ActiveSheet.Cells(z, 6).FormulaR1C1 = _
        Sheets("Nascosto").Cells(k, 1).FormulaR1C1
        ActiveSheet.Cells(z, 7).FormulaR1C1 = _
        Sheets("Nascosto").Cells(k, 2).FormulaR1C1
        k = k + 1
        z = z + 1
    Loop
    With ActiveSheet
        With .Cells(3, 6)
            .Interior.ColorIndex = 6
            .FormulaR1C1 = "NOME"
            .HorizontalAlignment = xlCenter
        End With
       
        With .Cells(3, 8)
            .Interior.ColorIndex = 8
            .FormulaR1C1 = "GIORNO"
        End With
        With .Cells(3, 9)
            .Interior.ColorIndex = 5
            .Font.ColorIndex = 2
            .FormulaR1C1 = "NOTTE"
        End With
        .Columns(6).ColumnWidth = 26
        
        With .Columns(7)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
        
        With .Columns(8)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
    End With
End Sub
Modifichiamo...

Questo è il codice che rinomina le liste "Lista dei Nomi" e "Lista dei Nomi del Mese":
    ActiveWorkbook.Names.Add Name:="ListaNomi", RefersToR1C1:="=Nascosto!C1:C2"
    ActiveWorkbook.Names.Add Name:="ListaNomiMese", RefersToR1C1:="=C6:C7"

Ho verificato il funzionamento, essendo ancora inesperto di questa pratica, inserendo provvisoriamente comandi che modificassero il colore delle celle di un range...

Adesso devo copiare i nomi da una lista all'altra.
Questo è il codice originario:
    Dim k As Integer, z As Integer
    k = 1
    z = 4
    Do While Sheets("Nascosto").Cells(k, 1) <> ""
        ActiveSheet.Cells(z, 6).FormulaR1C1 = _
        Sheets("Nascosto").Cells(k, 1).FormulaR1C1
        ActiveSheet.Cells(z, 7).FormulaR1C1 = _
        Sheets("Nascosto").Cells(k, 2).FormulaR1C1
        k = k + 1
        z = z + 1
    Loop
che, definiti i punti di inizio delle due liste, k per quella del foglio Nascosto e z per quella del Mese, Procede, finché il contenuto di una cella della Lista dei Nomi non è nullo, a inserire nella Lista dei Nomi del Mese il valore della cella k della colonna 1 nella cella z della colonna 1 il valore trovato nella lista dei nomi; quindi scorre in avanti i due indici k e z.

Modifichiamola con i nomi:
    Dim k As Integer, z As Integer
    k = 1
    z = 4
    Do While Range("ListaNomi").Cells(k, 1) <> ""
        Range("ListaNomiMese").Cells(z, 1).FormulaR1C1 = _
        Range("ListaNomi").Cells(k, 1).FormulaR1C1
        Range("ListaNomiMese").Cells(z, 2).FormulaR1C1 = _
        Range("ListaNomi").Cells(k, 2).FormulaR1C1
        k = k + 1
        z = z + 1
    Loop
e funziona! Il vantaggio è di avere un codice molto più leggibile che evita evoluzioni cervellotiche e confusive diminuendo le possibilità di errore.

Il resto è formattazione della tabella della Lista Nomi del Mese, che magari vedrò successivamente...

sabato 20 settembre 2014

Parte seconda del mio programma: Sezione seconda: le routines che formattano il foglio.

La prima routine chiamata nel modulo relativo alla formattazione del foglio si chiama scriviIntestazioni e serve per ottenere queste prime righe del foglio:



Da notare, però, che il nome del mese non viene aggiunto ancora, perché se ne occuperà la routine successiva per l'aggiunta delle date. Il codice è questo:
Private Sub scriviIntestazioni()
     'ALTEZZA GENERALE DELLE RIGHE
     
     Cells.Select
     Selection.RowHeight = 12
     'AGGIUNTA DELL'INTESTAZIONE RIGA 1
    Range(Cells(Riga, Colonna), Cells(Riga, Colonna + 3)).Select
    Selection.Merge
    Selection.RowHeight = 17
    Selection.VerticalAlignment = xlCenter
    Selection.HorizontalAlignment = xlCenter
    Selection.Font.Bold = True
    Selection.FormulaR1C1 = "GUARDIA MEDICA AREA FUNZIONALE OMOGENEA"
    
    Riga = Riga + 1
    
    'AGGIUNTA DELL'INTESTAZIONE RIGA 2
    Range(Cells(Riga, Colonna), Cells(Riga, Colonna + 3)).Select
    Selection.Merge
    Selection.RowHeight = 17
    Selection.VerticalAlignment = xlCenter

    Selection.HorizontalAlignment = xlCenter
    Selection.Font.Bold = True
    Selection.FormulaR1C1 = "MEDICINA-CARDIOLOGIA"
    Selection.Font.ColorIndex = 48
    
    Riga = Riga + 1
    
    'SCRIVE LE PRIME 2 RIGHE DELLA TABELLA
    Columns(Colonna).HorizontalAlignment = xlLeft
    With Cells(Riga, Colonna)
        .FormulaR1C1 = "DATA"
        .ColumnWidth = 10
        .HorizontalAlignment = xlCenter
    End With
    With Cells(Riga, Colonna + 1)
        .FormulaR1C1 = "Nominativo turno"
        .ColumnWidth = 30
        .HorizontalAlignment = xlCenter
    End With
    With Cells(Riga, Colonna + 2)
        .FormulaR1C1 = "Unità Operativa"
        .ColumnWidth = 20
        .HorizontalAlignment = xlCenter
    End With
    With Cells(Riga, Colonna + 3)
        .FormulaR1C1 = "Nominativo turno"
        .ColumnWidth = 30
        .HorizontalAlignment = xlCenter
    End With
    
    Riga = Riga + 1
    
        
    With Cells(Riga, Colonna + 1)
        .FormulaR1C1 = "08.00 - 20.00"
        .ColumnWidth = 30
        .HorizontalAlignment = xlCenter
    End With
    With Cells(Riga, Colonna + 2)
        .FormulaR1C1 = ""
        .ColumnWidth = 20
        .HorizontalAlignment = xlCenter
    End With
    With Cells(Riga, Colonna + 3)
        .FormulaR1C1 = "20.00 - 8.00"
        .ColumnWidth = 30
        .HorizontalAlignment = xlCenter
    End With

    Riga = Riga + 1

End Sub
...tutto lavoro bruto e ripetitivo, niente di particolare da commentare, ad eccezione del fatto che la variabile Riga aumenta ogni volta che il programma passa a sistemare la riga successiva.
La variabile è definita a livello di modulo, così sarà lasciata, per le routines successive, già modificata da questa routine.



La seconda routine si chiama aggiuntaDate, e serve ad aggiungere le date per creare il calendario del mese.
E' ovvio che si serve delle variabili locali Mese e Anno che sono state eguagliate ai valori passati come parametri dalla routine che aggiungeva il foglio, nella quale è stata calcolata la data.
Private Sub aggiuntaDate()
    
    'stabiliamo la data del mese
    Dim Dat As Date, primaData As Date
    Dat = CDate("01/" & Mese & "/" & Anno)
    
    
    With Cells(Riga - 1, Colonna)
        .FormulaR1C1 = MonthName(Mese)
        .Font.Bold = True
    End With
    
    
    Do While Month(Dat) = Mese
        With Cells(Riga, Colonna)
            .FormulaR1C1 = Day(Dat)
            If WeekdayName(Weekday(Dat, vbUseSystemDayOfWeek)) = "domenica" Then .FormulaR1C1 = .FormulaR1C1 & " D"
            .Font.Bold = True
        End With
        Dat = Dat + 1
        Riga = Riga + 1
    Loop
End Sub
Dichiara una variabile locale Dat di tipo Date, e un'altra chiamata primaData, sempre di tipo Date.
Ricostruisce la data del primo giorno del mese successivo dai valori di mese e anno, e la pone nella variabile Dat.
Ora, credo di aver fatto un lavoro un po' cervellotico: innanzitutto la variabile primaData deve essere uno di quei residui di un qualche procedimento cui avevo pensato prima, in quanto mi sembra completamente inutilizzata in tutta la routine.
Provo a eliminarla... Private Sub aggiuntaDate() 'stabiliamo la data del mese Dim Dat As Date Dat = CDate("01/" & Mese & "/" & Anno) ... ...e come avevo capito già, funziona ugualmente!
Ma adesso mi viene in mente un'altra cosa: anziché passare, quando chiamo la prima routine del modulo, mese e anno, non potrei passare come un unico parametro direttamente la data del mese successivo?

Provo a ingegnarmi in tal senso...

No, non è che poi il codice si semplifichi più di tanto, sta bene così...

    With Cells(Riga - 1, Colonna)
        .FormulaR1C1 = MonthName(Mese)
        .Font.Bold = True
    End With
La riga del foglio su cui ci troviamo, indicata dalla variabile di modulo Riga, avanzata dalla routine precedente, è la quinta.
Ma dobbiamo posizionare il nome del mese su una cella della quarta riga, per cui riduciamo momentaneamente il valore di Riga per posizionare il nome del mese al posto dovuto.
Quindi...
    Do While Month(Dat) = Mese
        With Cells(Riga, Colonna)
            .FormulaR1C1 = Day(Dat)
            If WeekdayName(Weekday(Dat, vbUseSystemDayOfWeek)) = "domenica" Then .FormulaR1C1 = .FormulaR1C1 & " D"
            .Font.Bold = True
        End With
        Dat = Dat + 1
        Riga = Riga + 1
    Loop
Finché il mese della data è uguale al nome del mese che abbiamo memorizzato nella variabile Mese, scriviamo il numero del giorno (Day(Dat)) nella prima colonna.
La riga:
If WeekdayName(Weekday(Dat, vbUseSystemDayOfWeek)) = "domenica" Then .FormulaR1C1 = .FormulaR1C1 & " D"
serve a ad aggiungere la D nel caso in cui il giorno sia domenica (come da modello datomi).

A ogni aggiunta di giorno si aumenta di un giorno la data, e il numero di riga, in modo che aumentando ogni riga aumenti il giorno del mese, fin quando, come specificato sopra, il mese non cambi (month(Dat = Mese) rispetto a quello memorizzato nella variabile.
A questo punto, il processo finisce.

La routine successiva si chiama creazioneGriglia
Private Sub creazioneGriglia()
'CREAZIONE DELLA GRIGLIA COI BORDI

Range(Cells(primaRiga + 2, primaColonna), Cells(Riga, primaColonna + 3)).Select
Selection.Borders(xlDiagonalDown).LineStyle = xlNone
    Selection.Borders(xlDiagonalUp).LineStyle = xlNone
    With Selection.Borders(xlEdgeLeft)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With Selection.Borders(xlEdgeTop)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With Selection.Borders(xlEdgeBottom)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With Selection.Borders(xlEdgeRight)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With Selection.Borders(xlInsideVertical)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With Selection.Borders(xlInsideHorizontal)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
End Sub
Qui è successo qualcosa di singolare: analizziamo le prime righe:
Private Sub creazioneGriglia()
'CREAZIONE DELLA GRIGLIA COI BORDI

Range(Cells(primaRiga + 2, primaColonna), Cells(Riga, primaColonna + 3)).Select
...
Ecco, qui ho delle variabili che ho eliminato, ossia primaRiga e primaColonna.
Mancando queste, mi si genera un errore, che però non appariva, ma semplicemente il programma non mi caricava il foglio col calendario del mese.
Perché?

Eseguendo passo-passo con F8 il programma, ho visto che da questa riga il flusso del programma passava direttamente alla routine aggiungiFoglio nella sezione di gestione dell'errore che elimina il foglio:
X:     'gestione dell'errore di tentata rinominazione del foglio: elimina il foglio senza suscitare avvisi di eliminazione.
    Application.DisplayAlerts = False
    ActiveSheet.Delete
    Application.DisplayAlerts = True
End Sub
eliminandomi il foglio.
Questo perché, essendo tutte queste routines chiamate dalla routine aggiungiFoglio, sono soggette tutte alla clausola On Error GoTo X, e quindi l'errore non mi appariva ma veniva gestito da quella istruzione con eliminazione del foglio.

La griglia deve essere creata nel Range che va dalla cella che sta alla terza riga e prima colonna, per terminare in basso a destra con la cella che sta all'ultima riga e quarta colonna.
Ecco quindi che, per ovviare all'inconveniente di prima, trasformo così il codice:
Range(Cells(3, 1), Cells(Riga, 4)).Select
eliminando i nomi delle variabili che non esistono più.

Il resto:
Selection.Borders(xlDiagonalDown).LineStyle = xlNone
    Selection.Borders(xlDiagonalUp).LineStyle = xlNone
    With Selection.Borders(xlEdgeLeft)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With Selection.Borders(xlEdgeTop)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With Selection.Borders(xlEdgeBottom)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With Selection.Borders(xlEdgeRight)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With Selection.Borders(xlInsideVertical)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
    With Selection.Borders(xlInsideHorizontal)
        .LineStyle = xlContinuous
        .Weight = xlThin
        .ColorIndex = xlAutomatic
    End With
è tutto lavoro bruto di creazione delle linee, copiato pedissequamente dalle macro generate automaticamente, su cui non bisogna fare un granché di lavoro intellettuale.
Unica cosa notevole, che il valore di Riga, essendo stato aumentato a uno più della cella con l'ultimo giorno, è stato lasciato così, creando uno spazio vuoto sotto l'ultimo giorno, semplicemente per rispettare la similitudine con il foglio originale che mi è stato consegnato come modello.

Andiamo alla scritta di coda con la routine scrittaDiCoda
Private Sub scrittaDiCoda()
Riga = Riga + 2
Range(Cells(Riga, primaColonna), Cells(Riga, primaColonna + 3)).Select
Selection.Merge
Selection.RowHeight = 35
Selection.VerticalAlignment = xlTop
Selection.WrapText = True
Selection.FormulaR1C1 = "N.B. Gli eventuali cambi turno di Guardia Medica AFO devono essere comunicati, ai fini di riscontro, alla scrivente Direzione Sanitaria, sig.ra XXXXX XXXXXX, 38XXX fax 38XXX"
Cells(1, 5).Select
End Sub
Aumento di 2 il numero della riga corrente e mi ritrovo ancora una di quelle variabili fantasma, primaColonna, che serve per delimitare le caselle da fondere insieme per ospitare la scritta.
E infatti ottengo ancora l'eliminazione spontanea del foglio sempre per quel gioco di gestione dell'errore.
La sostituisco:
Private Sub scrittaDiCoda()
Riga = Riga + 2
Range(Cells(Riga, 1), Cells(Riga, 4)).Select
Selection.Merge
Selection.RowHeight = 35
Selection.VerticalAlignment = xlTop
Selection.WrapText = True
Selection.FormulaR1C1 = "N.B. Gli eventuali cambi turno di Guardia Medica AFO devono essere comunicati, ai fini di riscontro, alla scrivente Direzione Sanitaria, sig.ra XXXXX XXXXX, 38XXX fax 38XXX"
Cells(1, 5).Select
End Sub
Anche qui, come è successo prima, la prima volta che provo con le variabili sostituite il codice sembra funzionare, ma al secondo tentativo ritorna a mostrare l'eliminazione spontanea del foglio.
Stavolta è dovuto al fatto che le variabili appaiono ancora più sotto nel codice.
Questo non me lo so spiegare.

Bene, adesso c'è la routine copiaMenu, che fa qualcosa di completamente nuovo: copia la lista dei medici di guardia che ho su un foglio che, con l'intenzione di renderlo invisibile, ho chiamato Nascosto:
Sub copiaMenu()

    Dim k As Integer
    k = primaRiga
    Do While Sheets("Nascosto").Cells(k, primaColonna) <> ""
        ActiveSheet.Cells(k - primaRiga + scartoMenu + 1, primaColonna + 5).FormulaR1C1 = _
        Sheets("Nascosto").Cells(k, primaColonna).FormulaR1C1
        ActiveSheet.Cells(k - primaRiga + scartoMenu + 1, primaColonna + 6).FormulaR1C1 = _
        Sheets("Nascosto").Cells(k, primaColonna + 1).FormulaR1C1
        k = k + 1
    Loop
    With ActiveSheet
        With .Cells(primaRiga + scartoMenu - 1, primaColonna + 5)
            .Interior.ColorIndex = 6
            .FormulaR1C1 = "NOME"
            .HorizontalAlignment = xlCenter
        End With
       
        With .Cells(primaRiga + scartoMenu - 1, primaColonna + 7)
            .Interior.ColorIndex = 8
            .FormulaR1C1 = "GIORNO"
        End With
        With .Cells(primaRiga + scartoMenu - 1, primaColonna + 8)
            .Interior.ColorIndex = 5
            .Font.ColorIndex = 2
            .FormulaR1C1 = "NOTTE"
        End With
        .Columns(6).ColumnWidth = 26
        
        With .Columns(7)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
        
        With .Columns(8)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
    End With
End Sub
Questa è una selva di nomi delle variabili che ho eliminato!
Non so nemmeno se le ho evidenziate tutte in rosso, perché sono ripetute troppe volte.
Sostituiamole...

Sub copiaMenu()

    Dim k As Integer
    k = 1
    Do While Sheets("Nascosto").Cells(k, 1) <> ""
        ActiveSheet.Cells(k - 1 + scartoMenu + 1, 1 + 5).FormulaR1C1 = _
        Sheets("Nascosto").Cells(k, 1).FormulaR1C1
        ActiveSheet.Cells(k - 1 + scartoMenu + 1, 1 + 6).FormulaR1C1 = _
        Sheets("Nascosto").Cells(k, 1 + 1).FormulaR1C1
        k = k + 1
    Loop
    With ActiveSheet
        With .Cells(1 + scartoMenu - 1, 1 + 5)
            .Interior.ColorIndex = 6
            .FormulaR1C1 = "NOME"
            .HorizontalAlignment = xlCenter
        End With
       
        With .Cells(1 + scartoMenu - 1, 1 + 7)
            .Interior.ColorIndex = 8
            .FormulaR1C1 = "GIORNO"
        End With
        With .Cells(1 + scartoMenu - 1, 1 + 8)
            .Interior.ColorIndex = 5
            .Font.ColorIndex = 2
            .FormulaR1C1 = "NOTTE"
        End With
        .Columns(6).ColumnWidth = 26
        
        With .Columns(7)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
        
        With .Columns(8)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
    End With
End Sub
Mi sembra di averle sostituite tutte, ma il programma continua ad eliminare il foglio, e infatti appaiono ancora in un'altra routine...

Aggiusto meglio questa routine... C'è anche una variabile che si chiama scartoMenu, la quale l'ho creata sempre nell'ottica di una flessibilità nel posizionamento della tabella.
E' una variabile globale, definita nel modulo delle variabili globali:
Public Const scartoTabella = 4
Public Const scartoMenu = 3
Public Bersaglio As Range
Public Const Centralina = "Foglio1"
Che faccio, la elimino?
Vediamo di analizzare il codice...

Sub copiaMenu()

    Dim k As Integer, z As Integer
    k = 1
    z = 4
    Do While Sheets("Nascosto").Cells(k, 1) <> ""
        ActiveSheet.Cells(z, 6).FormulaR1C1 = _
        Sheets("Nascosto").Cells(k, 1).FormulaR1C1
        ActiveSheet.Cells(z, 7).FormulaR1C1 = _
        Sheets("Nascosto").Cells(k, 2).FormulaR1C1
        k = k + 1
        z = z + 1
    Loop
    With ActiveSheet
        With .Cells(3, 6)
            .Interior.ColorIndex = 6
            .FormulaR1C1 = "NOME"
            .HorizontalAlignment = xlCenter
        End With
       
        With .Cells(3, 8)
            .Interior.ColorIndex = 8
            .FormulaR1C1 = "GIORNO"
        End With
        With .Cells(3, 9)
            .Interior.ColorIndex = 5
            .Font.ColorIndex = 2
            .FormulaR1C1 = "NOTTE"
        End With
        .Columns(6).ColumnWidth = 26
        
        With .Columns(7)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
        
        With .Columns(8)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
    End With
End Sub
L'ho riscritta completamente perché era scritta con un codice incredibilmente cervellotico, sempre con l'intenzione di posizionare dinamicamente la tabella, cosa del tutto inutile.
Ho eliminato la variabile globale scartoMenu, che non serve più.
E funziona egregiamente!

Però avendo appreso una nuova funzionalità di Excel, ossia la possibilità di dare un nome ai ranges, ho ancora dei miglioramenti da fare per eliminare quel codice tipo
        ActiveSheet.Cells(z, 6).FormulaR1C1 = _
        Sheets("Nascosto").Cells(k, 1).FormulaR1C1
        ActiveSheet.Cells(z, 7).FormulaR1C1 = _
        Sheets("Nascosto").Cells(k, 2).FormulaR1C1
che facilmente può risultare confusivo.
Ma questo lo vedrò in seguito.


C'è poi la routine scorri, che serve per presentare il foglio all'operatore avendo sotto gli occhi tutti i giorni del mese, in modo da poter seguire le modifiche dei turni sull'elenco dei medici.
Sub scorri()
    ActiveWindow.SmallScroll Down:=2
End Sub
...copiata dalle Macro automatiche, regolando a 2 il numero di riga al quale far scorrere il foglio.
Nessun ragionamento particolarmente intelligente da fare.

L'ultima routine del modulo formattazioneFoglio è impostazioniStampa, che serve per preparare le giuste dimensioni del foglio per la stampa.
Private Sub impostazioniStampa()
    'IMPOSTAZIONI PER LA STAMPA
    With ActiveSheet.PageSetup
        .LeftMargin = Application.InchesToPoints(0.5)
        .RightMargin = Application.InchesToPoints(0)
        .PrintArea = "$A$" & primaRiga & ":$D$44"
    End With

End Sub
E qui ho copiato ampiamente dalle Macro automatiche, ma mi salta all'occhio che c'è ancora la fantomatica variabile primaRiga.
La eliminiamo:
Private Sub impostazioniStampa()
    'IMPOSTAZIONI PER LA STAMPA
    With ActiveSheet.PageSetup
        .LeftMargin = Application.InchesToPoints(0.5)
        .RightMargin = Application.InchesToPoints(0)
        .PrintArea = "$A$" & 1 & ":$D$44"
    End With

End Sub


Bene: andiamo a provare il tutto, compresa l'anteprima di stampa per saggiare questa ultima routine.

Tutto OK! (che faticaccia!)

Parte seconda del mio programma: Sezione prima: preparazione alla formattazione del foglio.

La routine chiamata nella prima parte del programma, quella del caricamento del foglio, si compone di diverse routines chiamate in sequenza, che svolgono vari compiti:
  • Scrivere le intestazioni del foglio;
  • aggiungere le date per creare la pagina col calendario del mese;
  • aggiungere i bordi in modo da organizzare il tutto in una griglia;
  • aggiungere la scritta di coda.
Queste sono le parti che costruiscono la struttura del calendario del mese.
Ecco la prima routine che chiama tutte le altre, nel modulo che si occupa della formattazione del foglio, insieme alle variabili dichiarate per questo modulo:
Option Explicit
Dim Riga As Integer
Dim Colonna As Integer
Dim primaRiga As Integer
Dim primaColonna As Integer
Dim Mese As Integer
Dim Anno As Integer


Sub formattaFoglio(r As Integer, C As Integer, M As Integer, Y As Integer)

'inizializza le variabili colonna e riga, e ne copia i valori iniziali per preservarli, nelle
'variabili PrimaColonna e PrimaRiga

Riga = r
Colonna = C
Mese = M
Anno = Y
primaRiga = Riga
primaColonna = Colonna


scriviIntestazioni
    
aggiuntaDate

creazioneGriglia

scrittaDiCoda

copiaMenu

scorri

impostazioniStampa

End Sub
Focalizziamoci sulle variabili:
Inizialmente avevo l'idea di rendere facilmente modificabile la posizione del foglio da stampare all'interno del foglio di lavoro di Excel, quindi l'ho abbandonata, ma è residuato del codice relativo a quello scopo...
Volendo lo potrei anche eliminare, quel codice, dal momento che quella mi sembra un'idea inutile e buona solo a confondersi durante la programmazione.
Ho un modulo di variabili e costanti globali che sono queste:
Public Const Riga = 1
Public Const Colonna = 1
Public Const scartoTabella = 4
Public Const scartoMenu = 3
Public Bersaglio As Range
Public Const Centralina = "Foglio1"
Prendiamo in esame solo Riga e Colonna.
Con questo codice sarebbe facile cambiare posizione in quanto sarebbe sufficiente soltanto modificare la costante Riga e/o la costante Colonna per poi, dato un idoneo codice, spostare tutto l'apparato, ma dal momento che non voglio farlo più potrei eliminare tranquillamente le due costanti Riga e Colonna.
Queste però vengono usate nella prima routine, vista in precedenza, quando viene chiamata la routine formattaFoglio, in questo modo:
....

    formattaFoglio Riga, Colonna, Month(d), Year(d)  'formatta il foglio secondo lo schema


...
...che poi, essendo pubbliche, è anche un po' ridicolo passarle come parametri dal momento che la routine può vederle direttamente...

Dunque se elimino le costanti devo modificare la firma della routine formattaFoglio e la sua chiamata, dando per scontato che il valore di Riga e Colonna è fisso a 1.
Vediamo la dinamica in formattaFoglio:
Option Explicit
Dim Riga As Integer
Dim Colonna As Integer
Dim primaRiga As Integer
Dim primaColonna As Integer
Dim Mese As Integer
Dim Anno As Integer

Sub formattaFoglio(r As Integer, C As Integer, M As Integer, Y As Integer)

'inizializza le variabili colonna e riga, e ne copia i valori iniziali per preservarli, nelle
'variabili PrimaColonna e PrimaRiga

Riga = r
Colonna = C
Mese = M
Anno = Y
primaRiga = Riga
primaColonna = Colonna

...
Ecco: ho delle variabili locali del modulo formattazioneFoglio, il cui nome è uguale a quello delle costanti.
Quando le costanti vengono passate come parametro (? cosa inutile) queste variabili locali assumono il valore delle costanti, e quindi potranno essere cambiate di valore nel corso del modulo senza influenzare il valore base di Riga e Colonna mantenuto dalle costanti.
Ma io, dando per fissi Riga e Colonna uguali a 1, posso benissimo, tolte le costanti, uguagliare le variabili locali di questo modulo a 1.
Sub formattaFoglio(M As Integer, Y As Integer)



Riga = 1
Colonna = 1
Mese = M
Anno = Y
primaRiga = Riga
primaColonna = Colonna
e chiamare questa routine, ovviamente, dalla routine aggiungiFoglio, con:
formattaFoglio Month(d), Year(d)
...che funziona perfettamente (ho fatto la prova).
Adesso vediamo come va avanti questa routine:
...
Riga = 1
Colonna = 1
Mese = M
Anno = Y
primaRiga = Riga
primaColonna = Colonna
La variabile locale Mese viene eguagliata al parametro M e la variabile locale Anno viene eguagliata al parametro Y.
Con questi, la routine aggiungiFoglio passa il valore calcolato del mese e dell'anno di cui fare il calendario. Ricordo il codice:
formattaFoglio Month(d), Year(d)
...sarebbero mese e anno della data calcolata del mese successivo.

Quindi le variabili locali primaRiga e primaColonna vengono eguagliate alle variabili locali Riga e Colonna: anche questo è inutile dal momento che Riga e Colonna variabili locali sono date per uguali a 1, in quanto ogni volta che, nonostante Riga e Colonna si saranno modificate nel corso del modulo, per fare riferimento alla prima riga e alla prima colonna basterà mettere il numero 1.
Il codice diventa quindi questo:
Sub formattaFoglio(M As Integer, Y As Integer)

Riga = 1
Colonna = 1
Mese = M
Anno = Y

scriviIntestazioni
    
aggiuntaDate

creazioneGriglia

scrittaDiCoda

copiaMenu

scorri

impostazioniStampa

End Sub
Quindi, posti i punti di partenza riga e colonna e presi i parametri relativi alla data calcolata, si eseguono una per una tutte le routines che formattano il foglio.