TheTruster's profileTheTruster's BoxPhotosBlogListsMore Tools Help

TheTruster's Box

TheTruster

Location
Interests

My Page Rank

September 25

Sezione Modelli 3D - Aggiunti 4 Modelli


Aggiunti 4 nuovi modelli nella sezione 3D Models.

Di seguito le anteprime con i collegamenti per scaricarli.



Potete scaricarli liberamente accedendo all'area Download -> Modelli 3D.


November 19

Automazione 2 - Importare dati da Excel ad Access con ADO


Nel post precedente ho affrontato il problema dell'importazione di dati da un foglio di lavoro di Excel ad un Documento Word.
La stessa cosa avrebbe potuto essere realizzata utilizzando come destinazione, in luogo del documento Word, un altro Foglio di Lavoro Excel, un Database Access oppure un programma in Visual Basic 6.
Il sistema dell'automazione, però, ci pone nella situazione di dover avere installato sulla macchina l'applicazione del pacchetto Office che intendiamo utilizzare. Nel caso di Excel ad esempio, non potremmo fare ricorso all'apposita DLL se Excel stesso non fosse installato nel sistema.

Una soluzione alternativa, anche se non si tratta propriamente di automazione, è quella di trattare Excel come fosse un Database, nel qual caso avremmo bisogno solo del modello ad oggetti ADO e, se il sistema operativo è Windows XP, questo è installato insieme al sistema, per cui sicuramente disponibile.

Per il nostro esempio di importazione prenderemo a base un Database Access anche perfettamente vuoto senza alcuna tabella, visto che ci occuperemo di crearla dinamicamente al momento dell'importazione.
Avremo anche bisogno di un file di Excel per la nostra prova per cui, se non ne abbiamo uno già disponibile con dei dati intabellati in maniera coerente, creiamone uno, con questa impostazione:


A
B
C
1
CognomeNomeTelefono
2
RossiMario01/123456
3
VerdiLuigi02/789012
4
GialliCarlo03/345678

Come si può notare, la cosa importante è utilizzare la prima riga come intestazione dei campi.

Passiamo alla creazione del nostro Database - o creiamone uno nuovo - e aggiungiamo un nuovo Modulo, premendo Moduli nella finestra del Database e Nuovo sulla barra superiore della stessa finestra.
Si aprirà la finestra dell'Editor di Visual Basic e la prima cosa che faremo, è referenziare la libreria che ci interessa, relativa ad ADO.
Lo possiamo fare da Strumenti -> Riferimenti.... Molto probabilmente, dipendentemente dalla versione di Access, si troverà già referenziata la

Microsoft ActiveX Data Object 2.1 Library

nel qual caso possiamo de-referenziarla andandone a scegliere una analoga, ma più aggiornata, ovvero la

Microsoft ActiveX Data Object 2.8 Library

oppure la più recente installata nel sistema, se questa non è presente.

Il modello ad oggetti ADO, benchè disponga di un numero relativamente basso di oggetti, risulta piuttosto articolato e sarebbe impossibile discuterne le potenzialità e le caratteristiche in poche righe.
In generale, comunque, anche ADO come gli oggetti della libreria di Excel si possono riassumere in una certa gerarchia, a capo della quale troviamo l'oggetto Connection.
Questo è l'oggetto principale poichè permette di "aprire una porta" sul Database permettendoci l'accesso ai dati.
Attraverso la Connection possiamo eseguire delle operazioni direttamente sul DB inviandogli delle frasi SQL, per cui ci consente di inserire dati e riceverne indietro, creare tabelle o cancellarne o ancora modificarne la struttura.
Se creare una tabella piuttosto che inserire dati o modificarli non presuppone per forza un ritorno degli stessi verso la nostra applicazione, la semplice lettura dei dati contenuti in una tabella per la loro visualizzazione, ci pone nella situazione di doverli "immagazzinare" da qualche parte. Ci serve un contenitore, insomma. Questo contenitore si chiama Recordset.
Come suggerisce la parola questo oggetto è un set di record ovvero una porzione dei dati contenuti genericamente nel nostro DB anche se distribuiti su più tabelle, selezionati secondo dei criteri.
Per selezionare dei record si usa una Query ovvero una frase SQL che il motore del DB interpreta per restituirci dei dati coerenti con le nostre condizioni.

Mi scuso per questa digressione, forse per qualcuno superflua, ma probabilmente necessaria per permettere a chi si avvicina per la prima volta a questa tecnologia di capire almeno il senso delle righe di codice che andrò a riportare in seguito.
In ogni caso consiglio, per una concezione più dettagliata dell'argomento, di dare una scorsa a questo sito (in inglese) dove, oltre alle spiegazioni riferite ai vari oggetti di ADO, sono presenti anche alcuni esempi di utilizzo.

Ritornando alla nostra importazione, prepariamo l'oggetto principale di cui ci serviremo per aprire il DB Excel ovvero una Connection.

Codice:
Dim oConn As ADODB.Connection

Di solito, personalmente, preferisco avere un unico oggetto Connection, attraverso il quale aprire più Recordset. L'oggetto Connection viene dichiarato pubblico, aperto all'inizio dell'applicazione e chiuso alla chiusura della stessa oppure quando non risulta più necessario, mentre gli oggetti Recordset vengono aperti e chiusi, spesso nella stessa routine, quando esauriscono la loro utilità, sia essa di inserimento/modifica che di lettura.

