Gianni Giaccaglini

Tricks & mini applics on WPF
posts - 46, comments - 0, trackbacks - 0

giovedì 23 luglio 2015

Usare l'evento SelectionChange per lanciare macro

L'evento SelectionChange lancia macro differenziate

Premessa. Il Ribbon personalizzato nella maggioranza dei casi si presenta complesso da implementare e gestire e costituisce un "troppa grazia" per gli usi correnti, così molti utenti si arrangiano incollando sul foglio di lavoro dei controlli, quali Listbox e pulsanti, questi ultimi a loro volta associati a determinate macro VBA.

Questa sciocchezzuola, cui forse pochi pensano, sfrutta l'evento SelectionChange per surrogare tale comportamento. Andiamo al sodo con un primo esempio che illustra un certo Foglio1 ove è presente una serie di valori, quali  "Giallo", "Bianco" ecc. V. l'immagine riportatain fondo a questo post. In loro vece si immaginino analoghi colori di riempimento delle celle in C6:F9 (è solo un esmpio, ripeto).

Ed ecco la prima macro:

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
   ' Exit Sub ' Usato in fase di layout
   If Intersect(Target, Range("ZonaSelChange")) Is Nothing Then Exit Sub
   MsgBox "La cella " & Target.Address(False, False) & " ha indice colore = " & Target.Interior.ColorIndex
   Range("A1").Select ' De-seleziona Zona Frutti
End Sub

Tale codice ha lo scopo banale di lanciare un messaggio che indica il colore di ciascuna cella in C6:F8 ma ovviamente andrà personalizzato a seconda dei casi pratici. Importanti sono le istruzioni a monte e a valle: la prima evita l'azione scatenata se la cella selezionata è esterna a "ZonaSelChange", la seconda, selezionando sistematicamente la cella A1 permette di riattivare la macro precedente. Infatti non va dimenticato che SelectionChange si scatena solo se viene selezionata una cella diversa dalla precedente.

La variante sul Foglio2

Stavolta si abbia un intervallino del genere i cui elementi hanno nome "Frutti" (e, all'atto della creazione, il generico "Tabella1":

Frutto
Mele
Pere
Susine
Albicocche
Pesche
Ciliegie

La macro, che stavolta lascio all'esegesi autogestita del paziente lettore è subito vista:

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
  ' Exit Sub Servita per creare il layout
  If Intersect(Target, Range("Frutti")) Is Nothing Then Exit Sub
  Select Case Target.Address(False, False)
    Case "C3"
    MsgBox "Macro delle " & Target.Value
    Case "C4"
    MsgBox "Macro delle " & Target.Value
    Case "C5"
    MsgBox "Macro delle " & Target.Value
    Case "C6"
    MsgBox "Macro delle " & Target.Value
     Case "C7"
    MsgBox "Macro delle " & Target.Value
    Case "C8"
    MsgBox "Macro delle " & Target.Value
  End Select
  Range("A1").Select ' De-seleziona ZonaSleChange
End Sub

Il clic sulle varie celle lancia messaggi al limite del grottesco ("Macro delle mele", "Macro delle pere", nientemeno!) ma l'adattamento a casi concreti è facilmente immaginabile.

NOTA IMPORTANTE. Il massimo vantaggio di questi procedimenti si ottiene non tanto surrogando controlli ActiveX o classici, bensì sfruttando celle facenti parte del modello Excel.

Meditate, gente meditate. 

       A    B    C    D    E    F G H I
  1   Evento SelectionChange per lanciare macro differenziate  
  2 Al clic su una cella dell'intervallo C6:F8 denominata        
  3 ZonaSelChange viene indicato il color-index  è solo un esempio...)    
  4 N.B. - Al termine viene selezionata A1, consentendo così di attivare anche  la cella precedente
  5                    
  6     Giallo  Bianco  Rosso  Bianco        
  7      Bianco  Blu Bianco  Rosa        
  8      Marrone Bianco Rosso   Bianco        
  9                    
10                    
11               Sul Foglio2 si ha una variante dell'uso dell'evento SelectionChange
12                    

posted @ giovedì 23 luglio 2015 14:24 | Feedback (0) |

sabato 28 febbraio 2015

Crittografia di un documento Word

 

Criptare/decriptare un documento Word con tecniche più o meno originali

Antefatto. Dopo l’esperienza di cui al post precedente (Criptare/decriptare un testo con Excel + VBA) ho pensato di utilizzarla direttamente su un documento Word. La scommessa era quella di ottenere una crittografia in grado di:

a) mantenere i vari formati possibili (grassetti, corsivi, colori, evidenziazioni ecc.);

b) sfruttare, oltre agli scorrimenti (Shift, d’ora in poi) casuali, l’inversione di brani di testo.

Subito ho constatato che si tratta di impegni conflittuali. Sulla carta un oggetto Document di Word offre una proprietà alletante, ovvero Content, che racchiude, come dice il suo nome, l’intero contenuto. Attenzione però perché si tratta dei soli caratteri, escludendo segni speciali e formattazioni. Lo stesso accade per la funzione di stringa stringReverse (non sfruttata nel post precedente, mi era sfuggita) che traduce “Roma” in “amoR”. Non è finita: a complicare le cose si mette di traverso un tabella Word. Questa, come non tutti sanno (me compreso, fino a ieri l’altro) è caratterizzata da particolari segni che, per così dire, ne fissano l’intelaiatura, che viene sconvolta ricorrendo a StringReverse.

Forse una soluzione del genere moglie ebbra e botte piena esiste, ma alla fine ho deciso di non ammattire, decidendo di procedere carattere per carattere.

L’archivio sperimentale .docm

Il testo possibile è quello riportato qui di seguito, racchiuso tra linee :

______________________________________________________________________________________________

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna.

Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus.

SHIFT CASUALI AL TERMINE DEL DOC macro GestioneTabella di ThisDocument. Ciao

TABELLINA

Ciao

Pio

pao

chi

sei

Tu?

________________________________________________________________________________________________ 


Ricordo che la parte in pseudo-latinorum si ottiene digitando =Lorem() + Invio.

In fondo al documento ho infine inglobato una casella di testo – comando Inserisci > Casella di testo, quindi In tale oggetto (Shape, detto en passant) ho poi inserito una tabella (pochi ci penserebbero, ma si può), caratterizzata da una sola riga e venti colonne. L’obiettivo è di poter coprire, insieme a un numero di altre righe, un numero adeguato di scorrimenti, alias shift.

NOTA. Per evitare la vista di tali shift a intrusi sarebbe stato bello poter occultare la fatidica casella, che ahimè non gode della proprietà Visible. Un rimedio parziale consiste nel minimizzare le dimensione dei caratteri e assegnare loro il colore bianco nonché ridurre le dimensioni della casella fin quasi a farla scomparire.

La macro crittografica

Dopo tante chiacchiere riporto la macro dal nome di non sfrenata fantasia (Cripta) qui sotto, pezzo per pezzo.  Cominciamo con la parte iniziale.

Dim VettShift() As Integer ' A livello Dichiarazioni

Sub Cripta()
  Selection.HomeKey unit:=wdStory
  Dim Tutticar As Characters
  Set Tutticar = ThisDocument.Characters
  Dim TotCar As Long
  Dim NumCol As Integer, NumRighe As Integer
  NumCol = 20
  TotCar = Tutticar.Count
  If TotCar > 20 Then
    NumRighe = Int(TotCar / (8 * NumCol)) ' Copre 1/8 ca. del doc.
 
Else
    NumRighe = 1
  End If

L’istruzione Selection.HomeKey unit:=wdStory assicura che il cursore si porti al primo carattere del testo. Segue l’impostazione in una variabile Tutticar di tipo Characters dell’intero sistema dei caratteri del nostro documento. Le rimanenti istruzioni, fino a ThisDocument.Shapes(1).Select definiscono il numero di colonne (fissato a 20) e quello delle righe in modo da coprire un ottavo circa del documento. Volendo si può aumentare tale copertura, ma a scapito dei tempi di creazione degli scorrimenti (pseudo) casuali.

Segue la parte che svolge due mestieri: aggiungere alla prima riga, inizialmente unica, della tabella interna della textbox, altre righe fino al NumRighe calcolato sopra e poi caricare numeri casuali da 1 a 10 nell’insieme delle celle create in precedenza. Il procedimento non dovrebbe presentare ostacoli al comprendonio.

  Dim i As Integer, j As Integer
  ThisDocument.Shapes(1).Select
  Dim MiaTab As Table
  Set MiaTab = Selection.Tables(1)
  ' MiaTab.Rows.Delete ' Utilizzabile carica
  For i = 1 To NumRighe – 1
    MiaTab.Rows.Add
  Next
  Dim TestoCella As String
  For i = 1 To NumRighe
    For j = 1 To NumCol
      TestoCella = "" & Int(Rnd * 10) ' Numeri casuali fino a 10
      MiaTab.Cell(i, j).Range.Text = TestoCella
    Next
  Next

NOTA. Va solo detto che non sembra proprio possibile agire “a distanza” sugli elementi della tabella interna, è giocoforza esordire con  ThisDocument.Shapes(1).Select e solo allora si può impostare in MiaTab come membro della selezione.

Un punto problematico

Qualcuno si stupirà del codice seguente, in quanto carica gli shift non tanto su un’entità in memoria, bensì su un vettore unidimensionale anziché su una matrice di tante righe e colonne quante ne ha la tabella interna alla casella di testo. Ne riparlerò in fondo all’articolo, per ora mi limito a far notare che l’uso di un vettore è più funzionale alla gestione carattere per carattere già anticipata.

  ' Carica Shift da MiaTab a VettShift
  ReDim VettShift(1 To NumRighe * NumCol)
  Dim k As Integer
  ThisDocument.Shapes(1).Select
    For i = 1 To NumRighe
      For j = 1 To NumCol
        TestoCella = _
        Selection.Tables(1).Cell(i, j).Range.Text
        ' Elimina il pallino:
        TestoCella = _
        Left(TestoCella, Len(TestoCella) - 1)

      k = k + 1
      VettShift(k) = 0 + TestoCella
    Next j
  Next i
  Selection.HomeKey unit:=wdStory ' Cursore sul documento

L’unica osservazione degna di rilievo è relativa al codice in neretto, che serve a eliminare lo strano “pallino” nero che affianca i contenuti di ogni cella di una tabella, permettendo così di caricare i soli numeri nel VettShift.

Il codice crittografico, finalmente

  ' Criptazione
  Dim Car As String
  Dim Shift As Integer, NumShift As Integer
  NumShift = UBound(VettShift)
  k = 1
  For i = 1 To Tutticar.Count
    Car = Mid(Tutticar(i), 1)
    Shift = VettShift(k)
  ' Evita i pallini e altri segni speciali delle tabelle
    If Asc(Car) > 31 Then  _
      Tutticar(i).Text = Chr(Asc(Car) + Shift)
    end if
    k = IIf(k = NumShift, 1, k + 1)
  Next
  Set Tutticar = Nothing
End Sub

La struttura e la missione di tale codice dovrebbe, qui giunti, essere chiara a tutti, per cui mi limito a due osservazioni:

a) l’istruzione k = IIf(k = NumShift, 1, k + 1) fa sì che quando si l’ultimo scorrimento di VettShift si riparta con indice k = 1;

b) le istruzioni in grassetto eliminano pallini e altri speciali simboli che, per così dire, “sostengono” la struttura tabellare. Questi hanno tutti valore Ascii <= 31 (*) per cui saltandoli come pestiferi vengono considerati solo i caratteri “veri e propri”, evitando strane anomalie in presenza di tabelle (tra cui un  fenomeno, per me inspiegabile, che fa sì che caratteri del documento fuori tabella vengano incorporati in essa!).

NOTA (*) L’esplorazione normale dei caratteri della tabella indica sistematicamente dei Carriage Return, di Ascii = 13. Forse altri speciali, esistenti, sono occultati.

Infine, stavo quasi per dimenticarmene, la macro Decripta di ripristino dell’originale è pressoché identica alla Cripta, a parte i valori negativi degli shift, in valore assoluto uguali a quelli usati nel crittografare. L’importante è operare come già detto nel precedente post. Ossia, repetita iuvant:

1. le routine Cripta e Decripta vanno collocate in moduli distinti, Modulo1 e Modulo2 (o altri nomi graditi a ciascuno);

2. al destinatario del documento ne va spedita una copia .xlsx e comunque priva di macro VBA, inviando a parte e possibilmente in tempi diversi il Modulo2. Così solo un hacker superlativo potrà intercettare tutto quel che occorre per ricostruire l’originale.

NOTA. Questo metodo ha qualche analogia con quello a doppia chiave? Giudichi ognuno...

Conclusioni

Le contraddizioni da me stesso ammesse francamente meritano una spiegazione. In primis va puntualizzato che lo scopo di questo articolo non era di offrire una soluzione né tantomeno un applicativo. Il codice fornito è figlio di quello del post precedente, per cui inizialmente pensai che si potesse applicare a brani di testo. Per i motivi esposti all’inizio tale ipotesi presentava complicanze e difficoltà, almeno per chi scrive e... la sua pigrizia, decidendo così di agire carattere per carattere.

Da ultimo mi vengono in mente due possibili correzioni:

1) inserire nella TextBox una sola riga con una sola colonna, inserendo poi altre colonne (e celle) in relazione al numero di caratteri del documento;

2) rinunciare alla TextBox e inserire di brutto in cima a entrambe le Sub Cripta e Decripta un’istruzione VettShift = Array(3,5,2,... ), questa scelta oltretutto velocizza l’esecuzione ma richiede che il numero e i valori degli scorrimenti vengano elaborati a parte (suggerimento: tramite Excel).

posted @ sabato 28 febbraio 2015 10:43 | Feedback (0) |

venerdì 27 febbraio 2015

Crittografia di un testo con Excel + VBA

 Criptare/decriptare un testo usando Excel + VBA

Premessa teorica. Dal dibattito sui procedimenti di cifratura/decifratura o, come qui mi esprimerò, di criptazione/decriptazione di un testo alla fine è scaturito questo caposaldo: soltanto una chiave di lunghezza uguale a quella del testo garantisce totale segretezza

Convinto che questa meta sia molto ardua da conseguire (specie se a livello di un amatore per quanto creativo) ritengo tuttavia che si possa raggiungere un ragionevole compromesso, adottando per giunta criteri particolari non del tutto standard (anche se non s’inventa nulla a questo m ondo…).

 

Veniamo al dunque mostrando subito la parte sinistra del modello Excel, posta a partire dalla cella A1.

NOTA. Le evidenziazioni come sfondi e sottolineatura sono servite a mostrare che il codice macro agisce esclusivamente sui valori, lasciando immutate e formattazioni.

Brani originali

Shift1

Shift2

Shift3

Shift4

Brani criptati

Nel deserto dell'Africa

4

3

3

2

dfmtiD+nohh"rwvgvhh"ohR

un leone, che j'era entrato n’ago

2

4

1

4

pxcvurg$bvg+k$gld$.iosgp!rw

chiamò un tenente.

5

4

1

3

2jwoishu$sx!ördjlh

"Grazie! - je disse doppo - .

5

4

2

5

3%/$tursi%gwxnf$jo"1%&gmftK'

de avemme libberato.

5

1

5

2

