venerdì 26 settembre 2014

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...