In questo caso, possiamo accontentarci un solo oggetto Recordset che dichiareremo all'interno della routine di importazione.
A proposito, cominciamo a costruirla. Dal punto di vista logico la nostra routine dovrà:

  1. Aprire la Connessione verso Excel
  2. Aprire il Recordset
  3. Immagazzinare i dati da importare nel Recordset
  4. Creare la Tabella che conterrà i dati nel nostro DB
  5. Copiare i dati sulla Tabella
  6. Chiudere il Recordset
  7. Chiudere la Connessione con Excel


Per aprire la Connessione verso una qualsiasi DB è necessaria la cosiddetta ConnectionString ovvero stringa di connessione. Essa ci permette di scegliere la modalità di apertura del DB, specificando il motore e il file del DB stesso, e passando al Database Username e Password nel caso in cui fossero necessari per accedervi.
Per Excel è necessario una particolare ConnectionString e un'ottima fonte per reperirne una adeguata alle nostre necessità è il sito ConnectionStrings.com.
Guardando tra quelle disponibili per aprire Excel come DB troveremo anche questa

Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\MyExcel.xls;Extended Properties="Excel 8.0;HDR=Yes;IMEX=1";

Per semplicità possiamo anche realizzare una Routine riutilizzabile, appositamente creata per aprire la connessione, liberandoci dalla necessità dover inserire il codice necessario tutte le volte in diverse routines:

Codice:
Sub ApriConnessione(NomeDB As String)

On Error GoTo Err_Handle

If oConn Is Nothing Then
Set oConn = New ADODB.Connection
ElseIf oConn.State = adStateOpen Then
oConn.Close
End If

oConn.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & NomeDB & ";Extended Properties=""Excel 8.0;HDR=Yes;IMEX=1"""

Exit Sub

Err_Handle:
MsgBox Err.Number & ", " & Err.Description

End Sub

Come si può notare la connessione verso il nostro file Excel si ridurrà ad una semplice riga del tipo:

Codice:
ApriConnessione "C:\Cartella\FileExcel.xls"

Analogamente possiamo creare una seconda routine per chiudere la connessione eventualmente aperta:

Codice:
Sub ChiudiConnessione()

If Not oConn Is Nothing Then
If oConn.State = adStateOpen Then
oConn.Close
End If
End If
Set oConn = Nothing

End Sub

Abbiamo praticamente realizzato i punti 1 e 7 della lista precedente, quindi passiamo, finalmente, alla creazione della routine di importazione. Nella sua costruzione utilizzeremo, oltre alla Connessione verso Excel e al Recordset, la connessione che Access, ovvero la sua Applicazione, apre verso le sue tabelle. Questa Connessione è accessibile attraverso l'oggetto CurrentProject.
Sfrutteremo questa Connessione per inviare al DB delle frasi SQL idonee a creare la tabella che ospiterà i dati e le frasi di UPDATE (sempre SQL) che ci permetteranno di inserirvi i dati letti da Excel.

Codice:
Sub ImportaDatiExcel()

' Dichiaro il Recordset e la variabile per la Query
Dim oRSet As ADODB.Recordset
Dim SQL As String

' Apro la Connessione
ApriConnessione "E:\VisualBasic\My Tutorials\Interventi Blog\Automazione Office\ExcelProva.xls"

' Assegno un nuovo Recordset
Set oRSet = New ADODB.Recordset

' Creo la query per reperire i dati sul foglio Excel
SQL = "SELECT Cognome, Nome, Telefono FROM [Foglio1$]"
oRSet.Open SQL, oConn

' Attraverso un'apposita routine, elimino la
' tabella di destinazione, se esistente.
EliminaTabella ("DatiExcel")

' Creo la Tabella di destinazione dei dati, definendone i campi con nome, tipo e lunghezza
SQL = "CREATE TABLE DatiExcel (Cognome TEXT(50), Nome TEXT(50), Telefono TEXT(20))"
CurrentProject.Connection.Execute SQL

' Mediante un ciclo, scorro tutto il Recordset
' compiendo una serie di INSERT sulla tabella
' di destinazione appena creata
With oRSet
While Not .EOF
SQL = "INSERT INTO DatiExcel " & _
"(Cognome, Nome, Telefono) VALUES (" & _
"'" & .Fields("Cognome").Value & "', " & _
"'" & .Fields("Nome").Value & "', " & _
"'" & .Fields("Telefono").Value & "')"

CurrentProject.Connection.Execute SQL
oRSet.MoveNext
Wend
End With

' Chiudo il Recordset e annullo l'oggetto
oRSet.Close
Set oRSet = Nothing

' Chiudo la connessione
ChiudiConnessione

End Sub

Nel codice appena riportato ho richiamato anche la Routine EliminaTabella, appositamente creata per eliminare la tabella, nel caso fosse presente, prima di crearne una nuova. E' ovvio che, se non si cancellasse la tabella preesistente si avrebbe un errore. Utilizzando una routine separata, l'errore viene trascurato, se rilasciato dall'applicazione, evitandoci di inserire ulteriori righe di codice alla routine di importazione mantenendola, tutto sommato, compatta.
Ecco la dichiarazione della routine:

Codice:
Sub EliminaTabella(NomeTabella As String)

On Error Resume Next
CurrentProject.Connection.Execute "DROP TABLE " & NomeTabella

End Sub