0tuftjcgkq!jorf{c%fi

Vedrai sarò ariconoscente.

3

4

1

3

2hwoifvprrfjvd#óvdv!mdueiY

Che voi? Esse promosso?

3

2

4

4

Cqvwsorvt"hwwG#Cmqy$ijF

Embè...te darò na mano."

2

2

5

5

'3qpfr"cs%ôtfi"gy300ígoG

E quella sera stessa

3

5

5

2

cxxhvx%dtjx#cqqhwv%H

mantenne la promessa.

4

5

2

3

2duxipqwt#cq$hpsiwpfq

Andò dar tenente: "Amico,

1

5

4

1

-pgnnB&%;fxsfoiy!sei!óhsB

 la promozione è certa;

3

1

1

4

$<bwvfd#ì!fqsj{rqpss$bm#

ho magnato er capitano."

2

1

2

2

$0ppcvjrce!tg"pvcphco"pj

CIFRE E SEGNI SPECIALI:

2

5

5

3

?NNDNHGSX%KQLJU#J%GUKNE

    12*3^3 >= 4àéè§[]%& y=(1+0,25x)

2

1

1

4

*y76-1-5)>{$'&__¨éëä5!?B!4`7+33

 

Sulla prima colonna si ha una serie di brani originari, affiancata dalla colonna dei vari scorrimenti  (Shift) da applicare ad essi, in modo ciclico. Facciamo un esempio, partendo dal primo brano (Nel deserto…). Ai vari caratteri si applicano gli shift posti fra parentesi, che si susseguono con 4, 3, 3, 2 poi di nuovo 4, 3, ecc.

N (4) => R; e(3) =>h;  l(3) => o spazio(2) => "; d(4) =>h; e(3) =>h ... e via di seguito, ottenendo Rho”hh...

N.B. Il fatto di aver spezzato un testo in brani di lunghezza limitata dovrebbe far sì che quattro scorrimenti bastino a fornire una ragionevole sicurezza contro curiosi e spie, umani o softwaristici. A ciò ho aggiunto due particolarità:

1) Gli shift vengono di volta in volta creati mediante numeri  casuali.

2) Nella criptazione, come nell’opposta operazione decriptante, è stata usata la funzione VBA StrReverse (Testo) che rovescia un Testo, tramutando ad esempio Roma in amoR.

NOTA. Si riveda il caso del primo brano (Nel deserto…). Sopra avevamo visto che la criptazione lo traduce in Rho”hh... Qualcuno si sarà stupito di non aver trovato questa sequenza nella terza colonna. Ma rileggendola all’incontrario il mistero si chiarisce, alla luce dell’osservazione 2.

Sulla colonna subito a destra (non riportata sopra per motivi editoriali) si ha la situazione identica all’originale, com’era da attendersi.

Brani decriptati

Nel deserto dell'Africa

un leone, che j'era entrato

chiamò un tenente.

"Grazie! - je disse doppo - .

de avemme libberato.

Vedrai sarò ariconoscente.

Che voi? Esse promosso?

Embè...te darò na mano."

E quella sera stessa

mantenne la promessa.

Andò dar tenente: "Amico,

 la promozione è certa;

ho magnato er capitano."

CIFRE E SEGNI SPECIALI:

12*3^3 >= 4àéè§[]%& y=(1+0,25x)

 

N.B. I risultati della prima e della quarta colonna derivano da due funzioni personali dal nome promettente TestoCript e TestoDecript entrambe dotate di argomenti Testo, Sh1, Sh2, Sh3 ed Sh4. Altro non sono che i succitati scorrimenti, alias Shift.

Prima di esaminarle indichiamone l’impiego, ponendo sulla sinistra l’intervallo cui ciascuna è applicata:

F2:F16=TestoCript(A2;B2;C2;D2;E2) 
G2:G16=TestoDecript(F2;B2;C2;D2;E2)

Ai buoni intenditori che hanno seguito questi discorsi non servono altre ciance.

Il codice criptante

Si trova nel Modulo1, insieme alla macro che prepara gli scorrimenti (pseudo) casuali:

Dim ZonaShift As Range ' Variabile a livello Modulo

Sub CaricaShift()
  Dim NumRighe As Integer
  With Range("A2")
    NumRighe = Range(.Cells(1), .End(xlDown)).Rows.Count
  End With
  Set ZonaShift = Range(Range("B2"), Cells(NumRighe + 1, 5))
  Dim Cella As Range
  For Each Cella In ZonaShift
    Cella = Int(Rnd * 5 + 1)
  Next
End Sub

Function TestoCript(ByVal Testo As String, Sh1 As Integer, Sh2 As Integer, Sh3 As Integer, Sh4 As Integer) As String
  Dim VettShift(1 To 4) As Integer
  Dim i As Integer, j As Integer
  ' Carica scorrimenti nella matrice VettShift
  VettShift(1) = Sh1
  VettShift(2) = Sh2
  VettShift(3) = Sh3
  VettShift(4) = Sh4
  Dim NuovoTesto As String, Car As String
  j = 1
  For i = 1 To Len(Testo)
    Car = _
    Chr(Asc(Mid(Testo, i, 1)) + VettShift(j))
    j = IIf(j = 4, 1, j + 1)
    NuovoTesto = NuovoTesto & Car
  Next
  TestoCript = StrReverse(NuovoTesto)
End Function

Sub ProvaTestoCript()
  MsgBox TestoCript("Amba123;?", 1, 2, 3, 4)
End Sub

È importante sottolineare che l’istruzione
TestoCript = StrReverse(NuovoTesto)

è presente all’uscita della Function, il cui risultato deriva dalla successione cripta poi rovescia. Come conviene anticipare ed è logico attendersi, l’imminente funzione di decriptazione compie le due operazioni in senso contrario.

Il codice de-criptante

Si trova nel Modulo2.  È riportato qui di seguito, a questo punto affidando commenti dettagliati all’esegesi-self di quanti hanno compreso  il codice precedente, di cui questo è stretto parente.

Function TestoDecript(ByVal Testo As String, Sh1 As Integer, Sh2 As Integer, Sh3 As Integer, Sh4 As Integer) As String
Dim VettShift(1 To 4) As Integer
  Dim i As Integer, j As Integer
  ' Carica Shift in VettShift, con segno opposto
  VettShift(1) = -Sh1
  VettShift(2) = -Sh2
  VettShift(3) = -Sh3
  VettShift(4) = -Sh4
  Dim NuovoTesto As String, Car As String
  j = 1
  Testo = StrReverse(Testo)
  For i = 1 To Len(Testo)
    Car = _
    Chr(Asc(Mid(Testo, i, 1)) + VettShift(j))
    j = IIf(j = 4, 1, j + 1)
    NuovoTesto = NuovoTesto & Car
  Next
  TestoDecript = NuovoTesto
End Function

Come testé anticipato, è necessario e sufficiente far notare che l’istruzione
Testo = StrReverse(Testo)

In questo caso precede il ciclo For ...  Next di decriptazione.

Come usare il modello

Il suo scopo primario era quello di offrire una palestra per comprendere certi principi. L’applicazione pratica, nel caso di un dialogo con un collega fidato potrebbe procedere con i passaggi seguenti.

1. Tradurre le formule dei brani criptati in valori.

2. Copiare tutte le colonne del modello esclusa la prima.

3. Incollare tale materiale in un nuovo spreadsheet, per salvarlo e spedirlo al partner.

Mi è mancata la voglia e il tempo di automatizzare tali operazioni. Comunque agl’interessati suggerisco questo codice che svolge il primo mestiere. Corrisponde a un utilizzo manuale per abilissimi: 1) copiare l’intervallo; 2) spostarlo un pochino col clic destro senza lasciare la presa; 3) riportalo nella posizione di partenza; 4) scegliere Copia qui come valori.

Range("F2:F16").Copy
Range("F2:F16").Select
Selection.PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False

NOTA.  I più diffidenti potrebbero occultare le colonnine degli shift, magari con larghezza minima e caratteri bianchi, e soprattutto proteggendone l’accesso con mosse che do per note e passward nota al nostro amico.

Senza l’operazione predetta il neonato foglio di lavoro si presenta con l’angosciante ma non imprevista domanda sull’estrema colonna.

Shift1

Shift2

Shift3

Shift4

Brani criptati

Brani decriptati

4

3

3

2

gqrin#qy"dfmtiD+nohh"rwvgvhh"ohR

#NOME?

2

4

1

4

ieikt!vc$pvvrfh"she"rv$qxbvvrf$cvf+l$fle

#NOME?

5

4

1

3