Abbiamo praticamente terminato e l'unica cosa che rimane da fare è provare il tutto. Se tutto andrà a buon fine, riusciremo ad importare i dati contenuti in un File Excel aperto come fosse un DB in un Database Access, dentro una tabella creata dinamicamente. La creazione della tabella, in questo caso, è stata fatta in maniera un po' "rigida" nel senso che abbiamo già impostato da codice i campi con loro tipo e lunghezza, ma volendo rendere il tutto ancora più flessibile, prima della creazione della tabella, potremmo scorrere con un ciclo i campi del Recordset importato per valutarne numero, nome e tipo di dati inseriti, creando una tabella esattamente corrispondente ai dati da importare.

Nel caso vogliate segnalarmi inesattezze o richiedere ulteriori chiarimenti contattatemi pure senza remore Sorriso



November 14

Automazione 1 - Importare dati da Excel a Word


Le applicazioni del pacchetto Office, possiedono in compendio alle loro già numerose funzioni, anche un ambiente di sviluppo integrato: il Visual Basic for Application o VBA.
Ognuna delle applicazioni ovvero Excel, Access, Word, etc., ha delle peculiarità, principalmente dovute alla diversità degli oggetti che in esse vengono trattati. Per fare un esempio, in Word esistono gli oggetti Document, mentre in Excel gli oggetti Worksheet, e così via.
La base del linguaggio, quindi, è una sola ed è Visual Basic (seppure in una versione "ridotta" e adattata), ma a seconda dell'applicazione che si sta utilizzando è possibile avere a disposizione degli oggetti ognuno con sue proprietà e metodi utili per automatizzare molte delle operazioni che normalmente andrebbero compiute manualmente.

Descrivere seppur brevemente tutte le possibilità offerte dall'automazione sarebbe impossibile per cui mi limiterò a descrivere come è possibile accedere ad un file di Excel dall'esterno, ovvero da un programma Visual Basic 6, o un altro file di Excel o ancora da un documento Word o un Database Access.
La metodologia non cambia e data la base comune del linguaggio le operazioni da compiere sono esattamente le stesse.

La prima cosa da considerare è che Excel stesso è un oggetto, come pure oggetti sono i suoi vari elementi gerarchicamente dipendenti come Workbook, WorkSheet, Range, Cell, etc.
Tutti questi oggetti fanno parte di una cosiddetta Libreria che, se si usa Excel, è già referenziata e fruibile senza fare null'altro, ma che va referenziata nel caso in cui si abbia intenzione di accedere ad un'applicazione Excel da un qualsiasi altro programma VB6 o applicazione Office.

Per referenziare la libreria è sufficiente andare nella finestra dell'Editor di Visual Basic attraverso Strumenti -> Macro -> Visual basic Editor - oppure premendo CTRL+F11 - scegliere da questa nuova finestra, dal menu Strumenti, la voce Riferimenti... e cercare la libreria che si chiama Microsoft Excel xx.x Object Library spuntando l'apposita casellina.


Effettuata questa semplice operazione avremo a disposizione tutti gli oggetti che Excel espone normalmente all'interno del proprio VBA.

Come accennato in precedenza la struttura degli oggetti di Excel è di tipo gerarchico, nel senso che esiste un oggetto principale ovvero Application, dal quale interdipendono via via altri oggetti.
Im maniera molto, molto semplificata, potrebbe riassumersi in questo modo:


Tenendo a mente questa struttura è facile immaginare come può essere possibile gestire più Workbook facenti parte della stessa applicazione o più Worksheet sempre dipendenti dallo stesso Workbook.
Alla base ci sono gli oggetti Range che rappresentano una cella o un loro insieme, e gli oggetti Cells attraverso i quali è possibile identificare una singola cella attraverso riga e colonna.

Ognuno di questi oggetti possiede numerosi metodi e proprietà, che sarebbe eccessivamente dispersivo trattare in questo piccolo spazio, ma che è possibile esplorare e provare con un minimo di intraprendenza e l'aiuto della Guida in Linea.

Fatta questa doverosa presentazione, entriamo nel vivo dell'argomento, ovvero l'accesso ad un file di Excel da VBA.
Proveremo, da un Documento Word, ad accedere ad un Workbook Excel salvato sul nostro HardDisk per prelevare il valore da alcune celle, importandole contestualmente sul nostro documento.

La prima cosa da fare in Word, dopo aver referenziato la libreria come descritto sopra, è aggiungere un nuovo Modulo VBA.
Si fa semplicemente scegliendo la voce Modulo dal menu Inserisci.

Nel nuovo Modulo cominciamo con l'istanziare gli oggetti che ci serviranno per accedere al nostro file di Excel, iniziando dall'Applicazione e proseguendo con il WorkBook e il Worksheet.

Codice:
Dim xlApp As Excel.Application
Dim xlBook As Excel.Workbook
Dim xlSheet As Excel.Worksheet

Quindi creiamo una nuova sub che chiameremo Importa.
All'interno di questa dovremo occuparci di "dare vita" alla nuova istanza dell'applicazione Excel che intenderemo utilizzare, assegnando di seguito i vari oggetti, "puntandoli" sul Workbook che intendiamo aprire e sul Worksheet sul quale risiedono i dati che vogliamo importare.
C'è da precisare che accedendo ad Excel in questo modo, esso rimarrà invisibile (a meno che non si renda visibile esplicitamente), permettendoci così di effettuare le operazioni che ci servono in maniera trasparente per l'utente.

Codice:
Sub Importa()

'Dichiaro le variabili locali
Dim ValoreCella As String
Dim i As Integer


'Creo la nuova applicazione
Set xlApp = New Excel.Application

'Attraverso la nuova applicazione apro il Workbook
'assegnandolo alla variabile oggetto xlBook
Set xlBook = xlApp.Workbooks.Open("C:\ImportaExcel.xls")