1frtl{~fuftt*m$whq$jwoishu$sx!ördjlh

#NOME?

5

4

2

5

3tk~ftksnte,y"sn%/$tursi%gwxnf$jo"1%&gmftK'

#NOME?

5

1

5

2

qn{cwux",pyu%bi"tuftjcgkq!jorf{c%fi

#NOME?

3

4

1

3

1hurhftsqrdmud!öudt$hw!ikf!mdueiY

#NOME?

3

2

4

4

Csuvsqqut$gvwi)G$Cqxx$quimuqit"ui$ê#pewT

#NOME?

2

2

5

5

'0qsfo"fs"ôwff"jy".txuqu%qk,x".ígoG

#NOME?

3

5

5

2

cxxhvx%dtjx#cqqhwv%H"3twvfk#pz%h"tywgi%qw%zI

#NOME?

4

5

2

3

rpfmwunvf"sy*f%smgr$üku$duxipqwt#cq$hpsiwpfq

#NOME?

1

5

4

1

0tdjqF#!>jttmi!f$juoisfu$wbe$jefrlB

#NOME?

3

1

1

4

#sdjg$pm#iu!h$-bwvfd#ì!fqsj{rqpss$bm#

#NOME?

2

1

2

2

#0qpbvkrbe"tf"qvbpicn"qu!go"êjetfr

#NOME?

2

5

5

3

<LQFKFJUU#NSIHX%G#JWHLH%PRH%CYTWR

#NOME?

2

1

1

4

*z.63.4!,"5)>{$'&__¨éëä""qspO"C55"A?!57!4`7+33

#NOME?

 

A questo punto non resta altro da fare:

3. Spedire al partner il Modulo2, che costituisce la vera chiave decriptante!

Costui potrà semplicemente importarla vedendo così risuscitare i brani originali al posto dei vari angoscianti #NOME?.

Ricordo in fine che la cosa funziona anche con un formato .xlsx (ovviamente la fatidica funzione si perde al salvataggio, ma se rispondiamo giusto alla segnalazione di Excel possiamo sempre imporre il formato .xlsm.

posted @ venerdì 27 febbraio 2015 13:50 | Feedback (0) |

lunedì 5 gennaio 2015

Dati numeri e formule Excel segretati con crittografia differenziata

Dati, numeri e formule Excel segretati

Gianni Giaccaglini

Antefatto. Questo post fa seguito al precedente su questo stesso blog in cui ho esposto un sistema per occultare le (sole) formule di un foglio di lavoro, indicando di ciascuna i riferimenti e la formula stessa in due colonne di un secondo foglio nascosto. Meglio ancora, aggiungo ora, assegnandogli la proprietà Visible pari a xlSheetVeryHiddeen. Ho poi penato per estendere il metodo a più fogli, ma alla fine mi sono detto: perché non ricorrere alla crittografia? In tal modo si può estendere la protezione ad altri tipi di dato e l’applicabilità a qualsiasi foglio di lavoro o a tutti quanti è più semplice.

Ed ecco il frutto di questo secondo approccio.

Il mio modello si può scaricare da:
www.giannigiaccaglini.it/download/DatiFormuleSegretate.zip

Ad ogni buon conto per dare un'idea a quanti vorranno farne a meno sulla scorta delle abbondanti delucidazioni che tra poco fornirò, ecco come si presenta il mio modellino, una previsione di vendite trimestrali:

                increm 1,20%    
               
    Gennaio Febbraio Marzo

TotTrim

   
  Bulloni 160 161,92 163,86 485,78  

Cripta

  Viti 134 135,61 137,24 406,84    
  Dadi 192 194,3 196,64 582,94    
  Rondelle 119 120,43 121,87 361,3    
  Coppiglie 194 196,33 198,68 589,01    
               
  TOTALI 799 808,59 818,29 2.425,88    
               

In alto (cella B1) si ha una scritta Vendite primo trimestre 2014 frutto di questa formulaccia:
="Vendite primo trimestre  "&ANNO(OGGI()) 

Sulla destra la dicitura Cripta raffigura rozzamente un pulsante classico (non ActiveX) incollato nel foglio di lavoro, che inoltre comprende un grafico incorporato, sopra omesso. Attivando Cripta, connesso all'omonima macro si ottiene il guazzabuglio seguente:

        gŒƒ‹ 445,60%    
               
    eƒŒŒ‡ dƒ€€‡ k˜ r’r‡‹ƒ‘’ƒ    
  `“ŠŠŒ‡     504,00  e©¡®Ÿª…gmdfp   e©¡®Ÿª…gmdfp€   pgp€gp     
  t‡’‡     578,00  e©¡®Ÿª…gmdfq   e©¡®Ÿª…gmdfq€   qgq€gq     
  b‚‡     536,00  e©¡®Ÿª…gmdfr   e©¡®Ÿª…gmdfr€   rgr€gr    Decripta
  pŒ‚ƒŠŠƒ     553,00  e©¡®Ÿª…gmdfs   e©¡®Ÿª…gmdfs€   sgs€gs     
  aŽŽ‡…Š‡ƒ     538,00  e©¡®Ÿª…gmdft   e©¡®Ÿª…gmdft€   tgt€gt     
               
  rmr_jg  etvpd‰‘   et€vp€d‰‘   etvpd‰‘   lmglm€glm     
               

Faccio solo notare che a) i numeri, modificati, sono rimasti tali con l'Increm percentuale divenuto abnorme (ma senza possibilità di indovinare cos'era prima); b) le formule, stravolte, hanno un contrassegno di due simboli speciali (v. più avanti); c) il pulsante Cripta è sparito e ne è comparso uno Decripta, collegato all'omonica macro restauratrice.

Crittografare, che passione!

La crittografia è un mezzo per rendere segreti documenti di vario genere. Nel caso Excel, sono partito dalla constatazione che in un foglio di lavoro vi sono tre distinte tipologie di oggetti documentari:

- testi non numerici
- valori costanti numerici
- formule

Dopo di che ho pensato di applicare una strategia diversificata per “mascherare” i tre casi. In particolare, già diversi anni fa, avevo escogitato un metodo che crittografasse con chiavi distinte testi e numeri, in modo – si badi bene – che le cifre venissero tradotte in altre cifre. Con tale metamorfosi distinta qualora l’intruso umano o digitale riuscisse a scoprire la chiave applicata ai testi utilizzandola per recuperare gli originali otterrebbe però solo strane combinazioni di caratteri alfanumerici. La qual cosa farebbe ammattire i meno scaltri, mentre anche i più smaliziati potrebbero ipotizzare l’esistenza di una chiave ad hoc per i numeri tuttavia senza poterli scoprire, perché i numeri, salvo casi specialissimi, non hanno valenza semantica.

Citazione dotta. Non tutti sanno che Champollion poté decifrare i geroglifici egizi solo perché conosceva l’antica lingua copta, affine all’egiziano. Così i più sofisticati algoritmi frequenziali non riuscirebbero a recuperare un testo scritto in una lingua ignota (come quella dei messaggeri indiani nella seconda guerra mondiale).

Per fare un esempio spicciolo, si abbiano i due dati “ciao” e 123. Una banale translitterazione di quattro posti per i testi e di tre per i numeri produce “gmes” e 456. Con la chiave 4 si riotterrebbe “ciao, ma applicandola al numero darebbe “-./”. Infatti Asc(“1”) – 4 è uguale a 45 e Chr(45) restituisce il trattino. Analogamente Chr(Asc(“2”) - 4) = “.” E Chr(Asc(“3”) – 4) = “/”.

Il modello utilizzato per prova

Per sperimentare il procedimento anticipato ho adottato sul primo foglio di lavoro una classica situazione di vendite trimestrali di certi articoli, con i valori dei mesi di febbraio e marzo che crescono secondo un valore Incr (percentuale) applicando una formula del tipo =B1*(1 + Incr). Altre sono pedestri addizioni tipo =B1 + C1 + D1 né mancano più comuni funzioni =SOMMA nella riga dei totali. Ho inoltre previsto quest’altra formulaccia, nella prima riga: ="Vendite primo trimestre "&ANNO(OGGI()). Sullo stesso foglio è poi incorporato un grafico.

Il lettore comunque può crearsi una situazione analoga di suo gusto purché caratterizzata da una buona varietà di dati costanti e formule.

Repetita iuvant. Le macro fondamentali, che, abbiate pazienza, mostrerò fra poco, nel mio modellino sperimentale hanno nome Cripta e Decripta e sono assegnate a due pulsanti “classici” (non ActiveX) di nome Pulsante 1 e Pulsante 2 ed etichettati guardacaso Cripta e Decripta, incollati sul foglio. Cliccando sul primo si ottiene uno stravolgimento in cui l’azione criptante ha modificato i valori alfanumerici (etichette, nel gergo spreadsheet) con strani simboli tipo `“ŠŠŒ‡ o pŒ‚ƒŠŠƒ e analoghe metamorfosi hanno subito le formule ma tutte precedute dai caratteri di numero Ascii 14 e 15. In gergo Visual Basic si tratta dei codici Chr(14) e Chr(15) non riprodotti in Word (sono comandi di cambio pagina e altro) ma che nelle celle Excel vengono visualizzati (il primo è una coppia di note musicali). Hanno sostituito il segno = e servono a marcare la presenza di formule in origine, permettendo così la decrittazione.

Anche i numeri sono stati alterati ma rimanendo tali, pertanto Il grafico continua a rappresentare solo tali valori mentre quelli legati a formule scompaiono. Al posto del pulsante Cripta compare un pulsante Decripta, in precedenza occultato.

Quando si clicca su Decripta si riceve domanda idiota e fasulla, cui invece occorre rispondere digitando una certa password. Solo così ricompare fedelmente la situazione corretta, altrimenti non succede nulla (ho infatti evitato un messaggio tipo “Password scorretta” o che, peggio, sbeffeggi l’utente).

Importante! Va subito sottolineato che è fondamentale l’utilizzo di una password (unica, ma solo a beneficio degli smemorati) non solo per lanciare Decripta ma per occultare il codice VBA. Infatti altrimenti i malintenzionati che avessero accesso al modello crittografato potrebbero scoprire facilmente gli altarini!

Tutto il codice VBA

Riportato qui di seguito le funzioni e le routine, intercalate da essenziali commenti del caso.

Sul Modulo1

Function strCripDecr(Str As String, Correz As Integer, Cript As Boolean) As String

   Dim Car As String

   Correz = IIf(Cript, Correz, -Correz)

   For i = 1 To Len(Str)

      Car = Mid(Str, i, 1)

      Mid(Str, i, 1) = Chr(Asc(Car) + Correz)

    Next

    strCripDecr = Str

End Function

Sub ProvastrCriptDecript()

  MsgBox strCripDecr("", 3, True) ' Cripta

  MsgBox strCripDecr("ambarabà", 2, True)

  Dim strCript As String

  strCript = strCripDecr("ciccicoccò", 2, True)

  strCript = strCripDecr(strCript, 2, False)

  MsgBox strCripDecr(strCripDecr("Ciao mondo!", 2, True), 2, False)

End Sub

 

La strcripDecr è una funzione ambidestra che, applicata a una certa Str, restituisce con Cript = True una stringa crittata e ripristina l’originale con Cript =False. Il procedimento si limita ad aggiungere o togliere, nei due casi, una certa Correz all’i-esimo carattere della stringa trattata ed è facilmente verificabile con la susseguente routine di prova (in particolare se ne noti l’ultima istruzione che segnala con MsgBox un immutato “Ciao mondo” dopo averlo crittato e decrittato.

Nota. A nessuno sfuggirà la rozzezza di questo algoritmo, dovuto alla pigrizia e alla natura sostanzialmente didattica di questo articolo.

Ma passiamo alla routine crittografica, fortemente caratterizzata dal distinto trattamento di costanti alfabetiche, numeriche e formule.

Sub Cripta()

  Dim Cella As Range

  Dim Form As String, Lung As Integer

  Application.Calculation = xlCalculationManual ' IMP! Senza, formule criptate => ERRORE

  For Each Cella In ActiveSheet.UsedRange.Cells

    If Cella.HasFormula Then

      Form = Cella.Formula

      Lung = Len(Form)

      Form = Right(Form, Lung - 1)

      Form = strInversa(Form) ' Inverti la stringa Form

      Cella.Value = Chr(14) & Chr(15) & strCripDecr(Form, 10, True)

    Else

    If WorksheetFunction.IsNumber(Cella) Then

      Cella.Value = NumCrip(Cella.Value, 4) ' Diversifica i casi numerici

    Else

      Cella = strCripDecr(Cella.Value, 5, True)

    End If

    End If

  Next

  Application.Calculation = xlCalculationAutomatic

  Foglio1.Shapes("Pulsante 1").Visible = False

  Foglio1.Shapes("Pulsante 2").Visible = True

End Sub

 

Tale routine viene scatenata pigiando il pulsante omonimo incollato sul foglio di lavoro. Si tratta di un oggetto Shape battezzato come “Pulsante 1” all’atto della creazione, così come “Pulsante 2” è quello etichettato Decripta. Le due ultime istruzioni palesemente provvedono al termine della routine ad occultare il primo pulsante facendo riapparire il secondo. Le analoghe e opposte istruzioni in fondo alla Sub Decripta sono scontate.

Il nucleo di Cripta è il ciclo For Each Cella In ActiveSheet.UsedRange.Cells ... Next che spazzola tutte e sole le celle dell’intervallo utilizzato anziché le miriadi, vuote, del foglio corrente. La proprietà UsedRange non a tutti nota è importantissima in casi del genere e va tenuta presente a futura memoria.

Il mestiere svolto nei tre casi distinti, ossia Cella.HasFormula, che caratterizza celle contenenti formule, celle numeriche individuate dalla funzione Excel WorksheetFunction.IsNumber e le altre celle, alfanumeriche è abbastanza chiaro nel secondo e terzo caso, ove le distinzione sta nel fatto che alle etichette viene applicata la funzione ambidestra strCripDecr, mentre i numeri vengono assoggettati a una più particolare
NumCrip(Cella.Value, 4) per maggiormente confondere le acque a chi volesse recuperare l’originale. La funzione NumCrip e la sua opposta NumDecr saranno mostrate più avanti.

Il procedimento applicato alle formule è più elaborato. Va premesso che Excel VBA in una cella contenente una formula distingue il valore evidenziato diciamo così in superficie (Value) dalla formula “in cantina”, sempre così per-dicendo. I gioco consiste nel prelevare la Formula nella variabile Form, togliere a questa l’iniziale segno =, invertirla poi con la funzione strInversa che vedremo (e che, comunque anticipiamo, trasforma “Roma” in “amoR”) e, infine, riversare nel Value la Form assoggettata alla funzione ambidestra già un paio di volte vista, con tanto di anteposizione dei due predetti caratteri Chr(14) e Chr(15). Per comodità ripeto questa cruciale istruzione:

Cella.Value = Chr(14) & Chr(15) & strCripDecr(Form, 10, True)

 

A questo punto la funzione Decripta non dovrebbe avere sorprese, pertanto la lascio all’esegesi autonoma, incluse le prime istruzioni relative alla password.

Sub Decripta()

   Dim Cella As Range

   Dim Lung As Integer, Form As String

   Dim Pswrd As String, Dom As String, Titolo As String

   Pswrd = "33trentini"

   Dom = "Quanto fa 2 + 2 ?": Titolo = "Sembra facile..."

   If InputBox(Dom, Titolo) <> Pswrd Then Exit Sub ' Uscita brutale senza altre storie

   For Each Cella In ActiveSheet.UsedRange.Cells

     If Left(Cella, 2) = Chr(14) & Chr(15) Then

       Lung = Len(Cella)

       Form = strInversa(strCripDecr(Right(Cella, Lung - 2), 10, False))

       Cella.Formula = "=" & Form

     Else

     If WorksheetFunction.IsNumber(Cella) Then

       Cella.Value = NumDecr(Cella.Value, 4)

     Else

       Cella = strCripDecr(Cella.Value, 5, False)

     End If

     End If

   Next

  Foglio1.Shapes("Pulsante 1").Visible = True

  Foglio1.Shapes("Pulsante 2").Visible = False

End Sub

Sub DecriptaFormula()

  'PROVATA SOLO SU UNA FORMULA ATTIVA CRIPTATA!

  Dim Lung As Integer

  Lung = Len(ActiveCell)

  ActiveCell = strCripDecr(ActiveCell, False)

  ActiveCell.Formula = "=" & Right(ActiveCell, Lung - 2)

End Sub

 

Sul Modulo2

Su tale modulo sono presenti funzioni evocate da Cripta e Decripta, il cui ruolo avevo solo anticipato. Le riporto senza commento alcuno, considerando facili da capire e testare.

Function strInversa(Str As String) As String

  Dim i As Integer, Car As String, StrInv As String

  For i = Len(Str) To 1 Step -1

    StrInv = StrInv & Mid(Str, i, 1)

  Next

  strInversa = StrInv

End Function

Sub ProvastrInversa()

  Dim miaStr As String

  miaStr = "Pio"

  MsgBox strInversa(miaStr)

End Sub

 

Function NumCrip(Num As String, Corr As Integer)

  If Num = "" Then Exit Function

  For i = 1 To Len(Num)

    Car = Mid(Num, i, 1)

    If Asc(Car) >= 48 And Asc(Car) <= 57 Then

      Car = 0 + Car + Corr

      If Car > 9 Then Car = Car - 10

    End If

    Mid(Num, i, 1) = Car

  Next

  NumCrip = 0 + Num

End Function

Sub ProvaNumCrip()

  ActiveCell.Offset(0, 1) = NumCrip(ActiveCell, 4)

End Sub

 

Function NumDecr(Num As String, Corr As Integer)

  If Num = "" Then Exit Function

  For i = 1 To Len(Num)

    Car = Mid(Num, i, 1)

    If Asc(Car) >= 48 And Asc(Car) <= 57 Then

      Car = 0 + Car - Corr

      If Car < 0 Then Car = Car + 10

    End If

    Mid(Num, i, 1) = Car

  Next

  NumDecr = 0 + Num

End Function

Sub ProvaNumDecr()

  ActiveCell.Offset(0, 1) = NumDecr(ActiveCell, 4)

End Sub

 

Sono di facile accesso al comprendonio dei più, ergo non le commenterò salvo far notare che stavolta ho rinunciato a ricorrere a una funzione ambidestra. Questa, chi la gradisse, potrebbe estrinsecarsi nella funzione seguente:

Function NumCripDecr(Num As String, Corr As Integer, Crip As Boolean)

 If Num = "" Then Exit Function

  For i = 1 To Len(Num)

    Car = Mid(Num, i, 1)

    If Asc(Car) >= 48 And Asc(Car) <= 57 Then

      If Crip Then

        Car = 0 + Car + Corr

        If Car > 9 Then Car = Car - 10

      Else

        Car = 0 + Car - Corr

        If Car < 0 Then Car = Car + 10

      End If

    End If

    Mid(Num, i, 1) = Car

  Next

  NumCripDecr = 0 + Num

End Function

 

Sub ProvaNumCripDecr()

  'MsgBox NumCripDecr(Range("A2"), 4, True)

  ' MsgBox NumCripDecr(Range("B2"), 4, False)

  A = NumCripDecr(Range("A2"), 4, True)

  B = NumCripDecr("" & A, 4, False) ' Senza "" & A si ha errore

End Sub

Conclusioni

Il procedimento descritto non è certo privo di pecche, cui tuttavia si potrebbe in parte porre rimedio mediante algoritmi meno elementari. In particolare quello di troppo semplice translitterazione applicato alle etichette, mentre rimane valido quando si applica ai numeri. Quanto al trattamento delle formule va precisato che degli strani caratteri anteposti di codice ASCII 14 e 15 contrassegnano in modo univoco l’origine delle stringhe criptate in quanto il parametro Correz sposta in avanti i caratteri, rendendo precisa la decrittazione. Tali segnali potrebbero insospettire gli estranei desiderosi di scoprire l’arcano, ma è un compito davvero arduo a causa anche dell’inversione ad opera della funzione strInversa. Chi proprio ci tiene potrebbe posporre i due caratteri strani.

E gli altri fogli di lavoro?

Fin qui abbiamo descritto l’operatività del codice VBA sul primo foglio del modello. Ma lo si può utilizzare con gli altri, in una cartella di lavoro a fogli multipli?. La risposta è positiva: basta selezionare un qualsiasi altro foglio e lanciare Cripta e Decripta per vederne il regolare funzionamento. Com’è da attendersi, se il foglio è vuoto non succede nulla, inoltre le istruzioni che celano e fanno riapparire i due pulsanti continuano ad agire, senza altro disturbo.

A questo punto sorge spontaneo il desiderio di implementare una versione di Cripta e Decripta in grado di operare su tutti i fogli della cartella, mantenendone l’associazione ai due fatidici pulsanti omonimi. Con riferimento, per semplicità alla prima delle due routine, occorre anzitutto modificarla così:

Sub Cripta(Foglio As Worksheet)

  Dim Cella As Range

  Dim Form As String, Lung As Integer

  Application.Calculation = xlCalculationManual ' IMP! IMP! Senza le formule criptate => ERRORE

  For Each Cella In Foglio.UsedRange.Cells

. . .  eccetera . . .

End Sub

 

' Routine di prova. Semplicissima:

Sub ProvaCripta

  Cripta Foglio3 ' Oppure Cripta Sheets(2)

End Sub

 

La routine che Cripta tutti i fogli, da associare al primo pulsante, è subito vista:

Sub CriptaTuttiFogli

  Dim mioFoglio As Worksheet

  For Each mioFoglio In ThisWorkbook.Sheets

    On Erros GoTo 1 ' Salta eventuali fogli-Grafico

    Cripta mioFoglio

1: ' Prosegui

  Next

End Sub

 

Quasi del tutto analoga è la contrapposta Sub DecriptaTuttiFogli, partendo da una nuova Decripta (Foglio As Worksheet) avendo solo cura di spostare il codice che reclama la password dalla seconda alla prima per evitare continue richieste persino con fogli nullatenenti.

Un’ultima trovata: export-import di moduli VBA

Ecco da ultimo (per davvero!) un suggerimento del genere cui non si pensa. Consiste nei passaggi seguenti.

1. Nell’Editor VBA selezionare e salvare tutti i Modulo1, Modulo2 e simili, nonché UserForm varie.

2. Eliminare tali moduli e salvare la cartella Excel, magari in formato xlsx di Office 13.

3. Alla riapertura di tale file .xlsx (o vecchio .xls), sempre nell’Editor VBA importare i moduli esportati al passo 1.

In tal modo soltanto noi (e i nostri partner “fidati”) possiamo sfruttare le funzionalità critto e decritto-grafiche senza necessità di applicare una password al Progetto VBA.

N.B. Il punto è che un file MiaCartella.xlsx una volta aperto accoglie nell’Editor nuove macro e le esegue senza fiatare. Tuttavia rifiuta di salvare tale archivio con le nuove macro, invitando semmai a farlo come MiaCartella.xlsm.

 

Se si insiste col formato .xlsx i moduli estemporanei vengono perduti, idem l’eventuale protezione con password del Progetto VBA. Comunque viene tranquillamente eseguita l’importazione di moduli preregistrati, che sono subito operanti.

Funzioni personalizzate. Si tratta dell’applicazione forse più interessante. Una funzione personalizzata agisce soltanto in presenza di uno specifico modulo VBA. In sua assenza la cella che la contiene dà errore ma se lo si importa la funzione risuscita (e se in origine puntava a un determinato intervallo si presta ad altri impieghi modificando i riferimenti).

Il sistema descritto vi sembra un po’ macchinoso? Sì, ma è grazioso.

 

posted @ lunedì 5 gennaio 2015 10:10 | Feedback (0) |

venerdì 5 dicembre 2014

Formule del foglio Excel segretate

  

Formule del foglio di lavoro segretate

Può capitare che un nostro file Excel Miomodello.xls rechi contenuti sensibili nel primo foglio di lavoro. Per tenerli lontani da occhi indiscreti ho pensato dapprima di adempiere a un suggerimento di Diego Cattaruzza, ossia trasferire valori e formule in un parallelo file MioModello.csv, da recuperare successivamente (previa cancellazione dei contenuti, ma conservando i formati). L’operazione in partenza si presentava macchinosa e ardua da automatizzare. Peggio ancora, dopo inenarrabili tormenti ho scoperto che il recupero del file .csv con comandi Excel (la via che ritengo più consona) è capriccioso, perché a volte presenta i dati tutti sulla colonna A, separati da virgole.

Così ho smesso di arrovellarmi, ma in seguito m’è venuta in mente una soluzione più semplice: trasferire in un foglio aggiuntivo segreto solo le formule abbinate alle coordinate di ciascuna. La ricetta funziona nei molti casi in cui le formule siano essenziali per ottenere i risultati di elaborazioni abbastanza complesse e non appesantisce troppo il Foglio2 (e l’intera cartella di lavoro).

NOTA. Anche il fatto che il metodo che mi accingo a illustrare si limiti al primo foglio (ma, volendo, generalizzabile, v. in fondo all’articolo) può restare valido qualora anche gli altri fogli dipendano fortemente dalle formule del primo.

Per studiare il marchingegno propongo un rudimentale modelluccio Il cui Foglio1 presenta grossomodo il layout seguente:

 

 

A

B

C

D

E

F

G

H

I

1

   

 

           

2

   

         Vendite

primo trimestre

2014

       

3

   

Gennaio

Febbraio

Marzo

 

Formule su Foglio2

 

4

 

Bulloni

144,00

155,00

183,00

   

5

 

Viti

183,00

122,00

175,00

       

6

 

Dadi

152,00

162,00

149,00

       

7

 

Rondelle

194,00

169,00

163,00

 

Ripristina Formule

 

8

 

Coppiglie

144,00

139,00

123,00

   

9

                 

10

 

TOTALI

817,00

747,00

793,00

       

11

                 

12

                 

 

Le formule nell’intervallo C4:E8 sono tutte =INT(CASUALE()*100+100) per cui restituiscono interi pseudo-casuali compresi tra 100 e 200, mentre a riga 10 si hanno scontate funzioni =SOMMA. La scritta in alto “Vendite primo trimestre 2014” deriva da un’unica formula in C2:
"Vendite primo trimestre "&ANNO(OGGI())

Va poi detto che in basso si ha un grafico non mostrato sopra, che raffigura le vendite di Bulloni, Viti, Dadi ecc. nei tre mesi fatidici. Infine ai due pulsanti “classici” a destra (della classe Modulo, non ActiveX) sono associate altrettante macro che svolgono il mestiere in ciascuno indicato. In realtà non sono visibili entrambi perché, come vedremo, l’attivazione di una macro rende invisibile l’uno facendo sparire l’altro.

La macro che porta le formule sul Foglio2...

Eccola, senza indugi.

Sub FormuleSuFoglio2()

  Dim miaCella As Range, i As Integer

  Dim miaForm As String, LungForm As Integer

  Sheets("Foglio2").Columns("A:B").ClearContents

  For Each miaCella In Foglio1.UsedRange

    If miaCella.HasFormula Then

      i = i + 1

      Foglio2.Cells(i, 1) = miaCella.Address(False, False)

       miaForm = miaCella.Formula

      LungForm = Len(miaForm)

      Foglio2.Cells(i, 2) = Right(miaForm, LungForm - 1)

      miaCella = ""

    End If

  Next

  Foglio1.Shapes("Pulsante 1").Visible = False

  Foglio1.Shapes("Pulsante 2").Visible = True

End Sub

COMMENTI. Questa routine esordisce ripulendo (ClearContens) le colonne A e B. Segue un ciclo che spazzola dalla prima all’ultima cella utilizzata (intervallo UsedRange) del Foglio1, B2:E10 nel nostro esempio verificando se contiene una formula (HasFormula), nel qual caso l’indirizzo (Address) viene posto in Foglio2.Cells(i, 1), ossia l’i-esima cella di colonna A, poi viene posta nella variabile stringa miaForm la formula (proprietà appunto Formula in assenza della quale viene comunque assunto il valore). Infine in Foglio2.Cells(i, 2), ovvero l’i-esima cella di colonna B, viene riportata la parte destra della formula meno un carattere.

Le due istruzioni finali, come anticipato, agiscono sulle forme (Shapes) di nome “Pulsante 1” e “Pulsante 2” occultando la prima e facendo riapparire la seconda.

Insomma, con riferimento al nostro modellino, al termine la situazione nel Foglio2 è la seguente:

C1

"Vendite primo trimestre  "&YEAR(TODAY())

C4

INT(RAND()*100+100)

D4

INT(RAND()*100+100)

 

... eccetera ...

 

... eccetera ...

E8

INT(RAND()*100+100)

C10

SUM(C4:C8)

D10

SUM(D4:D8)

E10

SUM(E4:E8)

Per accrescere la segretezza in un primo tempo ho preso due misure ingenue: occultamento del Foglio2 e colore bianco ai caratteri delle colonne A e B.

In realtà la misura davvero efficace consiste nel proteggere il foglio con tanto di password mediante Revisione > Proteggi foglio. All’apparire della finestra specifica oltre che inserire in alto la password è necessario togliere la spunta anche alle prime caselle di controllo Seleziona celle bloccate e Seleziona celle sbloccate. Ne consegue che l’utente non può attivare nessuna cella, visualizzando al più il contenuto di quella attiva nella barra della formula. A livello VBA il codice in questione è del tipo seguente:
Foglio2.Protect Password:="xxyyzz", Contents:=True

Mentre per eliminare la protezione occorre:
Foglio2.Unprotect

Cui l’utente deve reagire digitando l’ipotetico “xxyyzz” nella casella di testo che appare.

NOTA. Inutile dire che il codice di sprotezione e quello di protezione andrebbero inseriti a monte e, rispettivamente, a valle della routine FormuleSuFoglio2, esercizio che per pigrizia affido a chi legge.

Ma non basta, perché un utente scafato potrebbe sfruculiare nell’ambiente VBA e scoprire la famosa password proprio esaminando l’istruzione predetta Foglio2.Protect Password:=... con quel che segue. Il rimedio, noioso, consiste in questi passaggi: Alt+F11; Strumenti > scheda Protezione > clic sulla casella di controllo Proteggi dalla visualizzazione > indicare la password nelle due sottostanti caselle di testo.

... e la macro che le ripristina

Eccola subito:

Sub RipristinaFormule()

  Dim Riga As Range

  Foglio1.Activate

  If Foglio2.Range("A1") = "" Then

    Exit Sub

  End If

  ' Soluzione brutale ma efficace

  ' che spazzola le righe delle colonne A e B

  For Each Riga In Foglio2.Columns("A:B").Rows

    If Riga.Cells(1, 1) = "" Then Exit For

    Range(Riga.Cells(1, 1)) = "=" & Riga.Cells(1, 2)

  Next

  Foglio1.Shapes("Pulsante 2").Visible = False

  Foglio1.Shapes("Pulsante 1").Visible = True

End Sub

Dopo l’esauriente spiegazione della prima Sub sono tentato di affidare il codice della seconda all’esegesi autogestita. Comunque sottolineo che per ogni Riga della coppia A:B di Foglio2 il codice
"=" & Riga.Cells(1, 2

aggiunge il segno uguale alla formula, infilandola poi in
Range(Riga.Cells(1, 1))

Su quest’ultima istruzione faccio solo notare che l’attivazione preliminare del Foglio1 (Foglio1.Activate) fa sì che Range sottintenda il Foglio1. E con questo mi fermo qui.

Un possibile sviluppo: estensione del metodo a più fogli

Chi non si accontentasse di segretare solo le formule del primo foglio di lavoro potrebbe estendere il metodo a quelli successivi. Il procedimento, cui i più avveduti avranno già pensato, consisterebbe nell’assegnare mano a mano le coppia di colonne C:D a secondo foglio, E:F al terzo e così via.

Va da sé che ora il foglio extra non può essere Foglio2. Il resto? È silenzio, se un bel tacer, a volte, si può scrivere.

posted @ venerdì 5 dicembre 2014 15:13 | Feedback (0) |

venerdì 29 agosto 2014

Inserimento clausole di apertura/chiusura in documenti burocratici

In documenti di tipo notarile e simili, sono previste espressioni standard di due tipi, poste di solito all’inizio e al termine dell’atto:

• L’anno duemilaquattordici addì ventitre del mese di febbraio alle ore…

• Documento composto da venti pagine, progressivamente numerate da 1 (UNO) a venti.

Per evitare la noia di digitare a mano tali logorroiche formule il ricorso a template Word non va, perché diversi termini variano da caso a caso, così ho escogitato due macro di inserimento, basate rispettivamente sulle funzioni seguenti;

Function DataBurocr() As String e

Function NumPagine(questoDoc As Document) As Integer

 Il codice per la clausola di apertura

Come appena detto, alla base si ha la funzione seguente, ove per semplicità si è rinunciato alla tipizzazione delle variabili, salvo la funzione DataBurocr. Probabilmente molti si attendono l’utilizzo della funzione VBA Date() che restituisce il numero seriale della data offerta dal clock del sistema, nonché le conseguenti funzioni Day, Month e Year traducendone subito dopo i risultati nei testi letterali corrispondenti. La funzione preannunciata non presenta sorprese, salvo per quanti obliassero o addirittura ignorassero la comoda funzione Array che permette di creare i vettori del caso:

Function DataBurocr() As String
    Giorni = Array("", "primo", "due", "tre", "quattro", "cinque", "sei", "sette", "otto", "nove", "dieci", "undici", "dodici", "tredici", "quattordici", "quindici", "sedici", "diciassette", "diciotto", diciannove", "venti", "ventuno", "ventidue", "ventitre", "ventiquattro", "venticinque", "ventisei", "ventisette", "ventotto", "ventinove", "trenta", "trentuno")
    Mesi = Array("", "gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno", "luglio", "agosto", "settembre", "ottobre", "novembre", "dicembre")
    Anni = Array("quattordici", "quindici", "sedici", "diciassette", "diciotto", "diciannove", "venti")
    Oggi = Date ' Numero seriale data odierna
    Giorno = Day(Oggi)
    Mese = Month(Oggi)
    indAnnoVs14 = Right(Year(Oggi), 2) - 14
    DataBurocr = _
   "L'anno duemila" & Anni(indAnnoVs14) & " addì " & Giorni(Giorno) & " del mese di " & Mesi(Mese) & " alle ore "
End Function

Ulteriori commenti.

La stringa vuota ("") all’inizio dei vettori Giorni e Mesi deriva dal fatto che un Array parte con indice zero, mentre Day e Month iniziano – ci mancherebbe – con 1. Qualcuno poi osserverà che in luogo della funzione Date si sarebbe potuto adottare la funzione Now, che fornisce il numero seriale con tanto di parte decimale (mentre Date ci dà solo la parte intera). Solo così infatti si potrebbe ricavare l'ORA, con Hour(Now), mentre Hour(Date) dà sistematicamente 0. Giusto!, però ho pensato che fosse meglio affidare all'utente la responsabilità di definire questo particolare, in quanto la clausola dataria inserita all'inizio troppo presto potrebbe risultare imprecisa

Qualcuno a questo punto si stupirà che per l’anno non si utilizzi una funzione di conversione da cifre a lettere, reperibile agevolmente su Internet. Il motivo pratico è legato, oltre che alla pigrizia, alla riflessione che non si debba operare da qui all’eternità, ma solo a partire dal 2014, andando avanti per un numero ragionevole di anni.

Chi vuole può estendere a suo uso e consumo il vettore Anni. In questo caso, comunque il... tormentino dell’indice zero viene risolto togliendo 14 al valore fornito dalle due ultime cifre di Year(Oggi) e inserendo il risultato nella variabile indAnnoVs14 (indice dell’anno rispetto al 2014).

Il concatenamento di stringhe che chiude la funzione si commenta da solo. Idem la macro che utilizza DataBurocr per infilarla in cima all’atto (salvo far notare che lo spostamento finale a destra si è reso necessario in quanto Selection.Text inserisce una stringa selezionata.

Sub InserDataBurocratica()
    ' MsgBox DataBurocr ' Usata per debug
    Selection.HomeKey Unit:=wdStory
    Selection.Text = DataBurocr
    Selection.MoveRight Unit:=wdWord
End Sub

Il codice per la clausola di chiusura

In questo caso confesso di essermi trovato inizialmente in difficoltà, perché – salvo segreti tuttora a me ignoti – nel modello a oggetti di Word non sembra esistere un oggetto Page o un insieme Pages.

Pensa e ripensa ho escogitato un trucco che ritengo brillante, senza falsa modestia. DEVO SUBITO ANTICIPARE CHE ALLA FINE HO SCOPERTO IL CODICE SPECIFICO VBA. L’algoritmo si basa su questi passi:

1. Inizia piazzando la strana coppia “#@” (difficilmente reperibile in atti burocratici) in testa al documento;

2. salta alla sua fine;

3. procedi a ritroso con ripetute mosse Ctrl+PagUp , incrementando il contapagine;

4. arresta il loop quando si arriva alla strana coppia in cima al documento.

L’inusitata funzione e la relativa macro che inserisce la clausola introduttiva sono riportate qui senza altre storie e pensando che i commenti inseriti nel codice siano sufficienti per chi non è alle prime armi col VBA.

Function NumPagine(questoDoc As Document) As Integer
   Application.ScreenUpdating = False
' Evita i movimenti di schermo, velocizzando il codice
   
Selection.HomeKey Unit:=wdStory
' Inizio documento
   
Selection = "#@"
' Coppia speciale di caratteri
   
Selection.EndKey Unit:=wdStory
' Fine documento
   
Dim Np As Integer
   Np = 1
' Inizializza il contapagine
  
While Selection <> "#@"
' Finché non incontri la coppia "#@"
  
   Application.Browser.Previous
' Equivale a Ctrl+PageUp
  
   Np = Np + 1
' Incrementa il contapagine
  
   Selection.MoveRight Extend:=wdExtend, Count:=2
' Seleziona i primi 2 caratteri
  
Wend
   Selection.Delete
' Cancella l'ultima coppia
  
NumPagine = Np
' Restituisci NumPagine
End Function

Sub ProvaNumPagine() ' Test di prova della funzione NumPagine
   
Application.ScreenUpdating = True
' Ripristina movimenti di schermo
   
MsgBox "Numero pagine: " & NumPagine(ActiveDocument)
End Sub

Sub InserChiusuraBurocratica()
    Application.ScreenUpdating = True
    Dim Np As Integer
    Np = ContaPagine(ActiveDocument)
    Selection.EndKey Unit:=wdStory
    Selection = _
    "Documento composto da " & Np & " pagine, progressivamente numerate da 1 (UNO) a " & Np
    Selection.EndKey Unit:=wdStory
End Sub

Il metodo funziona anche con documenti che comprendono immagini e tabelle.

La funzione che offre l'ambiente VBA, suggerita da D. CATTARUZZA

Public Function GetPageCount(doc As Document) As Integer
Dim oStat As WdStatistic
oStat = WdStatistic.wdStatisticPages
GetPageCount = doc.ComputeStatistics(oStat)
End Function


TUTTO QUI! MEGLIO TARDI CHE MAI..

posted @ venerdì 29 agosto 2014 16:15 | Feedback (0) |

martedì 1 aprile 2014

Ribbon personale per Office rivisitato

 

Personalizzazione del Ribbon, rivisitata

Gianni Giaccaglini

La barra multifunzione di Office, alias Ribbon, introdotta fin dalla versione 2007 è oggi più che familiare agli utenti. Nel frattempo si è persa di vista la possibilità di personalizzarla,specie in particolari documenti o modelli più o meno chiusi per l’utente finale. Confesso che ciò vale in particolare per chi scrive, a causa della macchinosità delle operazioni che, per giunta, non si possono automatizzare con codice macro, essendo il Ribbon un oggetto XML. In un precedente post su questo blog ho illustrato la possibilità di celare l’invadente Ribbon in Excel coi comandi che qui ripeto:

'Il Ribbon sparisce:

Application.ExecuteExcel4Macro "SHOW.TOOLBAR(""Ribbon"",False)"

'Il Ribbon riappare:

Application.ExecuteExcel4Macro "SHOW.TOOLBAR(""Ribbon"",False)"

 

Una misura così estremistica ha suscitato qualche critica, così ho deciso di pubblicare nuovamente informazioni piuttosto dettagliate sull’argomento, visto che oltretutto il trucco precedente non si applica a Word o PowerPoint.

La trattazione (quasi) completa

Office 2007 ha sostituito le molte precedenti barre strumenti (Commandbar) con un’unica barra multifunzione, in inglese Ribbon User Interface, abbreviato Ribbon UI. Le sole commandbar personali restano in vita, rese però obsolete da una tecnologia RibbonX (sta per Ribbon Extensibility), che descrive in linguaggio XML le caratteristiche di un ribbon personalizzato in un file customUI.xml, componente incorporato – almeno con Word, Excel e PowerPoint - in uno dato documento macro-enabled (xlsm, in Excel, docm in Word) o in un add-in (xlam in Excel) o un template Word dotm, secondo la famosa struttura open xml adottata da Microsoft con Office 2007. RibbonX consente multiformi, camaleontiche metamorfosi della nuova interfaccia utente (gli Autori Microsoft parlano di “fluent ribbon interface”) la cui natura XML fa sì che per tali personalizzazioni può bastare il Blocco Note. Occorre però apprendere la sintassi dei tag descrittori dei componenti del Ribbon, strutturati gerarchicamente: si va dalle tab (schede) ai group  (gruppi in cui si articolano le schede) fino ai control (controlli: pulsanti, caselle ecc.) cui si possono associare le cosiddette callback routine.

Si può così stravolgere sia il volto che le funzionalità, aggiunte o ridefinite, del ribbon. Le routine callback si possono scrivere in VBA (Visual Basic for application, le macro tradizionali) o C# (in ambito VSTO, Visual StudioTools per Office) e in questa sede di VBA ci occuperemo, per il suo più immediato utilizzo e perché identica è la natura e struttura di un archivio customUI.xml.

In medias res con un semplicissimo esempio

Proponiamoci l’aggiunta, in una cartella Excel macro enabled, di una tab (scheda) personale che si affianchi alle altre del ribbon, composta da un solo group (gruppo) comprendente due button (pulsanti di comando), ciascuno scatenante una determinata macro da noi creata.

Anticipazione importante. Prima di descrivere le operazioni da compiere, mi preme svelare che il passo 1 si può evitare compiendo direttamente la digitazione di cui al passo 2 nella finestra del Custom UI Editor, evitando anche altre noie relative ai passi da 4 in poi. Ne parlerò più avanti, comunque chi ha fretta può scaricare tale utile utility da questo sito, ove ne vengono descritte le peculiarità:

http://openxmldeveloper.org/blog/b/openxmldeveloper/archive/2006/05/26/customuieditor.aspx

Chi ha fretta può farlo direttamente da qui (NB: prima di communityserver c’è l’underscore _ ):

http://openxmldeveloper.org/cfs-file.ashx/_communityserver-components-postattachments/00-00-72-93/officecustomUIEditorSetup.zip

 

Procediamo con la prassi normale, che ha il pregio di chiarire bene quel che accade.

  1. Creare un file customUI.xml, con un editor di testo XML o anche il semplice Notepad.
  2. digitandovi questo codice XML:

<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">

  <ribbon>

    <tabs>

      <tab id="PersonTab" label="Mia Tab"  insertAfterMso="TabHome">

         <group id="PersonGruppo" label="Mio Gruppo">

            <button id="Puls1" size="large" tag="OraAttuale" label="Segnala l’ora" onAction="SegnalaOra" />

            <button id="Puls2" size="large" tag="LanciaUserForm" label="Lancia UserForm" onAction="LanciaUserForm" />

         </group>

      </tab>

    </tabs>

  </ribbon>

</customUI>

 

 

NOTA. La direttiva iniziale potrebbe recare Office/2009/07 in luogo di Office/2006/01 supportando così speciali novità di Office 2010/2013. Ma è una sottigliezza per espertissimi.

  1. Creare una cartella Excel PrimaProva.xlsm, e nell’Editor VBA inserire le macro seguenti, più una UserForm1 a piacere.

Sub SegnalaOra(ByVal controllo As IRibbonControl)

  msg = "Ore " & Hour(Time) & " e " & Minute(Time) & " minuti"

  'Indica l’orario e il nome del pulsante

  MsgBox msg, vbInformation, controllo.Tag

End Sub

 

Sub LanciaUserForm(ByVal controllo As IRibbonControl)

  'Richiama la UserForm...

  Load UserForm1

  UserForm1.Show

End Sub

 

Sono le Sub associate ai controlli Button1 e Button2 descritti nel customUI.xlm, il cui compito è ovvio, ma senza dimenticare l’argomento controllo, di ben preciso tipo IRibbonControl altrimenti la macro fallisce.

  1. Chiudere PrimaProva.xlsm e modificarne l’estensione .xlsm in .zip, poi con manovre date per note, aggiungervi il predetto customUI.xml.
  2. Aprire la cartella _rels  e aggiungere le righe seguenti prima della tag finale delle Relationships, una direttiva che indica il “target” che descrive il ribbon personale:

<relationship Id="someID" Type="http://schemas.microsoft.com/office/2006/relationships/ui/extensibility" Target="customUI/customUI.xml" />

NOTA. Si può pensare di sostituire customUI.xml con un nome diverso, ma è da sconsigliare specie se si usa l’editor ad hoc (v. più avanti).

  1. Chiudere il file .zip e ripristinarne l’estensione originaria .xlsm.

Alla riapertura di PrimaProva.xlsm si noterà la neonata Mia Tab, col gruppo etichettato "Mio Gruppo" che mostra due grossi pulsanti, corredati dalle label previste. Cliccando sull’uno o sull’altro otterremo quel che promettono.

Ciascuno potrà progettare una Userform1 di suo gusto oppure modificare le macro senza toccare il customUI.xlm. E una volta creato e incorporato quest’ultimo, le routine associate si possono modificare, in particolare per le eventuali UserForm richiamate.

Tab personali con pochi pulsanti soddisfano i casi normali della vita, per cui si può pensare di predisporre una serie di file customUI.xml tipici da adattare a varie esigenze per inserirli in questo o quel documento. Qui conviene poi anticipare la possibilità di salvare un file macro enabled che incorpora un customUI.xml d’uso generale in forma di add-in (.xlam), poi richiamabile in un qualsiasi file .xlsm (o .docm) che così assume quella particolare interfaccia utente senza avere in pancia un (proprio) customUI.xml.

La figura 1 illustra, senza commenti, una situazione più complessa conseguente all’aggiunta di un secondo gruppo della "Mia Tab", comprendente controlli di altro tipo, come una editBox e una comboBox, un pot pourri che però evidenzia la versatilità di questa tecnica e la sua perfetta consistenza col Ribbon.

http://www.giannigiaccaglini.it/download/FigRibbon-01.jpg

Figura 1

Altri particolari d’anteprima

I diversi componenti RibbonX godono di numerosi attributi. Oltre ai label o size già viste citiamo insertAfterMso=”TabData” o insertBeforeMso=”TabHome” e visible=”false” o enabled=”false”. I primi due collocano la tab personale accanto alla tab standard Home anziché in fondo alle tab.

Interessante l’opzione di apertura startFromScratch del tag ribbon:

<ribbon startFromScratch="true">

startFromScratch per default ="false" col valore "true" occulta tutte le tab standard, mantenendo solo i comandi Nuovo, Apri e Salva del menu Office. Tale direttiva, come si comprende è particolarmente utile per i modelli molto chiusi per l’utente finale.

Prima di procedere con una trattazione passabilmente sistematica riteniamo utile fornire subito una precisazione e qualche indicazione di documenti sul tema reperibili sul web.

RibbonX con Access e Outlook. L’esempietto preliminare e quelli che andremo a proporre prevedono l’incorporazione di un customUI.xlm nel documento XML (di regola in un’omonima cartella). Come tutti comprendono, se non ci hanno già pensato, ciò non ha senso con Access né tantomeno con Outlook, che non prevede documenti separati, tuttavia l’implementazione di ribbon personali è possibile anche in questi mondi, tramite add-in e… complicanze che esulano dagli scopi di questo articolo, interamente focalizzato su Excel, anche nella convinzione che i concetti base che esamineremo accomunano tutti i membri della famiglia Office 2007.

Link essenziali

Ripeto a beneficio di pigri & smemorati il link da cui scaricare Il Custom UI Editor:

http://openxmldeveloper.org/cfs-file.ashx/_communityserver-components-postattachments/00-00-72-93/officecustomUIEditorSetup.zip

(o con una ricerca via Google; tale link potrebbe variare nel tempo...)

Una volta installata tale utility la si può lanciare e subito compare una semplicissima dialog box con una commanbar che presenta un paio di opzioni. L’operazione inizia col comando Open seguito dall’indicazione del nostro file .xlsm o .docm, che sarà stato creato, perlomeno nei suoi termini essenziali. Subito dopo si scriverà nella finestra centrale del codice XML come quelle descritto sopra. Al termine occorre e basta salvare il lavoro.

La figura 2 illustra la creazione (o la successiva revisione) di un customUI.xml relativo al file .xlsm aperto. Rispetto all’elementare esempio precedente si hanno tra l’altro due gruppi, un combo box e una routine OnChange di quest’ultimo, legato alla scelta dell’elemento (item). Ne parleremo più diffusamente in seguito.

 

http://www.giannigiaccaglini.it/download/FigRibbon-02.jpg

Figura 2

Questa utility inserisce automaticamente le relationship di cui sopra, risparmiandoci una bella noia.

Un semplice esercizio

A beneficio dei principianti, rivisitiamo l’XML relativo al file PrimaProva.xlsm descritto all’inizio con qualche piccola variante:

<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">

  <ribbon startFromScratch="true">

    <tabs>

      <tab id="PersonTab" label="Mia Tab"  insertAfterMso="TabHome">

         <group id="PersonGruppo" label="Mio Gruppo">

            <button id="Puls1" size="large" tag="OraAttuale"

              label="Segnala l’ora" image="OROLOG" onAction="SegnalaOra" />

            <button id="Puls2" size="large" tag="LanciaUserForm" label="Lancia UserForm" image="ASINO" onAction="LanciaUserForm" />

         </group>

      </tab>

    </tabs>

  </ribbon>

</customUI>

 

Le novità, evidenziate in grassetto sono questi argomenti:

 startFromScratch="true" attributo aggiunto alla tag <Ribbon>;

image="OROLOG"aggiunto al <Button id=”Puls 1 ... >

image="ASINO"aggiunto al <Button id=”Puls 2 ... >

Le operazioni da compiere sono qui sotto elencate.

  1. Nello spreadsheet PrimaProva.xlsm preparare le macro SegnalaOra e LanciaUserForm viste in apertura.
  2. Creare una banale UserForm1 dotata di una TextBox1 e un CommandButton1 e, per dare un senso pratico al suo apparire, aggiungere una macro (normale) dell’evento Click del secondo:

Private Sub CommandButton1_Click()

  ' Contenuto della TextBox1 in A1

  Range("A1") = TextBox1.Text

End Sub

  1. Lanciare il Custom UI Editor avendo cura di salvare e chiudere PrimaProva.xlsm (altrimenti il passo seguente non avrebbe senso).
  2. Aprire PrimaProva.xlsm col comando File > Open.
  3. Dare un clic sull’icona Insert Icons e nella susseguente finestra attingere al file .jpg o .png che ci piace (suggeriamo di copiarlo preliminarmente nella stessa cartella ove risiede PrimaProva.xlsm). L’importante è che il nome da indicare nell’attributo Image sia privo di estensione (per es. image=”OROLOG” se il file è un certo OROLOG.jpg).

La Figura 3 esibisce una fase in cui il nostro Editor dell’ultima edizione 2010, presenta nel riquadro di destra le figurine man mano prelevate.

http://www.giannigiaccaglini.it/download/FigRibbon-03.jpg

Figura 3

  1. Salvare il risultato, ripetere l’operazione precedente per la seconda icona, salvare ancora e chiudere L’Editor.

La Figura 4 mostra il risultato, che pensiamo non meriti commenti.

http://www.giannigiaccaglini.it/download/FigRibbon-04.jpg

Figura 4

Un ampio repertorio di immagini appositamente studiate per il Ribbon e suddivise per categorie si trova all’URL seguente:

http://soltechs.net/customUi/imageMso01.asp

La pagina si presenta in sostanza così:

  • Click the Gallery link (Gal 1 through Gal 9) to see the images and the imageMso name you need all in one place.
  • Click in the Text Box with the imageMso information. It will select the text and you can copy and paste it into your project...
  • The galleries have between 245 and 321 individual .JPG images in them, so they take a while to load. The "All (overview)" page contains all 2,577 images in a static display.

E

Gal 1 (Large)

Gal 2 (Large)

Gal 3 (Large)

Gal 4 (Small)

Gal 5 (Small)

Gal 6 (Small)

Gal 7 (Small)

Gal 8 (Small)

Gal 9 (Small)

All (overview)

 

I vari Gal1, Gal2 ecc. danno adito alle varie “gallerie” di tipi, ciascuno corredato di una precisa stringa (cpme la “HappyFace” di cui fra poco, affiancata dalla figura risultante.

Importante. Nelle tag XML si deve usare l’attributo imageMso in luogo dello Image relativo a figure locali.

Vediamo subito un semplice esempio. Nella precedente tag,relativa al secondo pulsante basta sostituire a Image=”ASINO” con:

<button id="Puls2"imageMso="HappyFace"... />

Per vedere una nota faccina gialla ridente al posto dell’asinello.

NOTA. Il bello della faccenda è che non occorre, come con image, attingere a un nostro file locale. Basta copiare fedelmente la stringa indicata in quella tal galleria.

Altre immagini standard: “FilePrint”, “FilePrintPreview”, “FileSave, “Filter” ecc., molte relative a comandi già presenti nel Ribbon normale, con rischio di confusione qualora se ne associ macro troppo personali... Interessanti piuttosto cose come “BlogHomePage” o "StartAfterPrevious" che raffigurano, l’uno una casetta, l’altro un orologio.

Immagini per controlli normali della vita. Sono quelle che visualizzano i familiari controlli ActiveX, CommandButton, ListBox ecosì via. Sono reperibili nella Gal 4 (Small), di nome auto-eloquente:

“ActiveXButton”

“ActiveXComboBox”

imageMso="ActiveXListBox"

eccetera.

Creazione di add-in

In questo esempio ci proponiamo di creare un add-in di pura interfaccia che semplicemente ridisponga alcuni componenti standard (built-in, in inglese) raggruppandoli in modo più consono a un certo modus operandi. A tale scopo si cominci col creare un file Revisioni.xlsm e, dopo averlo salvato e chiuso mediante il Custom UI Editor inserirvi il file customUI.xml seguente:

<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">

  <ribbon>

    <tabs>

      <tab id="Audit" label="Revisioni" >

        <group idMso="GroupFormulaAuditing" />

        <group idMso="GroupFont" />

        <group idMso="GroupNumber" />

        <group id="Varie" label="Operazioni varie">

          <box id="SortBox">

            <control idMso="SortAscendingExcel" showLabel="false"/>

            <control idMso="SortDescendingExcel" showLabel="false"/>

            <control idMso="SortDialog" showLabel="false"/>

          </box>

          <control idMso="Copy" />

          <control idMso="PasteMenu" />

          <separator id="Separatore1" />

          <control idMso="NameManager" />

          <control idMso="ViewFreezePanesGallery" />

          <control idMso="WindowSwitchWindowsMenuExcel" />

        </group>

      </tab>

    </tabs>

  </ribbon>

</customUI>

Aprendo di nuovo Revisioni.xlsm noteremo in fondo al Ribbon la nuova tab Revisioni di figura 5.

http://www.giannigiaccaglini.it/download/FigRibbon-05.jpg

Figura 5

Revisioni.xlsm potrà servire per modifiche successive dell’add-in di cui stiamo per indicare le operazioni necessarie.

Per creare un add-in occorre eseguire le mosse qui sotto indicate.

 

1.       Aprire un file come il nostro Revisioni.xlam.

2.       Salvarlo con Salva con nome... scegliendo Tipo File: Componente Aggiuntivo di Excel (*.xlsm)

Si otterrà così la registrazione sistematica nella sottocartella Addins di Excel. Da quel momento esso si può utilizzare scegliendo Revisioni.xlsm nell’elenco dei Componenti aggiuntivi, che aggiunge la tab Revisioni a qualunque file Excel aperto. In verità in nostre prove nelle versioni dalla 2007 alla 2013 confessiamo di essere incappati a fallimenti, a volte ovviati con un provvidenziale pulsante Sfoglia... Auguri!

NOTA. In ogni caso ci sentiamo di sconsigliare di creare add-in partendo da file .xlsm o .docm dotate, come nel caso PrimaProva.xlsm, di macro dotate di argomento ByVal controllo As IRibbonControl legate a un modello particolare: darebbero errore su file qualsiasi! Di regola esse dovrebbero comprendere esclusivamente codice di tipo generale (trattamento di celle, righe, formati ecc.). Ma ne vale la pena?

Nella giungla delle tab, dei gruppi e dei controlli

Ai più esperti suggeriamo anzitutto di cercare articoli sul Ribbon nell’(affollato) blog di Frank Rice:

http://blogs.msdn.com/b/frice/

Il nostro fluent ribbon presenta una miriade di componenti. Con un po’ di pazienza sul sito Microsoft si possono reperire e scaricare diversi file Excel, contenenti l’elenco completo dei controlli e dei relativi tab o gruppi dei vari membri di Office 2007/2011/2013.

Per chi non ha grandi ambizioni ne forniamo qui sotto uno scampolo relativo a Excel, limitato alle tab TabHome e TabInsert, depurato di talune intestazioni e di molte voci:

Control Name

Control Type

Tab Name

Group Name

TabHome

Tab

 

 

GroupClipboard

Group

TabHome

 

Paste

Button

TabHome

GroupClipboard

PasteSpecialDialog

Button

TabHome

GroupClipboard

Cut

Button

TabHome

GroupClipboard

Copy

Button

TabHome

GroupClipboard

Bold

toggleButton

TabHome

GroupFont

Italic

toggleButton

TabHome

GroupFont

Underline

toggleButton

TabHome

GroupFont

GroupNumber

Group

TabHome

 

NumberFormatGallery

comboBox

TabHome

GroupNumber

GroupCells

Group

TabHome

 

InsertCellstMenu

splitButton

TabHome

GroupCells

AutoSum

Button

TabHome

GroupEditingExcel

TabInsert

Tab

 

 

PivotTableInsertMenu

splitButton

TabInsert

GroupInsertTablesExcel

PivotTableInsert

Button

TabInsert

GroupInsertTablesExcel

PivotChartInsert

Button

TabInsert

GroupInsertTablesExcel

Cerchiamo di fissare i punti salienti. Tutti i componenti del Ribbon sono in inglese e validi pure nelle edizioni localizzate. I nomi di ciascuno sono definite mediante tre distinti identificatori (univoci):

-      idMso per i componenti standard (es. <group idMso="GroupFont" />);

-      id per quelli customizzati (es. <box id="SortBox">);

-      idQ, identificatori "qualificati", relativi a specifici namespace e dei quali diamo solo cenno.

Tali parole chiave non vanno confuse tra loro, rispettandone i ruoli!, inoltre sottolineiamo la necessità di rispettare rigorosamente maiuscole e minuscole, come ben sa chi si è accostato all’XML, pena fastidiosi rigetti. Un esempio per tutti è onAction, che i primi tempi è facile scrivere OnAction, un termine VBA consimile (relativo a pulsanti delle Commandbar tradizionali).

Le tab built-in principali sono TabHome, TabInsert, TabPageLayoutExcel, TabFormulas, TabData, TabReview, TabView, TabDeveloper (se l’utente l’ha attivata) cui va aggiunto un OfficeMenu. Ognuno di questi "padri" ha come figli dei Gruppi. I gruppi sia built-in che personali possono comprendere controlli di vario tipo. I puntini alludono ai diversi possibili attributi dei vari componenti. Indichiamo i principali, avvertendo che lo spazio non ci consente di trattarli tutti né tantomeno a fondo:

  • <button...>, pulsante di commando;
  • <toggleButton...>, pulsante premuto/non premuto; più toggleButton corrisponde a una serie di pulsanti di opzione, mutuamente esclusivi;
  • <checkBox...>, casella di selezione;
  • <editBox...>, casella di testo;
  • <gallery...>, griglia tipica del Ribbon;
  • <item...>, elemento di un controllo comboBox o dropDown;
  • <dialogBoxLauncher...>, "lanciatore" di una dialog box;
  • <separator ...> ¸linea verticale di separazione di controlli.

Prima di proseguire con (alcuni) controlli "contenitori", invitiamo a rivedere i due esempi finora forniti, in particolare per quel che riguarda il separatore, e liquidiamo il dialogBoxLauncher illustrandolo con una variante del primo esempio:

   <dialogBoxLauncher>

      <button id="Lanciatore" screentip="Lancia una UserForm..."

      onAction="LanciaUserForm" />

   </dialogBoxLauncher>

</group>

Il dialogBoxLauncher deve incastonare un button, auspicabilmente dotato di attributo screentip (suggeritore che ne indica lo scopo quando il mouse lo sfiora) fa apparire la tipica freccina in basso a destra di certi gruppi, che per l’appunto lancia una finestra di dialogo (o una qualsiasi macro). Nel nostro caso onAction attiva la medesima macro del Puls2.

Ma ecco solo alcuni controlli destinati a contenere altri controlli:

  • <comboBox...>, contiene più item;
  • <dropDown...>, contiene più item e/ button;
  • <box...>, riquadro che raggruppa ogni tipo di controllo;
  • <menu...> menu pop-up con ogni tipo di voce, controllo o sotto menu.

Il box è stato usato nel caso Revisioni. Quanto al combo Box si riveda la figura 1 e si esamini il seguente brano, senza commenti perché la sintassi XML ci pare eloquente.

  <comboBox id="MioCombo" tag="ScegliEl" label="Scegli il mese"

   onChange="ScegliMese">

     <item id="Elem1" label="Gennaio" />

     <item id="Elem2" label="Febbraio" />

     <item id="Elem3" label="Marzo" />

  </comboBox>

I limiti della nostra trattazione e la convinzione che i componenti base di RibbonX bastano e avanzano per la maggioranza dei casi pratici ci spingono a citare soltanto lo splitButton e il buttonGroup, complessi contenitori che permettono di imitare ogni aspetto della nuova interfaccia.

Gli attributi

Tanto per cambiare, sono numerosi, e il guaio è che non esiste allo stato nessuno strumento suggeritore (l’intellisense? è un sogno). Si può comunque avere un’indicazione di errori ex post. Tale funzionalità va attivata in ciascun applicativo Office (altrimenti il nostro sudato ribbon, se contiene il minimo errore non appare proprio!).

Le operazioni da compiere in Excel sono le seguenti: clic sul pulsante Office, poi scegliere le Opzioni di Excel; clic sulle Impostazioni avanzate, e spostarsi in basso fino alla sezione Generale; attivare infine la casellina "Mostra errori dell'interfaccia utente dei componenti aggiuntivi".

Gli attributi comuni a tutti i controlli, oltre ai predetti identificatori sono, per limitarsi ai principali: image (controlli nostrani) e imageMso (controlli standard), label, tag, screentip e supertip, enabled, visible, size e showLabel. I primi due corredano il controllo di un’immagine. Ripetiamo l’esempietto tipico già visto sopra:

<button id="Puls1" size="large" tag="OraAttuale"

       label="Segnala l’ora" image="OROLOG" onAction="SegnalaOra" />

 

Il capitolo immagini è divertente quanto... ridondante. Si possono usare immagini predefinite, tra cui quelle dei controlli built-in (i cui nomi si possono scaricare dal sito msdn), immagini (jpg o png, possibilmente) presenti sul proprio PC, aiutati in tal caso dal customUI Editor con il comando Insert icons di facile sperimentazione. Su possibilità più avanzate è giocoforza sorvolare.

Tornando a tag e label, la seconda è un’etichetta esplicativa del controllo mentre la prima è un attributo richiamabile in una procedura callback:

Sub MiaCallback(Ctrl As IRibbonControl)

   Msgbox "Sono il controllo " & Ctrl.Tag

End Sub

Va qui precisato che un oggetto IribbonControl gode anche della proprietà Id, l’identificatore del controllo, e null’altro (non esiste alcuna proprietà riconducibile alla label).

L’attributo size ha due soli valori: "large" e "normal" e, in realtà, non è supportato da tutti i controlli. In particolare, la larghezza di una ComboBox o di una casella editBox si regola con sizeString cui assegnare una stringa arbitraria della lunghezza voluta (12 caratteri, nell’esempio seguente):

<editBox id="MiaCas" sizeString="zzzzzzzzzzzz"... />

Le procedure callback

Sono routine associate ai vari controlli  e/o a loro attributi. Con qualche arbitrio ne distinguiamo due tipi:

  • onAction e onChange, la prima scatenata dal clic su un button, toggleButton, checkBox, dropDown o gallery, la seconda dal mutamento di un’editBox o un Combobox;
  • le routine con prefisso get, come getLabel, getPressed, getText e numerose altre.

La nostra distinzione mira a evitare equivoci circa il funzionamento a run-time. La più "tranquilla" al riguardo è onAction, ma già con onChange si presenta il problema delle firme (signature) della callback associata, che nel secondo come in altri casi è duplice.

Esempio, relativo a una comboBox supposta formata dalle voci (item): "Gennaio", "Febbraio" e "Marzo".

Sub OnChangeCallback(Ctrl As IRibbonControl, Testo As String)

   Select Case Testo

     Case "Gennaio"

      ...

     Case "Febbraio"

      ...

     Case "Marzo"

      ...

  End Select

End Sub

Il secondo argomento alias firma, contiene il testo dell’item scelto (il testo digitato dall’utente nel caso editBox). Volendone conservare traccia, si danno due modi: una variabile definita a livello modulo:

Dim ItemScelto As String

 

Sub OnChangeCallback(Ctrl As IRibbonControl, Testo As String)

  ItemScelto = Testo

End Sub

In Excel c’è l’alternativa della registrazione in una cella, diciamo Range("ItemScelto") = Testo. Nell’uno o nell’altro caso il dato della variabile a livello modulo può essere usato da altre, normali Sub.

 Venendo a routine come getLabel, getImage e altre, esse espongono una seconda firma RetVal di tipo Variant (anziché String, badare bene).

NOTA. Nelle callback come pure in quelle di evento (es. Change) si può assegnare agli argomenti qualsiasi nome, a patto di rispettarne ordine e tipo. L’abbiamo fatto con Controllo, Testo e RetVal al posto dei canonici Control, Text e ReturnValue. Si provi con Bersaglio in luogo del Target suggerito in Change: il risultato è lo stesso.

Ora la situazione è più limitante, in quanto tali Sub servono all’acquisizione dinamica di un’etichetta, un’immagine ecc., che però avviene solo all’avvio del documento.

Premesso che l’attributo getLabel è goduto anche dai gruppi, ecco un caso tipico. Si abbia un add-in, o anche solo un modello, il cui customUI comprende un gruppo "Azienda" da etichettare col nome della ditta, supposta scritta nella cella A1. Ed ecco una riga XML seguita dalla sua brava callback:

<group id="Azienda" getLabel="PrendiNomeAz">

Sub PrendiNomeAz(Ctrl As IRibbonControl, RetVal As Variant)

  RetVal = Range("A1").Value

End Sub

Una variante banale potrebbe poi essere l’istruzione RetVal = InputBox (“Nome della ditta").

NOTA. Una curiosità. Una sub come la precedente potrebbe surrogare l’evento Open? Certo, basta inserirvi qualsiasi istruzione anche non pertinente il Ribbon. E non c’è conflitto con Open, perché questa parte comunque per prima. Comunque meglio evitare, per la chiarezza dei vari ruoli.

Dimenticavamo una segnalazione di firme (l’ultima), relativa alla procedura onAction dei controlli checkBox e  toggleButton, che presenta il booleano Pressed come secondo argomento, di cui tutti dovrebbero capire l’uso.

Uno schema generale

Il RibbonX si compone di varie parti, che si susseguono secondo lo schema seguente.

<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">

  <commands>

    <command ... />

  </commands>

  <ribbon ... >

    <officeMenu>

      controlli del menu Office

    </officeMenu>

    <qat>

      <sharedControls>

        controlli condivisi

      </sharedControls>

      <documentControls>

         Controolli di documento

       </documentControls>

    </qat> <!—Quick access toolbar-->

    <tabs>

      <tab ... >

        <group ... >

           Controlli vari

        </group> </tab>

    </tabs>

    <contextualTabs>

      <tabSet idMso=”TabSetChartTools”>

        <tab ... >

          <group ... >

             controlli vari

          </group>

        </tab>

      </tabSet>

    </contextualTabs>

  </ribbon>

</customUI>

 

Lo riportiamo quasi solo per dare un’idea della gran varietà del fluent Ribbon. In particolare, la sezione dei Commands  serve a ridefinire le azioni associate a comandi standard e la qat a inserire controlli nella Quick Access Toolbar.

Uno schema XML ibrido per uso “dittatoriale”

Lo schema che stiamo per vedere utilizza l’opzione startFromScratch=”true” e la sezione di ridefinizione dei comandi, relativamente a quelli del menu Office, in modo da soddisfare varie esigenze:

  • mantenere la tab Home consentendo all’utente le formattazioni;
  • eliminare il comando Nuovo (cattiveria per meri fini didattici);
  • resuscitare invece il pulsante Stampa, ma associandolo a una macro particolare;
  • in conseguenza, disabilitare e occultare i pulsanti sottostanti al menu Stampa ossia: Stampa (pulsante, da non confondere col suo omonimo padre), Stampa immediata e Anteprima di stampa.

Lo schema qui sotto è ricco di commenti che ne facilitano la comprensione.

<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">

  <commands>

   <command idMso="FileSave" />

   <!—Disabilita il comando Nuovo-->

   <command idMso="FileNew" enabled="false" />

   <!—Associa il comando Stampa alla macro StampaCondiz-->

   <command idMso="FilePrint" onAction="StampaCondiz" />

  </commands>

  <ribbon startFromScratch="true">

    <officeMenu>

     <!—Abilita e mostra il menu Print-->

     <menu idMso="FilePrintMenu" enabled="true" visible ="true">

      <!—Disabilita i 3 pulsanti sotto il menu Print-->

      <button idMso="FilePrintPreview" enabled="false" visible="false" />

      <button idMso="FilePrint" enabled="false" visible="false" />

      <button idMso="FilePrintQuick" enabled="false" visible="false" />

     </menu>

    </officeMenu>

    <tabs>

     <!—visualizza la tab Home-->

     <tab idMso="TabHome" visible="true" />

     <!—Inserisci una tab personale prima della tab Home-->

     <tab id="PersonTab" label="Mia bella Tab" insertBeforeMso="TabHome">

       <group id="GruppoEsempio" label="Macro varie">

        <button id="Button1" size="large" tag="AggTotali"

         label="Aggiungi totali" onAction="AggTot"/>

        <button id="Button2" size="large" tag="PiccoloTest"

         label="PiccoloTest" onAction=" PiccoloTest"/>

       </group>

     </tab>

    </tabs>

  </ribbon>

</customUI>

 

Per comprendere la cosa esaminare il file ExcelRibbonControls.xls, dal quale si evince che FilePrintMenu è un gruppo che fa parte della tab OfficeMenu e, insieme, è – si badi bene! - un menu, comprendente i button FilePrint, FilePrintQuick (stampa immediata) e FilePrintPreview (anteprima). Si dovrebbe così comprendere il significato delle tag incastonate tra <menu …> e </menu>, nonché il fatto che la prima, relativa a FilePrintMenu lo abilita e visualizza, a onta della direttiva dittatoriale startFromScratch=”True” che lo vorrebbe defunto. Quanto ai button compresi nel menu Office gli specifici tag li tolgono di mezzo.

Quanto ai command  incapsulati tra commands e /commands vi ritroviamo pulsanti standard (built-in) come quelli facenti parte del menu Office. Risulta così possibile ridefinirne la funzione mediante macro onAction associate che vanno a sostituire e prevaricare (inglese "override") la funzione normale.

Venendo a possibili routine associate, quella che prevarica sul normale funzionamento del comando Stampa potrebbe essere la seguente che impone che la stampa avvenga solo al mattino:

Sub StampaCondiz(ctrl As IRibbonControl, cancelDefault As boolean)

  Dim Msg As String

  Msg = "Buogiorno! Puoi Stampare" & vbLf & "solo al mattino! "

  If Time > 0.5 Then

    MsgBox Msg & " Spiacente..."

  Else

    MsgBox Msg & " Contento?"

    ThisWorkbook.PrintPreview

  End If

  cancelDefault = False

End Sub

Si noti l’argomento cancelDefault, che mantiene l’override.

Quest’altra Sub, viene invece scatenata dal primo pulsante della Mia bella tab e aggiunge la funzione =SOMMA() in una zona pre-denominata "Totali" posta in fondo a un elenco con intestazioni:

Sub AggiungiTot(ByVal ctrl As IRibbonControl)

  Dim RifCol As String

  With Range("Totali")(0, 1)

    RifCol = Range(.Cells(1), .End(xlUp)(2, 1)).Address(False, False)

  End With

  Range("Totali").Formula = "=SUM(" & RifCol & ")"

End Sub

Quanto alla macro associate al secondo pulsante della tab ognuno può crearsela a suo gusto. Ribadiamo infatti che a patto di mantenere i nomi delle callback il codice relativo può essere sempre modificato senza ritocchi al Ribbon custom.

NOTA. I modelli più chiusi per l’utente finale non possono comunque impedire che egli ricorra a short cut di tastiera, per Salvare, stampare e quant’altro. Accadeva lo stesso con le Commandbar.

Altro caso di Office menu personalizzato

Lo schema XML, relativo alla sola sezione del menu Office è il seguente:

<officeMenu>

  <!--I pulsanti di officeMenu NON supportano l’attributo size-->

  <button id="OffButton1" label="PulsOffice 1"

   imageMso="HappyFace" onAction="MacPulsOff1" />

  <button id="OffButton2" label="PulsOffice 2"

   imageMso="HappyFace"  onAction="MacPulsOff2" />

  <menu id="MioOfficemenu" label="Sotto-menu del menu Office">

    <button id="mioMenuOffbtn1" label="Mio menu Office puls. 1"

     onAction="MacPulsmioMenu1" />

    <button id="mioMenuOffbtn2" label="Mio menu Office puls. 2"

     onAction="MacPulsmioMenu2" />

   </menu>

</officeMenu>

 

Per brevità si riporta soltanto il risultato nella figura 6, confidando nella sua eloquenza. E le callback sono lasciate per esercizio.

http://www.giannigiaccaglini.it/download/FigRibbon-06.jpg

Figura 6

Aggiornamento del Ribbon a run-time

Chi ha ben seguito la nostra trattazione avrà compreso che la tecnologia RibbonX ha diverse limitazioni (almeno per i palati più esigenti), che a nostro avviso si collegano al fatto che non si tratta di un vero modello a oggetti. In particolare non sembra possibile accedere ai vari controlli per ottenerne o, peggio ancora, modificarne programmaticamente proprietà o valori. Abbiamo intravisto una scappatoia nell’uso di variabili a livello modulo aggiornate da talune (non tutte) routine Callback come onChange o getText.

Un’altra possibilità avanzata, anche se un poco contorna è data dalla callback onLoad relativa all’intero ribbon, che in una certa misura permette di ridefinirlo a run-time e, con esso, gestirne taluni componenti.

Un esempio specifico è la scelta migliore. La figura 7 mostra un modello dotato di una tab personale comprendente una casella di testo, di cui si vuole aggiornare il contenuto tramite macro, l’appena annunciata callback onLoad.

http://www.giannigiaccaglini.it/download/FigRibbon-07.jpg

Figura 7

L’XML relativo è riportato qui sotto.

<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui"

 OnLoad="RibbonCarica">

  <ribbon startFromScratch="true">

    <tabs>

      <tab id="TabPers" label="MiaTab">

        <group id="GruppoPers" label="Mio gruppo">

          <editBox id="MiaCas" label="Scrivi:"

           sizeString="precipitevolissimevolmente" getText="Prenditesto" />

          <separator id="Lineadivisoria" />

          <button id="MioPuls" label="Ruota testo" size="large"

           onAction="RuotaTesto" imageMso="HappyFace" />

        </group>

      </tab>

    </tabs>

  </ribbon>

</customUI>

 

Ed ecco la fatidica callback associata all’attributo onLoad del nostro ribbon e quelle associate alla editBox e al button della tab personale.

Dim RegRibbon As IRibbonUI

 

Sub RibbonCarica(ribbon As IRibbonUI)

  Set RegRibbon = ribbon

End Sub

 

Sub PrendiTesto(Ctrl As IRibbonControl, RetVal)

  RetVal = Range("CellaOrigine")

End Sub

 

Sub RuotaTesto(Ctrl As IRibbonControl)

  'Callback del pulsante MioPuls

  Dim N As Integer, Testo As String

  With Range("CellaOrigine")

    N = .Characters.Count

    Testo = .Text

    .Value = Right(Testo, 1) & Left(Testo, N - 1)

  End With

  RegRibbon.InvalidateControl ("MiaCas")

End Sub

 

La sub RibbonCarica imposta (Set) il nostro ribbon nella variabile definita a livello modulo RegRibbon di tipo IRibbonUI che inequivocabilmente si attaglia a un oggetto ribbon. Anche la Sub PrendiTesto dell’editBox non ha misteri: preleva il valore della cella pre-denominata "CellaOrigine" (B4, in figura). Nei casi normali tale acquisizione si avrebbe una tantum, al caricamento del nostro ribbon, ma qui interviene la classe IribbonUI cui appartiene RegRibbon. Essa espone due soli metodi:

-       Invalidate e

-       InvalidateControl(Idcontrollo)

Il primo "invalida" l’intero nastro customizzato, in pratica inizializzandolo nuovamente, il secondo metodo compie lo stesso mestiere limitatamente al controllo specificato, tramite il suo Id, "MiaCas" nella fattispecie. Ciò consente alla Sub RuotaTesto connessa al button "MioPuls" di modificare la "CellaOrigine", in questo caso facendo ruotare ciclicamente di un carattere il suo testo, che subito viene acquisito dalla editBox tramite la sua "risvegliata" callback PrendiTesto. Lo stesso avviene con una routine esterna del tutto simile, connessa al pulsante classico del foglio Ruota un carattere e in entrambi i casi ci si può divertire con clic ripetuti che via via girano in circolo la scritta sia nella cella che nell’editBox.

Tutto bene? Non al cento per cento. Una routine connessa al secondo pulsante di figura che invoca la Sub Ruota un carattere per un numero di volte pari alla lunghezza del testo, a sorpresa agisce soltanto nella cella, mentre l’editBox resta immutato. Il motivo? Appare misterioso, comunque non c’è che da prendere atto di questa oggettiva limitazione.

Affidiamo altri dettagli e gli aspetti più avanzati a quanti volessero approfondire il romanzo di un giovane… ricco. Fin troppo, per cui la nostra trattazione termina qui, convinti che basti, anzi travalichi le normali necessità.

posted @ martedì 1 aprile 2014 09:37 | Feedback (0) |

martedì 25 marzo 2014

Importanti novità di Windows Azure

 

Importanti novità di Windows Azure

Gianni Giaccaglini

Il sistema Windows Azure di Microsoft è in continua evoluzione e viene costantemente aggiornato e migliorato. In questo articolo, tratto da un intervento della brava Eva Gjeci¸ Technical Evangelist  di Azure, passeremo in rassegna i principali servizi e le funzionalità introdotte recentemente, accennando a come si possono sfruttare al meglio per sviluppare e/o migrare applicazioni cloud.

Ricordiamo che Azure è una piattaforma cloud rilasciata nel febbraio 2010, che permette di creare, dispiegare e gestire applicazioni e servizi tramite migliaia di server server e relativi datacenter sparsi nel mondo gestiti da Microsoft. Alla sua base risiede il Sistema Microsoft Hyper-V (che rivaleggia con il famoso Hadoop di Apache), su cui poggiano le famose machine virtuali (VM).

Informazioni generali in materia si trovano su Wikipedia:

http://en.wikipedia.org/wiki/Windows_Azure

A programmatori e analisti suggerisco invece i due manuali seguenti:

Windows Azure, programmare per il Cloud Computing
di Fabio Cozzolino – Ed. FAG (www.fag.it)

Azure in action
di Chris Hay e Brian H. Prince – Ed. Manning (www.manning.com )

La classificazione dei servizi cloud offerti da Azure divide I commentator, inclusi quelli del succitato post Wikipedia che parlano di servizi sia di tipo PaaS che IaaS. È opinione personale di chi scrive, per quel che vale, che il supporto infrastrutturale di Azure non sia alla pari con quello di Amazon (by the way, Azure e Amazon sono in testa alla classifica dei sistemi cloud pubblici). Tra l’altro la gestione dei dati privilegiata e prevalente si fonda su servizi Storage (cosiddetti)  tipo NoSQL, mentre i servizi SQL, ossia strutturati, relazionali hanno limiti di ampiezza. Chiudiamo questa parentesi affermando che comunque Azure, non troppo idonea far migrare sulla nuvola programmi tradizionali, si presenta appetibile per nuove applicazioni più consone al nuovo mondo cloud, grazie a copiosi tools e framework forniti da Microsoft-e terze parti.

Che c’è di nuovo

Le indicazioni che seguono, tratte dall’intervento di Eva Gjeci ,richiedono ulteriori approfondimenti, comunque danno un quadro della varietà e dell’importanza dei molti e significativi potenziamenti e migliorie. La figura che segue mette ne mette i molteplici punti del mondo Azure caratterizzati da innovazioni.

http://www.giannigiaccaglini.it/download/FigAzure-01.jpg

Proseguiamo esponendo, un po’ alla rinfusa, le novità più interessanti.

Agente VM. È un processo che può essere eseguito all’interno di macchine Windows e Linux. Può essere utilizzato per installare e gestire estensioni come moduli software che estendono le funzionalità della VM.

Siti Web. In quest’area si danno numerose nuove, e utili, funzionalità:

-          Supporto alla pubblicazione in Staging. Consente di dispiegare gli aggiornamenti di un’applicazione in una versione di staging, accessibile tramite un URL diverso da quello utilizzato dalla versione in produzione.

-          Backup e Ripristino, per salvare uno snapshot del sito insieme a qualsiasi DB (SQL o MySQL).

-          Web jobs: fornisce un modo semplice per eseguire programmi quali servizi o attività in background in un sito Web. È possibile caricare ed eseguire un file eseguibile come un .exe, cmd o. bat file.

-          Supporto Web Sockets.

-          Supporto Remote Debugging.

-          Supporto Always-on.

Ma le innovazioni più interessanti, se non altro per la loro crescente attualità, sono quelle relative al settore Mobile. La figura seguente ne fornisce un’idea, evidenziando che si esplicano in tutti i vari aspetti.

http://www.giannigiaccaglini.it/download/FigAzure-02.jpg

 ALTRE NOVITA'

Si danno, infine:

Nuovi servizi in GA, per i quali è garantito uno SLA, supportato da Microsoft Support. Tra questi si segnalano un Hyper-V Recovery Manager e un BizTalk Service.

Scheduler Service, servizio che  consente di pianificare job che richiamano endpoint HTTP/S o di inviare messaggi a una coda di archiviazione su qualsiasi pianificazione definita.

Monitoring. In quest’area si danno ora che permettono dati aggiornati ogni minuto, con un maggior numero di metriche e notifiche sull’interruzione del servizio. Il rilascio della Windows Azure Monitoring Service Library agevolerà il lavoro degli sviluppatori.

Riduzione dei prezzi

Da marzo 2014 Microsoft ha ridotto i prezzi di Windows Azure Storage e offre una versione di prova di Azure, che per un mese offre gratis € 150 di utilizzo.

Altre informazioni riguardo a Windows Azure sono reperibili ai link seguenti.

-  http://blogs.msdn.com/b/windowsazure/ (blog ufficiale di Windows Azure)

-   http://weblogs.asp.net/scottgu/ (blog dell’esperto Scott Guthrie)

-  http://www.windowsazure.com/en-us/documentation/ (sito ufficiale di Azure)

 

posted @ martedì 25 marzo 2014 09:16 | Feedback (0) |

mercoledì 5 marzo 2014

Un utile libro Manning su Windows Phone 8

 

Un utile libro Manning illumina il mondo Windows Phone 8

TITOLO:
Windows Phone 8
IN ACTION
Autori:
Timothy Binkley-Jones, Massimo Perga, Michael Sync, Adam Benoit

Pagine: 496

PREZZI

Libro stampato + Ebook: $44,90
solo EBook: $35,99

URL: http://www.manning.com/binkley/

______________________________________

Il settore degli smartphone cresce a ritmi tali da superare ormai, in numeri e fatturato, quello dei PC e notebook classici. In tale contesto aumenta di pari passo l’interesse degli sviluppatori, attratti dalle possibilità di guadagno offerte dai vari magazzini (Store) su cui pubblicare le proprie app destinate al vasto parco pubblico di consumatori o, magari, ad usum Delphini (leggi: aziende e altre organizzazioni).

Questo testo dell’Editore Manning  scritto da quattro esperti di linguaggi Microsoft, ergo a otto mani, copre quasi tutti gli aspetti dell’ambiente Windows Phone 8 sia strutturali che sintattici, con dovizia di snippet ed esempi pratici. L’esposizione parte con un’ampia panoramica dell’arena dei telefonini intelligenti, ove Microsoft deve vedersela con la perdurante supremazia di Apple/iOS e Google/Android. Questa parte, che a nostro avviso può interessare anche i non codificatori, ossia analisti ed IT Manager, costituisce un discorso piuttosto approfondito sulle differenze fra i vendor rivali mostrandone altresì le somiglianze (al punto di delineare la possibilità di migrare un’app da un sistema all’altro). Significativa, al riguardo, la franca ammissione di taluni limiti di Windows Phone 8 rispetto ai concorrenti, soprattutto per la gestione di dati esterni. Probabilmente, ci è parso di capire, Microsoft colma tale lacuna con la possibilità di integrare le sue app con altri prodotti, ossia non solo con Windows 8, di cui Win Phone è, diciamo così, cugino, ma con ambienti web comunitari e cloud quali SharePoint o Azure.

Primi passi in Windows Phone

Il manuale prosegue con una puntuale e precisa descrizione dell’architettura, evidenziando fra l’altro la derivazione dal predecessore Windows 7 e fornendo concreti consigli sul riuso di precedenti programmi, in sostanza per la permanente validità di API dell’ambiente .NET e dei linguaggio di fondo CLR, nonché del framework XAML, il linguaggio dichiarativo nato nato con WPF (Windows Presentation Foundation).

Ma questa è una recensione, non il compendio di un manuale di 497 pagine, 18 capitoli più due Appendici, così per non tediare rubiamo subito dal libro il seguente brano XAML.

<Grid x:Name="LayoutRoot" Background="Transparent">

  <Grid.RowDefinitions>

    <RowDefinition Height="Auto" />

    <RowDefinition Height="*" />

  </Grid.RowDefinitions>

  <!--TitlePanel contains the name of the application and page title-->

  <StackPanel x:Name="TitlePanel"

          Grid.Row="0"

          Margin="12,17,0,28">

    <TextBlock x:Name="ApplicationTitle"

        Text="MY APPLICATION"

        Style="{StaticResource PhoneTextNormalStyle}" />

    <TextBlock x:Name="PageTitle"

        Text="page name"

        Margin="9,-7,0,0"

        Style="{StaticResource PhoneTextTitle1Style}" />

  </StackPanel>

  <!--ContentPanel - place additional content here-->

  <Grid x:Name="ContentPanel"

    Grid.Row="1"

    Margin=”12,0,12,0”>

  </Grid>

</Grid>

Chi mastica almeno i rudimenti dell’XAML (che, per inciso, i sullodati... “ottumani” illustrano gradualmente, dedicando poi un’ampia sezione, valida per neofiti ed esperti) capisce subito che il precedente codice descrive una griglia (Grid) che racchiude una catasta (Stack), eccetera, il tutto comprendente due blocchi di testo (TextBlock) di dimensioni e proprietà ben precise.

L’anteprima mostra il risultato dell’XAML precedente, visualizzato da un particolare  tool di emulazione, grazie al quale è possibile redigere un’app, debuggarla e testarla non su uno Smartphone (sarebbe scomodo, anzi privo di senso) ma su un PC attrezzato con l’ambiente di sviluppo Win Phone 8 e la specifica IDE, che come ci si attende rassomiglia molto a quello dei vari ambienti .NET.

Il libro descrive anzitutto le celebri piastrelle (Tile) del mondo Win 8, ovviamente presenti pure in Win Phone 8, descrivendone tutte le tipologie e caratteristiche, come dimensioni, presenza o meno di immagini in primo piano o in background e altre peculiarità, fra le quali brillano quelle delle tile dedicate alle applicazioni multimediali, di cui la dotazione standard dello Store Win Phone 8 è già abbastanza ricco.

Seguono capitoli che illustrano i principali aspetti dello sviluppo di App per Win Phone 8. Va qui precisato che gli Autori hanno per lo più evitato trucchi particolari, eccezion fatta per quelli strettamente peculiari di Win Phone 8, una scelta giusta perché rimanda a testi base dedicati alla programmazione .NET che molti già possiedono.

Il libro mostra anche come gestire programmaticamente particolarità smartphone, come l’accelerometro, la fotocamera, il GPS e il  microfono.

In definitiva la parte centrale è quella dedicata all’XAML, basilare per la creazione di tile e quel che segue. Tali capitoli servono anzitutto a chi ne ignori del tutto la sintassi, mentre chi ne ha già familiarità potrà coglierne le novità relative allo smartphone, magari aggiornando in tal senso suoi precedenti template (meglio se già per Windows 8 che anch’esso si basa su tile)).

L’elenco seguente dà un’idea di tali aspetti, come i (cosiddetti) pivot, le viste geografiche, le applicazioni HTML 5, nonché le modalità di rilascio e commercializzazione.

·         - ApplicationBar and context menus

·        -  Panorama and pivot controls

·         - Building a media player

·        -  Using Maps

·         - Building HTML applications

·         - Releasing and monetizing apps

 

Sostengono gli Autori:

With 10 million (and climbing) active handsets, Windows Phone 8 has become a real alternative to Android and iOS. WP users are hungry for great apps, so it’s time for you to start creating them.

Windows Phone 8 in Action teaches you how to design, build and sell WP8 apps. In it, you’ll learn to use the WP Runtime and .NET APIs to control key features like the accelerometer, camera, GPS, and microphone. This example-driven book also shows you how to write applications that use location and push notification, enhanced navigation services, and WP8’s deep multimedia capabilities.

Contenuti

·         ●  Build your first phone app

·         ●  Master the Windows Phone 8 interface

·         ●  How to sell on the Windows Phone Store

·         ●  Use features like voice recognition and media

You’ll need a working knowledge of C#. No experience with Windows Phone or XAML is required.

posted @ mercoledì 5 marzo 2014 17:28 | Feedback (0) |

mercoledì 19 febbraio 2014

Big Data e NoSQL in Azure. Un’introduzione

 

Big Data e NoSQL in Azure. Un’introduzione

Confessiamolo. Chi, affrontando la prima volta il mondo di Microsoft Azure, non è rimasto sorpreso se non sgomento nel constatare che tale piattaforma supporta in modo limitato database Sql, privilegiando un misterioso sistema NoSQL? Il punto è che tale situazione fa a pugni con il paradigma relazionale con le sue regole di tabelle multiple correlate tra loro in modo razionale e, insiame, in grado di minimizzare le ridondanze. Di primo acchito il nuovo verbo NoSQL si presenta più rozzo.

Successivamente si è acceso il dibattito sui (cosiddetti) Big Data, le enormi basi di dati diffuse sul web. Da tali articoli (ai quali rimando) si apprende che, in realtà, il sistema relazionale, proprio per la sua pignoleria, mal si presta a gestire basi di dati  collocate su macchine virtuali e nodi “sparpagliati” worlwide” (e la ridondanza non conta più troppo, visto che oltretutto ragioni di sicurezza impongono repliche multiple).

Avendo digerito tali concetti, chi scrive è ritornato in Azure rivisitandone l’architettura NoSQL con maggior interesse. Questo post si rivolge a chi muove i primi passi in tale ambiente, nonché a coloro che, anche senza nutrire, al momento, velleità di sviluppo, desidera comunque farsene un’idea.

Il Table Service

Il Table Service, come il suo nome dichiara, è un servizio relativo a tabelle NoSQL, che la piattaforma cloud Microsoft Azure privilegia, in quanto più idoneo a fare da base per applicazioni on-line. Per la cronaca, come testé accennato, al Table Service si accompagna un sistema relazionale, SQL di portata ridotta rispetto all’omologo offline.

L’impianto di Table Service è concettualmente simile a quello dei sistemi BigTable e HBase per i quali rimando al seguente importante articolo chiarificatore:

http://jimbojw.com/wiki/index.php?title=Understanding_Hbase_and_BigTable

La questione concettuale spinosa è la famosa distinzione fra database row-oriented e database column-oriented. Gli esempi A e B seguenti li ho ripresi dalle prime pagine dell’articolo succitato. Ho osato interpretarli con dati tipici di una tabella anagrafica aggiungendo celle NUL, che l’articolo non prevedeva.

A) Tabella relazionale - database row-oriented

   

COD

Cognome

Prenome

Nome

DataNascita

Età

Salario

1

Rossi

Doria

Carlo

13/05/1980

33

1.200 €

2

Paoli

NUL

Mario

12/07/1975

38

1.500 €

3

Umberti

Zappa

Elena

NUL

NUL

500 €

4

Verdi

NUL

Luigi

05/02/2000

13

NUL

 

Invertendo righe e colonne si ottiene la situazione equivalente, conforme al nuovo paradigma NoSQL, intestazioni a parte (che non è chiaro dove vanno a finire).

B) Modello database column-oriented

1

2

3

4

Rossi

Paoli

Umberti

Verdi

Doria

NUL

Zappa

NUL

Carlo

Mario

Elena

Luigi

13/05/1980

12/07/1975

NUL

05/02/2000

33

38

NUL

13

1.200 €

1.500 €

500 €

NUL

 

L’articolo predetto esordisce ammettendo che i termini “base” e “table” possono creare l’idea errata che si tratti semplicemente di nuove edizioni di fonti relazionali (RDBMS). Il modello è, invece, radicalmente differente. E al centro dell’articolo viene ben specificato che la contrapposizione fra column-oriented e row-oriented è fuorviante, in quanto Hbase e BigTable sono caratterizzate da un insieme di mappe di dati a più livelli, ciascuna delle queli incorpora la mappa sottostante, in un sistema di scatole cinesi o, se si preferisce, di bambole matrioska.

Mi fermo qui. Passando al Table Service di Azure, la sintassi si presenta più semplice e, oltretutto, familiare a chi mastica linguaggio C# (mentre in Hbase e BigTable vige Java). Il modello di Azure si esprime in classi object-oriented. Qui accenno fugacemente che l’accesso si esplica tramite un’API REST basata su http, quindi salto a piè pari diversi passaggi, proponendo senz’altro un esempio tipico, che definisce l’equivalente di una (tradizionale) entità Prodotto nel servizio Azure Table, con le sue brave proprietà, da quelle diciamo così, globali, PartitionKey e RowKey, e quelle “normali”, Nome e Descrizione.

 [DataServiceKey("PartitionKey", "RowKey")]

public class Prodotto

{

public string Timestamp{ get; set; }

public string PartitionKey { get; set; }

public string RowKey { get; set; }

public string Nome { get; set; }

public string Descrizione { get; set; }

}

Quanto a Timestamp, mi limito a dire che, come ampiamente spiegato nel post su Hbase & BigTable,, è una variabile creata automaticamente in base al tempo di creazione/modifica del Prodotto . La qual cosa, come si intuisce, è particolarmente utile qualora si desideri monitorare l’evoluzione e il trend di certi dati per scopi di BI (Business Intelligence) o, come oggi si dice, di Data Analytics.

Ed ecco il codice per ottenere una nuova tabella Azure di oggetti Prodotto. Esso crea diverse istanze della classe Prodotto, incluse in un elenco (New List) di Prodotti:

var prodotti =

new List<Prodotto>

{

new Prodotto

{

PartitionKey = "Camicie",

RowKey= "1",

Name = "Camicia R",

Descrizione= "Camicia a righe"

},

new Prodotto

{

PartitionKey = "Camicie",

RowKey = "2",

Name = "Camicia B",

Descrizione = "Camicia bianca"

},

new Prodotto

{

PartitionKey = "Camicie",

RowKey = "3",

Name = "Hawai",

Descrizione = "Camicia a fiori hawaiana"

}

};

NOTA. Il precedente esempio, per semplicità e... pigrizia, è privo della proprietà Timestamp.

Con un non arduo sforzo di fantasia possiamo aggiungere un’ulteriore PartitionKey, diciamo Giacche composta da oggetti Prodotto di Nome a nostro piacimento.  Ne ometto i dettagli operativi, facilmente intuibili, penso. A questo punto per interpretare il risultato graficamente ho optato per una “brutale” rappresentazione tabellare,  che peraltro trae spunto e ispirazione nei manuali su Azure da me consultati (v. in fondo a questo articolino):

 

PartitionKey

RowKey

Nome

Descrizione

Camicie

1

Camicia R

Camicia a righe

Camicie

2

Camicia B

Camicia bianca

Camicie

3

Hawai

Camicia hawaiana

Camicie

4

Comune

Giacca normale

Camicie

5

Camicia R

Camicia a righe

Camicie

6

Camicia R

Camicia a righe

Giacche

7

Normale

Giacca tutti giorni

Giacche

8

Sportiva

Giacca sportiva

Giacche

9

Sera

Smoking

Giacche

10

Sera

Smoking

Giacche

11

Sportiva

Giacca sportiva

 

Qualcuno fra quanti avranno letto il post specifico citato sopra osserverà che così si perde la rappresentazione di mappe-di-mappe dei sistemi  BigTable e HTable. Oppure anche il NoSQL targato Azure la sottintende? Mi astengo dal pronunciarmi su queste sottigliezze. Di fatto, da tale banale schema a mio avviso si possono trarre due considerazioni:

·         per un verso, non è esclusa la possibilità di ricondurre, con qualche pena, una tabella NoSQL ad una di tipo SQL classico gestibile con tradizionali query in linguaggio SQL (per esempio IBM offre strumenti ad hoc);

·         per contro, il piccolo esempio mette in evidenza le molte ridondanze che affliggono il modello NoSQL; nel nostro esempio fa eccezione soltanto la RowKey, indispensabile per individuare in modo univoco una tabella.

Va Infine sottolineato che il ruolo della PartitionKey è strettamente legato alla possibilità di distribuire una Table del genere su una varietà di macchine sparse sul web, nella fattispecie le Virtual Machine (VM) di Azure.

Concludendo

Questo post ha carattere introduttivo, pertanto non aggiungo altro, suggerendo piuttosto a tutti gl’interessati i due manuali qui sotto riportati, necessari per comprendere più da presso e approfondire le peculiarità dell’architettura del servizio NoSQL di Azure,.

Windows Azure
Programmare peril Cloud Computing
di Fabio Cozzolino
Ed.
FAG

Azure in action
di  Chris Hay, Brian H. Prince
Ed. Manning Publications Co.
www.manning.com

posted @ mercoledì 19 febbraio 2014 16:02 | Feedback (0) |

Powered by:
Powered By Subtext Powered By ASP.NET