'Decido quale foglio utilizzare
Set xlSheet = xlBook.Worksheets("Foglio1")

'Instauro un ciclo per prendere, una alla volta, il
'valore dalle prime 100 celle della prima colonna,
'passandole sul documento Word
For i = 1 To 100
ValoreCella = xlSheet.Cells(i, 1).Value
Application.Selection.TypeText ValoreCella & vbCrLf
Next i

'Chiudo il Workbook e l'Applicazione
xlBook.Close
xlApp.Quit

'Annullo le variabili per liberare le risorse
Set xlSheet = Nothing
Set xlBook = Nothing
Set xlApp = Nothing

End Sub

Possiamo già provare il funzionamento del nostro codice chiudendo la finestra di Visual Basic e, da Word, andando sul menu

Strumenti -> Macro -> Macro...

per visualizzare una finestra contenente la lista di tutte le macro contenute nel Documento Word, tra le quali dovremmo trovare anche la nostra, denominata Importa. Selezioniamola e premiamo Esegui per avviarne l'esecuzione.

Se tutto è andato bene sulla pagina del Documento Word, a partire dal cursore, dovrebbe visualizzarsi la lista di valori contenuta nella prima colonna del nostro file di Excel.

Spero che questo semplice esempio sia stato abbastanza chiaro e utile a comprendere almeno i meccanismi che stanno alla base dell'automazione.
In caso contrario non esitate a chiedermi ulteriori chiarimenti.

Nel prossimo intervento proverò a spiegare come trattare Excel come un DB, accedendovi attraverso il modello ad oggetti ADO e prelevando dei valori da un Foglio di Lavoro, importandoli su una Tabella di un Database Access.

November 03

Inserire delle ProgressBar in una ListView con Visual Basic 6


Spesso capita che un programmatore si trovi a prendere spunto da altre applicazioni per realizzare ciò che gli passa per la testa. A volte è un'esigenza reale, a volte è solo la curiosità di confrontarsi con se stessi, cimentandosi in qualcosa che, probabilmente, sul momento non è utile. Ovviamente non ritengo queste attività "tempo perso", prima di tutto perchè tutto ciò che ci spinge a ragionare vale come bagaglio culturale e secondo perchè c'è la possibilità che, un giorno, si debba realizzare quella determinata cosa, e la si abbia già pronta! Animoticon

La mia "ispirazione" in questo caso, è stata la ListView di e-Mule. Come molti sapranno, in una delle sue colonne è presente una barra di avanzamento che sta ad indicare lo stato di avanzamento del download di un file. Incuriosito, ho deciso di provare a realizzare una cosa del genere con una ListView standard di Visual Basic 6.
La barra di avanzamento che ho realizzato non è della stessa complessità di quella di e-Mule, nel senso che non tiene conto di un avanzamento frammentato, ma mi ritengo soddisfatto del risultato e della sua funzionalità.

Il principio che sta alla base è quello di realizzare la barra di avanzamento in un'immagine bitmap, assegnandola successivamente al SubItem del controllo ListView.

Per prima cosa, cominciamo la realizzazione di questo progetto aggiungendo i Microsoft Windows Common Controls 6.0 ai componenti. Possiamo farlo andando su Progetto -> Componenti e selezionandoli dalla lista dei Controlli.

Poi prepariamo il Form che farà da base come in figura, rispettando la nomenclatura dei controlli:


Come accennavo prima, abbiamo bisogno che la nostra barra sia formata da una serie di bitmap, ognua delle quali rappresenterà uno stadio di avanzamento, ma nella ListView facente parte dei Common Controls non è possibile inserire direttamente un'immagine. Essa per essere assegnata ad un Item o SubItem deve risiedere su una ImageList.
La prima cosa da fare, quindi, è popolare l'ImageList con le immagini della barra di avanzamento.

Ovviamente le immagini della ProgressBar dovranno essere proporzionate alla colonna nella quale dovranno essere inserite, per cui definiamo una variabile pubblica nel form che ci consente di variare con poco sforzo e in qualunque momento, la sua collocazione.

Codice:
Dim pBarCol As Integer

Adesso possiamo creare la routine che disegna materialmente una ProgressBar. Per farlo, abbiamo bisogno del riferimento alla ListView per calcolare altezza e larghezza della barra, del valore di colonna nella quale dovrà essere inserita e, infine, del suo valore.
La routine MakeProgressBar, per spiegarla in breve, si occupa di:

  • definire i colori che determineranno l'aspetto della barra;
  • creare un controllo PictureBox temporaneo nel quale disegnarla;
  • proporzionare il valore passato come argomento alla larghezza del SubItem scelto;
  • disegnare la barra utilizzando gli strumenti grafici di VB6;
  • inserire la barra così creata nell'ImageList;
  • eliminare il controllo PictureBox temporaneo.

Questa è la routine completa.

Codice:
Sub MakeProgressBar(lw As ListView, colHead As Integer, Value As Integer)

Dim v As Long
Dim BarBorder As Long
Dim BarBack As Long
Dim BarNormal As Long
Dim BarComplete As Long
Dim tmpPic As PictureBox

BarBorder = RGB(0, 0, 0)
BarBack = RGB(255, 255, 200)
BarNormal = RGB(200, 0, 0)
BarComplete = RGB(0, 200, 0)

Set tmpPic = Me.Controls.Add("VB.PictureBox", "tP")

With tmpPic
.Visible = False
.AutoRedraw = True
.ScaleMode = vbPixels
.BorderStyle = 0
Set .Font = lw.Font
.Width = lw.ColumnHeaders(colHead).Width
.Height = Int(lw.ListItems(1).Height)
.Cls
.DrawMode = 13
tmpPic.Line (0, 0)-(.ScaleWidth - 1, .ScaleHeight - 1), BarBack, BF

v = (Value * (.ScaleWidth - 5)) / 100
.CurrentX = (.ScaleWidth - .TextWidth(Value & "%")) / 2
.CurrentY = (.ScaleHeight - .TextHeight(Value & "%")) / 2
.ForeColor = vbBlack
tmpPic.Print Value & "%"

If v > 0 Then
.DrawMode = IIf(Value = 100, 9, 14)
tmpPic.Line (2, 2)-(2 + v, .ScaleHeight - 3), IIf(Value = 100, BarComplete, BarNormal), BF
End If

.DrawMode = 13
tmpPic.Line (0, 0)-(.ScaleWidth - 1, .ScaleHeight - 1), BarBorder, B
ImageList1.ListImages.Add , "v" & Value, tmpPic.Image
End With

Me.Controls.Remove ("tP")
Set tmpPic = Nothing

End Sub

Prima ho detto che di ProgressBar ne andranno create una per ogni valore, quindi ci serve una routine che si occupi di generarle, passando alla MakeProgressBar i valori sequenzialmente corretti, da 1 a 100.

Codice:
Sub CreateProgressBars()
Dim k As Integer
ListView1.SmallIcons = Nothing
ImageList1.ListImages.Clear
For k = 0 To 100
MakeProgressBar ListView1, pBarCol, k
Next
ListView1.SmallIcons = ImageList1
End Sub

Come si può notare, la routine si occupa di eliminare l'assegnazione della ImageList dalla ListView poichè altrimenti non sarebbe possibile popolarla o cancellarla, lancia la generazione delle barre attraverso il ciclo da 1 a 100 e riassegna la ImageList alla ListView.
Vedremo in seguito che questa routine tornerà utile anche quando sarà necessario aggiornare la visualizzazione del controllo al variare della dimensione della colonna che ospita la ProgressBar.

Arrivati a questo punto abbiamo popolato la nostra ImageList con le immagini rappresentanti tutti i valori di avanzamento necessari. Non rimane che assegnare quella opportuna, in base al valore da rappresentare, al SubItem di nostro interesse.
Per rendere le cose più semplici ho predisposto 2 routines: SetValue e GetValue.

La SetValue controlla, innanzitutto, se il valore passato rispetta il range di 1 a 100 consentito, quindi assegna al SubItem scelto l'immagine corrispondente al valore da rappresentare, tramite la proprietà ReportIcon.
In questa routine, per tenere traccia del valore attualmente rappresentato, viene conservato, nella proprietà Tag del SubItem, anche il valore numerico passato come argomento.

Codice:
Sub SetValue(itm As ListItem, Value As Integer)
If Value < 0 Then Value = 0: Exit Sub
If Value > 100 Then Value = 100: Exit Sub
itm.ListSubItems(pBarCol - 1).ReportIcon = "v" & Value
itm.ListSubItems(pBarCol - 1).Tag = Value
End Sub

Per ottenere il valore attualmente impostato in un determinato SubItem basta usare la Function GetValue, passando l'Item di riferimento come argomento:

Codice:
Function GetValue(itm As ListItem) As Integer
GetValue = itm.ListSubItems(pBarCol - 1).Tag
End Function

Procediamo, adesso, al monitoraggio della larghezza delle colonne della ListView, poichè in corrispondenza di una variazione della loro larghezza sarà necessario adeguare la dimensione delle ProgressBar.
Purtroppo la ListView non fornisce nessun feedback riguardante l'evento di espansione o riduzione delle colonne, per cui possiamo ricorrere ad un semplice Timer, il quale si occuperebbe di controllare ciclicamente che le dimensioni della colonna contenente le ProgressBar non sia variata. Non è un'operazione molto impegnativa, poichè il "lavoro" di ridimensionamento verrebbe eseguito solo nel caso in cui ci sia una variazione di dimensioni.
Questa è la routine di evento del Timer nominato tResize:

Codice:
Private Sub tResize_Timer()
Static WidthMonitor As Double

If WidthMonitor <> ListView1.ColumnHeaders(pBarCol).Width Then
CreateProgressBars
WidthMonitor = ListView1.ColumnHeaders(pBarCol).Width
End If
DoEvents
End Sub

Come dicevo, attraverso una variabile Static (WidthMonitor) si controlla che la dimensione rilevata non sia differente rispetto al controllo precedente, nel qual caso le ProgressBar verrebbero rigenerate (sulla base della nuova dimensione) attraverso l'invocazione della CreateProgressBar.

Abbiamo adesso tutti gli strumenti per poter utilizzare le ProgressBar nel nostro controllo ListView e quello che dobbiamo fare, per provare il tutto è disporre di un certo numero di Item nella nostra ListView ai quali assegnare le barre.
Sfruttiamo l'evento Load del form, per formattare correttamente la ListView, popolandola con degli Item e dei dati fittizi.

Codice:
Private Sub Form_Load()

Dim itmX As ListItem
Dim k As Integer

Randomize

Me.ScaleMode = vbPixels

pBarCol = 4

With ListView1
.View = lvwReport
.FullRowSelect = True
.ColumnHeaders.Add , , "Dummy", 0
.ColumnHeaders.Add , , "Nome del File"
.ColumnHeaders.Add , , "Dimensione"
.ColumnHeaders.Add , , "Avanzamento"
For k = 1 To 20
Set itmX = .ListItems.Add(, , "")
itmX.SubItems(1) = "File n. " & k
itmX.SubItems(2) = CStr(k + 100) & "Kb"
itmX.SubItems(3) = ""
itmX.ListSubItems(pBarCol - 1).Tag = 0
Next
End With

CreateProgressBars

For k = 1 To ListView1.ListItems.Count
SetValue ListView1.ListItems(k), 0
Next k

End Sub

Si noterà nel codice, che alla ListView viene aggiunta una colonna iniziale che ho definito "Dummy" e che ha larghezza 0. Questo è, più che altro, un escamotage di natura estetica: quando si assegna un'immagine alla proprietà ReportIcon di un SubItem, lo stesso spazio che essa occupa, ma vuoto, viene aggiunto anche accanto all'Item nella prima colonna. Esteticamente è poco gradevole per cui ho preferito nascondere la prima colonna, lasciandola comunque non popolata.

Se lanciamo adesso il progetto possiamo già notare che la nostra ListView è popolata e con le barre di avanzamento (tutte settate a valore 0) inserite nella terza colonna. Possiamo anche variare la dimensione di quest'ultima, notando che le ProgressBar si ridimensioneranno di conseguenza.
Allo stato attuale il progetto è già funzionante e potrebbe essere usato per rappresentare i valori di avanzamento dei vari "files", poichè basterebbe impostare attraverso la SetValue il corretto valore all'Item interessato, per notare l'avanzamento della barra al valore settato.

Per una prova generale, però, abbiamo inserito sul form (vedi immagine iniziale) un secondo Timer e un CommandButton denominati rispettivamente tProgress e Command1.

Il Command1 serve semplicemente per avviare/stoppare il Timer, mentre all'interno della routine di evento tProgress_Timer() vengono generati dei valori casuali "decidendo" arbitrariamente l'uno o l'altro Item, incrementando il valore della ProgressBar di un tot, anch'esso casuale.

Ecco entrambe le routine di evento:

Codice:
Private Sub Command1_Click()
tProgress.Enabled = Not tProgress.Enabled

If tProgress.Enabled Then
Command1.Caption = "Stop"
Else
Command1.Caption = "Start"
End If

End Sub

Codice:
Private Sub tProgress_Timer()

Dim r As Integer

r = 1 + Int(Rnd(1) * ListView1.ListItems.Count)

With ListView1
SetValue .ListItems(r), GetValue(.ListItems(r)) + (1 + Int(Rnd) * 10)
End With

End Sub

Questo è il risultato finale con le barre in avanzamento:


I colori delle barre possono essere variati a piacere, basta intervenire sulle variabili BarBorder, BarBack, BarNormal e BarComplete dichiarate nella routine MakeProgressBar.

Come al solito, potete scaricare il progetto completo dalla sezione Download -> Software, oppure cliccando qui: DOWNLOAD

Non esitate a contattarmi per farmi presenti inesattezze o chiedere ulteriori chiarimenti. Sorriso

October 14

Array di Controlli in VBA di Excel


Il VBA (Visual Basic for Applications) relativo alle varie applicazioni Office quali Excel, Word, Access, etc. è uno strumento decisamente utile per coloro i quali intendono "espandere" le già ampie possibilità offerte da questi software, implementando da soli caratteristiche non previste dall'applicazione originale o creando, ad esempio, una maschera per il data-entry che faccia da front-end per il proprio documento.

Proprio riguardo l'aspetto del data-entry, chi è abituato a lavorare in Visual Basic 6, utilizzando il VBA potrebbe trovarsi ad operare in un ambiente un po' più stretto considerato che in quest'ultimo mancano strumenti importanti che renderebbero agevole la gestione di molti controlli dello stesso genere. Uno di questi è l'array di controlli.

Un array è definito come una serie di variabili identificate da uno stesso nome al quale è possibile riferirsi utilizzando un numero ovvero un indice.
Analogamente, un array di controlli, è una serie di controlli dello stesso genere che condividono il nome. Anche in questo caso ogni controllo è raggiungibile attraverso un indice numerico.
La cosa che rende gli Array di Controlli decisamente utili è l'esposizione degli eventi in maniera comune a tutti i suoi elementi e le Routines di evento sono in grado di restituire un indice che identifica il controllo che ha scatenato l'evento stesso.

In Visual Basic 6 è molto semplice creare e gestire un Control Array, poichè questi vengono gestiti direttamente dall'ambiente di sviluppo. Non si può dire alltrettanto per il VBA dove, se vogliamo un array di controlli, dobbiamo crearcelo e gestircelo da soli.

In VBA, quando si dispongono i controlli sul Form, essi vengono a far parte della collection Controls che è possibile ciclare per effettuare delle operazioni su più controlli, anche di genere diverso, a prescindere dal loro nome.

Questo è un breve esempio per dimostrare come è possibile azzerare il contenuto di tutti i TextBox presenti su un Form utilizzando la collection Controls:

Codice:
Sub AzzeraTxt(frm As UserForm)
Dim ctl As Control
For Each ctl In frm.Controls
If TypeOf ctl Is MSForms.TextBox Then
ctl.Text = ""
End If
Next ctl
End Sub

Se si inserisce la suddetta routine in un Modulo, sarà possibile richiamarla da qualsiasi UserForm semplicemente scrivendo:

Codice:
AzzeraTxt Me

Purtroppo, se è vero che tramite la collection Controls abbiamo accesso ad una serie di controlli pur se non indicizzati e con nomi differenti è altrettanto vero che non possiamo che ricevere singolarmente gli eventi relativi a ciascuno dei controlli presenti sul Form. Questo ci costringe a dover scrivere in maniera ripetitiva del codice per inserirlo negli eventi relativi a tutti i controlli che ci interessa gestire.

Una maniera interessante di far fronte al problema è l'utilizzo delle Classi.
Utilizzando una Classe si possono gestire attraverso un solo oggetto molti aspetti relativi a più oggetti facenti parte di uno stesso insieme. All'interno di una Classe si possono definire Proprietà, Metodi ma soprattutto Eventi, il che è proprio ciò che ci interessa, considerato che potremo rilasciare degli eventi comuni a tutti i controlli gestiti dalla Classe stessa.

Per fare in modo di avere la maggiore flessibilità possibile ho pensato di realizzare 2 Classi.
La prima rappresenta l'oggetto vero e proprio, ovvero l'Item della Array di controlli che andremo a gestire, mentre la seconda si occuperà di fare da "tramite" tra la gestione dei singoli Item disposti in una Collection e la nostra applicazione.

La prima delle due Classi è molto semplice e non fa altro che definire gli oggetti relativi alle varie tipologie di controlli che si desiderano gestire (TextBox, CheckBox, Images, etc...) ed il rilascio degli eventi peculiari per ogni controllo.
In considerazione del fatto che la classe può essere istanziata più volte per ottenere più Array di controlli, si è manifestato il problema di identificare la Classe "madre" alla quale restituire la chiamata dell'evento. Per ovviare a ciò è bastato utilizzare un ulteriore oggetto "CallerObject" che in pratica permette ad ogni Item di "sapere" a quale Classe appartiene.

Ecco il codice della classe cMatrixItem:

Codice:
Public WithEvents itmTextBox As MSForms.TextBox
Public WithEvents itmImage As MSForms.Image
Public WithEvents itmCheckBox As MSForms.CheckBox
Public CallerObject As cCtlMatrix

Private Sub itmCheckBox_Click()
CallerObject.ItemClick itmCheckBox
End Sub

Private Sub itmImage_Click()
CallerObject.ItemClick itmImage
End Sub

Private Sub itmTextBox_Change()
CallerObject.ItemChange itmTextBox
End Sub

Come si può notare gli eventi definiti sono davvero pochi come pure i tipi di oggetti gestiti ma, per analogia, è immediata l'aggiunta sia di ulteriori tipi di controlli che di ulteriori eventi ad essi relativi.

Andiamo adesso a progettare la Classe che ospiterà la Collection vera e propria.

Definiamo prima di tutto gli oggetti che la classe dovrà gestire: gli Item e la Collection.

Codice:
Dim itm As cMatrixItem
Dim cControls As Collection

L'oggetto itm è dichiarato come cMatrixItem e servirà per tipizzare correttamente l'oggetto da aggiungere alla Collection.

Quindi definiamo gli Eventi che la Classe sarà in grado di rilasciare.
Come detto prima, gli eventi qui dichiarati a scopo esemplificativo sono pochi, ma è possibile arricchire, semplicemente dichiarandoli, la lista di eventi che la Classe sarà in grado di rilasciare in risposta alle azioni compiute sui controlli gestiti dalla Collection.

Codice:
Public Event Click(item As Object)
Public Event Change(item As Object)

Nel nostro caso, al posto di restituire esclusivamente un indice, nelle Routines di Evento forniremo un riferimento all'oggetto che l'evento lo ha generato.
Restituire solo l'indice servirebbe a poco visto che i controlli in VBA non sono comunque indicizzabili, rendendo di conseguenza difficile l'identificazione del controllo stesso in relazione all'evento scatenato.

I moduli di Classe possiedono un evento che viene scatenato quanto esse vengono istanziate nel codice che le utilizza: l'evento Class_Initialize().
Questo possiamo sfruttarlo per "dare vita" alla nuova Collection di controlli:

Codice:
Private Sub Class_Initialize()
Set cControls = New Collection
End Sub

Occupiamoci di definire adesso un metodo Add attraverso il quale, da codice, potremo aggiungere alla Collection i controlli che ci interessa gestire come Array

Codice:
Public Sub Add(ByVal actItm As Object)
Set itm = New cMatrixItem
Set itm.CallerObject = Me

If TypeOf actItm Is MSForms.TextBox Then
Set itm.itmTextBox = actItm
ElseIf TypeOf actItm Is MSForms.Image Then
Set itm.itmImage = actItm
ElseIf TypeOf actItm Is MSForms.CheckBox Then
Set itm.itmCheckBox = actItm
End If

cControls.Add itm
Set itm = Nothing

End Sub

Nel metodo appena definito si può notare che il controllo è visto in maniera generica ma viene fatta una distinzione sul suo tipo tramite l'If...Then. Questo avviene perchè non tutti i controlli possiedono le stesse proprietà o i medesimi eventi, anche se molti condividono entrambi.
Anche in questo caso gli oggetti considerati sono solo 3, ma la possibilità di espandere il range di tipologie è sempre valida e fattibile in maniera immediata.

Continuiamo lo sviluppo della Classe aggiungendo 2 Proprietà in sola lettura: Count e ItemCollection per ottenere rispettivamente il numero di elementi già presenti nell'Array e il riferimento ad uno dei controlli dell'Array attraverso un Indice numerico.

Codice:
Public Property Get Count() As Single
Count = cControls.Count
End Property

Public Property Get ItemCollection(ByVal Index As Single) As Object
Dim tmpObject As Object
Set tmpObject = cControls(Index)
With tmpObject
If Not .itmCheckBox Is Nothing Then Set ItemCollection = .itmCheckBox
If Not .itmImage Is Nothing Then Set ItemCollection = .itmImage
If Not .itmTextBox Is Nothing Then Set ItemCollection = .itmTextBox
End With
End Property

Ricordo che anche nella ItemCollection è possibile aggiungere altri tipi di controlli analogamente a come già fatto per le 3 tipologie in esame.

Passiamo adesso a definire le routines che rilasceranno gli eventi veri e propri attraverso la loro invocazione (da parte dell'Item) dei metodi del CallerObject

Codice:
Friend Sub ItemClick(item As Object)
RaiseEvent Click(item)
End Sub

Friend Sub ItemChange(item As Object)
RaiseEvent Change(item)
End Sub

Inutile dire che anche in questo ennesimo caso si possono aggiungere tutti gli eventi necessari relativi ai vari controlli.
Ovviamente bisogna badare al fatto che molti controlli possono condividere eventi dello stesso genere, come nel caso dei controlli Image e CheckBox che condividono l'evento Click. In questi casi basterà dichiarare una sola volta la Routine ItemClick.

Questo è il codice completo della Classe cCtlMatrix:
Codice:
Dim itm As cMatrixItem
Dim cControls As Collection

Public Event Click(item As Object)
Public Event Change(item As Object)

Private Sub Class_Initialize()
Set cControls = New Collection
End Sub

Public Sub Add(ByVal actItm As Object)
Set itm = New cMatrixItem
Set itm.CallerObject = Me

If TypeOf actItm Is MSForms.TextBox Then
Set itm.itmTextBox = actItm
ElseIf TypeOf actItm Is MSForms.Image Then
Set itm.itmImage = actItm
ElseIf TypeOf actItm Is MSForms.CheckBox Then
Set itm.itmCheckBox = actItm
End If

cControls.Add itm
Set itm = Nothing

End Sub

Public Property Get Count() As Single
Count = cControls.Count
End Property

Public Property Get ItemCollection(ByVal Index As Single) As Object
Dim tmpObject As Object
Set tmpObject = cControls(Index)
With tmpObject
If Not .itmCheckBox Is Nothing Then Set ItemCollection = .itmCheckBox
If Not .itmImage Is Nothing Then Set ItemCollection = .itmImage
If Not .itmTextBox Is Nothing Then Set ItemCollection = .itmTextBox
End With
End Property

Friend Sub ItemClick(item As Object)
RaiseEvent Click(item)
End Sub

Friend Sub ItemChange(item As Object)
RaiseEvent Change(item)
End Sub

La progettazione delle Classi è terminata. Non rimane che creare un semplice progetto di prova per valutare la correttezza del comportamento.
Aggiungiamo un nuovo UserForm e disponiamo su di esso alcuni controlli Image, delle CheckBox, qualche TextBox, un CommandButton e una Label. Lasciamo pure i nomi di default, come in figura.



Usiamo questo codice per il Form:

Codice:
Dim WithEvents TextBoxArray As cCtlMatrix
Dim WithEvents ImageArray As cCtlMatrix
Dim WithEvents CheckArray As cCtlMatrix

Private Sub UserForm_Initialize()

Dim ctl As Control

Set TextBoxArray = New cCtlMatrix

For Each ctl In Me.Controls
If TypeOf ctl Is MSForms.TextBox Then
TextBoxArray.Add ctl
End If
Next ctl

Set ImageArray = New cCtlMatrix

For Each ctl In Me.Controls
If TypeOf ctl Is MSForms.Image Then
ImageArray.Add ctl
End If
Next ctl

Set CheckArray = New cCtlMatrix

For Each ctl In Me.Controls
If TypeOf ctl Is MSForms.CheckBox Then
CheckArray.Add ctl
End If
Next ctl

End Sub

Private Sub CheckArray_Click(item As Object)
Label1.Caption = item.Name & ": " & item.Value
End Sub

Private Sub Command1_Click()
For k = 1 To CheckArray.Count
CheckArray.ItemCollection(k).Value = True
Next
End Sub

Private Sub TextBoxArray_Change(item As Object)
Label1.Caption = item.Name & ": " & item.Text
End Sub

Private Sub ImageArray_Click(item As Object)
Label1.Caption = item.Name
End Sub

Per dubbi o chiarimenti non esitate a contattarmi.

E' possibile scaricare il file di Excel con il progetto completo da qui: DOWNLOAD


 
Click!  
Photo 1 of 15
Benvenuto in questo piccolo angolino di Web!
Se ti va, lascia un commento per testimoniare il tuo passaggio!
Please wait...
Sorry, the comment you entered is too long. Please shorten it.
You didn't enter anything. Please try again.
Sorry, we can't add your comment right now. Please try again later.
To add a comment, you need permission from your parent. Ask for permission
Your parent has turned off comments.
Sorry, we can't delete your comment right now. Please try again later.
You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
Complete the security check below to finish leaving your comment.
The characters you type in the security check must match the characters in the picture or audio.
Francescowrote:
Bellissime le immagini 3D. Mi ha colpito "Terrace" che se non mi sbaglio è stata pubblicata in una rivista di grafica 3D.
Non sono un grafico ma mi piace ammirare le opere di questo genere.
Complimenti di Nuovo.
Gandalfrank
Jan. 4

MasterDrive.it MasterDrive.it