TheTruster 的个人资料TheTruster's Box照片日志列表更多 工具 帮助

日志


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


11月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



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

11月3日

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

10月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


9月20日

Esplorare un Database Access con OpenSchema di ADO in Visual Basic 6


ADO è uno strumento molto potente per quanto riguarda l'accesso ad un DB, e consente di gestire il trattamento dei dati da/per il DB in maniera piuttosto agevole ed intuitiva con relativamente pochi oggetti e con semplici metodi.

Un aspetto meno conosciuto del modello ad oggetti ADO è la possibilità di "esplorare" il DB anche dal punto di vista strutturale con il metodo OpenSchema. Con questo metodo possiamo andare a sbirciare nell'elenco delle tabelle disponibili in un DB e dei relativi campi. Non solo questo, in realtà, poichè il metodo che andrò ad illustrare - seppure in minima parte - permette di esplorare molto più delle semplici tabelle e dei campi che le compongono (oltre ad eventuali indici, tipi di campo, chiavi primarie, etc.) in quanto consente di accedere all'intero catalogo delle query o delle procedure e ad altre peculiari caratteristiche riscontrabili normalmente nella struttura di un database.

A questo punto potrebbe anche sollevarsi la questione del "perchè" dovremmo occuparci di andare ad esplorare la struttura di un DB, quando quello che normalmente basta è trattarlo come un cassetto nel quale riporre dati ritirandoli fuori al momento giusto! In realtà (anche se non è una pratica che consiglierei) potrebbe capitare che la struttura di un DB non sia così rigida e che si renda necessario, in corso di utilizzo, l'aggiunta o l'eliminazione dinamica di tabelle o campi. Per non rinunciare alla flessibilità della nostra applicazione non rimane altra strada che far sapere all'applicazione stessa come è fatto il DB.

Per semplicità ci rivolgeremo ad un database Access e, per meglio comprendere il funzionamento del metodo in argomento, realizzeremo una semplice applicazione che, volendo, potrà essere migliorata ed ampliata per dotarla di ulteriori caratteristiche, che le permettano di andare ancora più in profondità nell'esplorazione di un DB.

Questo piccolo tool sarà formato da un singolo Form all'interno del quale troveranno posto alcuni controlli come un TextBox, due ListView, un controllo CommonDialog, un paio di pulsanti e qualche label.

Per usare la ListView e il CommonDialog dovremo referenziare questi controlli nella lista dei Componenti, accessibile dal menu Progetto -> Componenti.



Sarà inoltre necessario referenziare la libreria ADO più recente sul PC. Normalmente dovrebbe essere la Microsoft ActiveX Data Object 2.8 Library



Preparato lo scenario, possiamo procedere alla progettazione del Form.



Iniziamo a scrivere del codice e, nella sezione dichiarazioni del nostro Form, dichiariamo l'oggetto relativo alla connessione che servirà per accedere al DB.
Dim cn As ADODB.Connection

Sfruttando l'evento Form_Load() possiamo occuparci di preparare gli oggetti che raccoglieranno i dati, ovvero le ListView. Aggiungeremo delle colonne in relazione ai dati da visualizzare e contestualmente prepareremo anche il CommonDialog per permetterci di specificare il Database .mdb che esploreremo.
Private Sub Form_Load()

With lwTables
    .View = lvwReport
    .ColumnHeaders.Add , , "Nome Tabella"
    .ColumnHeaders.Add , , "Tipo Tabella"
End With

With lwFields
    .View = lvwReport
    .ColumnHeaders.Add , , "Nome Campo"
    .ColumnHeaders.Add , , "Tipo Campo"
    .ColumnHeaders.Add , , "Lunghezza"
End With

With cDialog
    .CancelError = True
    .DefaultExt = "*.mdb"
    .DialogTitle = "Seleziona un Database Access"
    .Filter = "Database Access (*.mdb)|*.mdb"
    .Flags = cdlOFNExplorer Or cdlOFNFileMustExist
End With

End Sub
Passiamo a scrivere il codice relativo alla pressione sul tasto di caricamento del DB.
La sequenza delle operazioni è piuttosto logica:

  • viene aperto il CommonDialog in modalità di selezione Files
  • una volta ottenuto il nome del file lo si usa per aprire la Connessione dichiarata in precedenza
  • vengono resettate le ListView che accoglieranno i dati
  • si invoca la routine RetrieveTables (che vedremo dettagliatamente in seguito) che serve per popolare la ListView con le tabelle presenti nel DB.
Private Sub cmdLoadDB_Click()
On Error GoTo No_File

cDialog.ShowOpen

Set cn = New ADODB.Connection
cn.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & cDialog.FileName
txtDBPath.Text = cDialog.FileName

lwTables.ListItems.Clear
lwFields.ListItems.Clear

RetrieveTables

No_File:

End Sub
La Routine RetrieveTables è uno dei tasselli importanti di questa piccola applicazione.
E' qui che viene utilizzato il metodo OpenSchema per accedere alla struttura delle Tabelle del nostro DB. Come dicevo, attraverso OpenSchema è possibile esplorare diversi aspetti della struttura del database e tutto dipende dalla costante passata come argomento al metodo (Schema As SchemaEnum) e dai criteri imposti (Restrictions), che sono opzionali e variano a seconda della costante utilizzata e in base alla necessità di filtrare, in relazione alla nostra esigenza, la lista degli oggetti che vogliamo ottenere.
La sintassi del metodo è la seguente:

OpenSchema (Schema As SchemaEnum, [Restrictions], [SchemaID])

Il metodo restituisce un oggetto di Recordset che conterrà tanti record per quante tabelle verranno trovate.
Per ottenere la lista di tutte le tabelle comprese quelle nascoste "di sistema" utili alle impostazioni del DB è necessario specificare esclusivamente la costante adSchemaTables.
Sub RetrieveTables()

Dim itmX As MSComctlLib.ListItem
Dim rsTables As ADODB.Recordset

Set rsTables = cn.OpenSchema(adSchemaTables)
If Not (rsTables.BOF And rsTables.EOF) Then    
    While Not rsTables.EOF
        Set itmX = lwTables.ListItems.Add(, , rsTables.Fields("TABLE_NAME").Value)
            itmX.ListSubItems.Add , , rsTables.Fields("TABLE_TYPE").Value
        rsTables.MoveNext
    Wend
End If
rsTables.Close

Set rsTables = Nothing
Set itmX = Nothing

End Sub
Come si può vedere nella precedente routine sono stati utilizzati esclusivamente 2 campi del recordset contenente la lista delle tabelle, ovvero TABLE_NAME e TABLE_TYPE, ma è possibile sapere molto di più su una tabella, utilizzando il valore contenuto negli altri campi:
TABLE_CATALOG
TABLE_SCHEMA
TABLE_NAME
TABLE_TYPE
TABLE_GUID
DESCRIPTION
TABLE_PROPID
DATE_CREATED
DATE_MODIFIED
Occupiamoci adesso di gestire la seconda ListView che ospiterà la lista dei campi e alcuni dei dati ad essi relativi.
Useremo l'evento ItemClick() per ottenere dall'Item cliccato il nome della tabella da passare al metodo OpenSchema come parametro "Restrictions".
Private Sub lwTables_ItemClick(ByVal Item As MSComctlLib.ListItem)
lwFields.ListItems.Clear
RetrieveFields (Item.Text)
End Sub
Restrictions in realtà può essere un Array contenente diversi parametri dipendenti strettamente dal tipo di oggetto richiesto.
Una lista completa dei parametri relativi ad ogni costante che è possibile passare al metodo OpenSchema la trovate qui:

W3Schools - OpenSchema

Per quello che ci riguarda, ovvero tirar fuori la lista dei campi relativi ad una tabella ci basta questo:
Constant Value Description Constraint Columns
adSchemaColumns 4 Returns the columns of tables defined in the catalog TABLE_CATALOG
TABLE_SCHEMA
TABLE_NAME
COLUMN_NAME
Come dicevo in precedenza Restrictions è facoltativo per cui, se non viene usato, OpenSchema restituirà l'elenco completo di tutti i campi del DB a prescindere dalla tabella in cui essi si trovino.
Ovviamente non è il nostro intento, e considerato che abbiamo a disposizione il nome della tabella della quale intendiamo sapere i campi, è opportuno preparare un Array da passare ad OpenSchema per ottenere quelli presenti nella tabella di nostro interesse.

L'array è presente nella routine seguente e si chiama Criteri. Esso viene valorizzato con 3 parametri dei quali i primi 2 come Empty (ovvero l'elenco dei campi non verrà filtrato nè per TABLE_CATALOG nè per TABLE_SCHEMA) ma contiene, nel terzo parametro, il nome della tabella passato attraverso l'argomento FromTable della Routine. Non è necessario indicare il 4° parametro che verrà valutato anch'esso come Empty:
Sub RetrieveFields(FromTable As String)

Dim itmX As MSComctlLib.ListItem
Dim rsFields As ADODB.Recordset
Dim Criteri As Variant

Criteri = Array(Empty, Empty, FromTable)

Set rsFields = cn.OpenSchema(adSchemaColumns, Criteri)
If Not (rsFields.BOF And rsFields.EOF) Then
    While Not rsFields.EOF
        Set itmX = lwFields.ListItems.Add(, , rsFields.Fields("COLUMN_NAME").Value)
            itmX.ListSubItems.Add , , ConvertTypeCode(rsFields.Fields("DATA_TYPE").Value)
            itmX.ListSubItems.Add , , rsFields.Fields("CHARACTER_MAXIMUM_LENGTH").Value & ""
        rsFields.MoveNext
    Wend
End If
rsFields.Close

Set rsFields = Nothing
Set itmX = Nothing

End Sub
Analogamente a quanto avviene per la ListView delle tabelle anche per i campi abbiamo ottenuto, oltre ai loro nomi, delle informazioni aggiuntive.
La prima è DATA_TYPE che altro non è che la tipizzazione del campo del DB. Il valore restituito è una costante numerica che effettivamente, a meno di non avere la memoria di Salomone, dice davvero poco sull'effettiva natura del dato presente nel campo. Per ottenere una risposta maggiormente indicativa possiamo realizzare una semplice Function che restituisce una stringa con il nome della costante passata come argomento.
Function ConvertTypeCode(c As Integer) As String

Select Case c
    
        Case adDBTimeStamp
            ConvertTypeCode = "adDBTimeStamp"
        Case adArray
            ConvertTypeCode = "adArray"
        Case adBigInt
            ConvertTypeCode = "adBigInt"
        Case adBinary
            ConvertTypeCode = "adBinary"
        Case adSingle
            ConvertTypeCode = "adSingle"
        Case adNumeric
            ConvertTypeCode = "adNumeric"
        Case adLongVarWChar
            ConvertTypeCode = "adLongVarWChar"
        Case adLongVarChar
            ConvertTypeCode = "adLongVarChar"
        Case adLongVarBinary
            ConvertTypeCode = "adLongVarBinary"
        Case adIUnknown
            ConvertTypeCode = "adIUnknown"
        Case adInteger
            ConvertTypeCode = "adInteger"
        Case adIDispatch
            ConvertTypeCode = "adIDispatch"
        Case adGUID
            ConvertTypeCode = "adGUID"
        Case adError
            ConvertTypeCode = "adError"
        Case adEmpty
            ConvertTypeCode = "adEmpty"
        Case adDouble
            ConvertTypeCode = "adDouble"
        Case adDecimal
            ConvertTypeCode = "adDecimal"
        Case adDBTimeStamp
            ConvertTypeCode = "adDBTimeStamp"
        Case adDBTime
            ConvertTypeCode = "adDBTime"
        Case adDBDate
            ConvertTypeCode = "adDBDate"
        Case adDate
            ConvertTypeCode = "adDate"
        Case adCurrency
            ConvertTypeCode = "adCurrency"
        Case adChar
            ConvertTypeCode = "adChar"
        Case adBSTR
            ConvertTypeCode = "adBSTR"
        Case adByRef
            ConvertTypeCode = "adByRef"
        Case adBoolean
            ConvertTypeCode = "adBoolean"
        Case adSmallInt
            ConvertTypeCode = "adSmallInt"
        Case adTinyInt
            ConvertTypeCode = "adTinyInt"
        Case adUnsignedBigInt
            ConvertTypeCode = "adUnsignedBigInt"
        Case adUnsignedInt
            ConvertTypeCode = "adUnsignedInt"
        Case adUnsignedSmallInt
            ConvertTypeCode = "adUnsignedSmallInt"
        Case adUnsignedTinyInt
            ConvertTypeCode = "adUnsignedTinyInt"
        Case adBoolean
            ConvertTypeCode = "adBoolean"
        Case adUserDefined
            ConvertTypeCode = "adUserDefined"
        Case adVarBinary
            ConvertTypeCode = "adVarBinary"
        Case adVarChar
            ConvertTypeCode = "adVarChar"
        Case adVariant
            ConvertTypeCode = "adVariant"
        Case adVector
            ConvertTypeCode = "adVector"
        Case adVarWChar
            ConvertTypeCode = "adVarWChar"
        Case adWChar
            ConvertTypeCode = "adWChar"
End Select

End Function
Al posto dei nomi delle costanti è ovviamente possibile farsi restituire dalla Function qualsiasi testo ci aiuti a capire meglio il tipo di contenuto del Campo.

La seconda informazione che intendiamo rilevare dal Campo è la lunghezza massima consentita (valevole per i campi di tipo testo), per cui possiamo analizzare il valore contenuto in CHARACTER_MAXIMUM_LENGTH

Anche in questo caso, come per le Tabelle è possibile sapere molte più informazioni sullo stato di un campo, e basta utilizzare altri nomi di campo, tra quelli qui sotto elencati, disponibili nel Recordset restituito da OpenSchema:
TABLE_CATALOG
TABLE_SCHEMA
TABLE_NAME
COLUMN_NAME
COLUMN_GUID
COLUMN_PROPID
ORDINAL_POSITION
COLUMN_HASDEFAULT
COLUMN_DEFAULT
COLUMN_FLAGS
IS_NULLABLE
DATA_TYPE
TYPE_GUID
CHARACTER_MAXIMUM_LENGTH
CHARACTER_OCTET_LENGTH
NUMERIC_PRECISION
NUMERIC_SCALE
DATETIME_PRECISION
CHARACTER_SET_CATALOG
CHARACTER_SET_SCHEMA
CHARACTER_SET_NAME
COLLATION_CATALOG
COLLATION_SCHEMA
COLLATION_NAME
DOMAIN_CATALOG
DOMAIN_SCHEMA
DOMAIN_NAME
DESCRIPTION
Siamo giunti alla conclusione e la nostra piccola applicazione dovrebbe essere funzionante, eseguendo le operazioni per le quali l'abbiamo creata. Per scoprire se è vero non ci resta che lanciarla con F5 e caricare un DB premendo sul pulsante con i tre puntini [...]



Il risultato non è male e abbiamo appena scalfito la superficie del vasto mondo di OpenSchema, ma la metodologia di base qui illustrata è valevole per l'esplorazione degli ulteriori e talvolta più complessi aspetti di un Database. Una delle caratteristiche che si possono implementare in maniera quasi indolore, ad esempio, è la rilevazione delle Query presenti nel DB (delle quali è possibile sapere anche la frase SQL che le definisce) o ancora le chiavi primarie relative ad una determinata Tabella oppure i suoi Indici... le possibilità sono moltissime.

In area Download -> Software potete scaricare il progetto di esempio realizzato per illustrare il metodo OpenSchema.




9月13日

Sezione Modelli 3D - Aggiunti 3 Modelli


Aggiunti 3 nuovi modelli nella sezione 3D Models, di cui uno (la bottiglia di Heineken) completo di Textures già mappate.



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



9月6日

Aggiornata sezione Modelli 3D


Ho inserito 4 nuovi modelli 3D:



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

9月5日

RemindMe!


Non so voi, ma io ho il bordo del monitor strapieno di piccole etichette adesive che mi stanno a "ricordare che devo ricordarmi" di fare qualcosa, o di chiamare qualcuno, o chissà che altro! Sorriso

Dunque è nato RemindMe!



E' nato un po' di tempo fa, ma considerato che ho deciso di includere questo programmino tra i programmi scaricabili dalla sezione Software, ve lo presento brevemente.

Ho deciso di realizzare questo programmino più che altro per passatempo... ce ne sono molti dello stesso tipo in rete, ma ho voluto cimentarmi lo stesso con questa mia realizzazione.

La versione del programma è la 0.1beta e mi piacerebbe se qualcuno di voi potesse provarlo comunicandomi cosa ne pensa e segnalandomi gli inevitabili problemi Sorriso

Visto che il programma attualmente è sprovvisto di Help, vi fornisco una panoramica generale delle sue funzioni, invitandovi a chiedermi "lumi" nel caso abbiate qualche dubbio sul funzionamento delle varie opzioni o funzioni.

Il programma, una volta avviato, risede nella TrayBar, ed è possibile accedere alle sue funzioni tramite il menu contestuale che appare quando si clicca col pulsante destro sull'icona.



è possibile aggiungere nuove note dall'apposita voce presente nel menu contestuale della TrayBar, oppure attivando la finestra di Gestione delle Note.



Attraverso questa finestra è possibile gestire tutti gli aspetti di ogni singola nota creata, ovvero:

  • testo della nota
  • colore del testo
  • formato carattere
  • allineamento (sinistra, destra, centro)
  • attributi (grassetto, corsivo, sottolineato)



E' possibile, inoltre, variare anche il colore della nota, attraverso una finestra di selezione del colore personalizzata



ed impostare un allarme per ricevere un avviso ad uno specifico giorno ed orario.



Ciascuna delle azioni su descritte è impostabile direttamente da ogni Nota presente sul Desktop, per cui senza la necessità di aprire la finestra di gestione delle note.

E' possibile anche variare l'opacità delle note, per renderle semi-trasparenti, facendo in modo che esse siano meno invasive possibile occupando parte del desktop.

Ogni Nota è dimensionabile separatamente, ma è possibile decidere una dimensione di Default che varrà applicata a tutte le Note create successivamente.
Questo è possibile dalla finestra delle Opzioni:



Attraverso questa finestra si potranno variare le impostazioni di Default che verranno applicate alle note di futura creazione, ovvero:

  • Dimensioni della nota
  • Colore di sfondo
  • Colore, stile, dimensione e attributi del testo
in più è possibile scegliere

  • File Audio da suonare per l'avviso
  • La permanenzza dell'avviso sullo schermo
  • Se il programma parte automaticamente all'avvio di Windows
  • Se viene visualizzato o meno lo Splash-Screen all'avvio

Spero di non aver tralasciato nulla nell'esposizione di questa panoramica.

Nel caso vogliate provare questo piccolo software potete scaricarlo da qui DOWNLOAD


8月31日

Nuova Sezione - Download Modelli 3D


Data la mia passione per la modellazione 3D, durante la creazione delle mie scene (alcune delle quali potete vedere nell'album 3D Gallery), mi servono degli oggetti 3D che la quasi totalità delle volte realizzo da me.
Data la difficoltà di reperire in rete oggetti free che siano anche dotati di un discreto livello d dettaglio, ho deciso di mettere a disposizione, periodicamente, alcuni dei modelli facenti parte del mio archivio.

Per adesso ho già inserito 2 modelli che è possibile scaricare liberamente, ma spero di poter aumentare, nel tempo, la collezione di modelli a disposizione.

E' gradito un cenno sul loro utilizzo da parte di quanti li riterranno meritevoli di far parte dei loro progetti. Animoticon

Potete accedere all'area dei modelli cliccando dall'apposito tasto nella sezione Download in basso a destra in Home Page oppure qui:


8月27日

"Making Of" del progetto "Retrò Rocket"


Ultimamente ho partecipato, sul sito di 3DAttack, ad una specie di mini-contest di modellazione grafica che viene chiamato "Sudden Attack". In pratica viene dato un tema, spesso è un oggetto, e bisogna realizzare una scena modellata, texturizzata ed illuminata, approfittando di un lasso di tempo relativamente breve. Più o meno 4 giorni.

Per l'ultimo Sudden Attack il tema proposto era un Retrò Rocket, ovvero uno di quei razzi con forme improponibili somiglianti molto più spesso a giocattoli che a razzi veri.
Cercando in rete delle references per la modellazione del soggetto principale mi sono imbattuto proprio in un razzo giocattolo, con una forma piuttosto simpatica, da qui è nata l'idea di inserire il Rocket in un contesto che lo facesse assomigliare il più possibile ad un giocattolo.

Ecco la reference sulla quale mi sono basato con accanto il modello che ho realizzato:



Il modello è realizzato con la tecnica del Box Modeling, ovvero costruito prendendo come base di partenza un solido semplce (nel mio caso un cilindro), sul quale si effettuano tagli, estrusioni, ridimensionamenti o spostamenti di poligoni, o punti.
L'oggetto finale ha un numero relativamente basso di poligoni e verrà inserito in un HyperNurbs (come quasi tutti i miei modelli).
L'HyperNurbs ha la caratteristica di "smussare" il modello, aumentandone la suddivisione dei poligoni e questo permette di avere dei modelli "lowpoly", semplici da gestire e veloci da renderizzare per le prove, ma dei quali è possibile aumentarne la definizione prima del render finale.



L'idea che avevo in mente era quella di inserire il "giocattolo" in uno scaffale, condividendo lo spazio con altri oggetti. Il successivo passo, quindi, è stato quello di realizzare la scaffalatura. Ne basta una piccola porzione... giusto la parte che rientrerà nell'inquadratura.



Creato il modello contenitore della scena, ho dimensionato adeguatamente il razzo.
La scena, a questo punto, come si potrà immaginare era decisamente spoglia, per cui ho deciso di riempirla con alcuni oggetti che normalmente si potrebbero trovare su uno scaffale della cameretta dei ragazzi. Dato il poco tempo a disposizione, piuttosto che modellare altri giocattoli che avrebbero richiesto più tempo del consentito e "rubato" la scena al soggetto principale, ho optato per qualche scatola e dei dischetti.



Un razzo va nello spazio... si sa. Come fare lo spazio nell'angolino di uno scaffale? Un poster! Va da se, che nello spazio ci sono anche i pianeti (Grazie Francesco Animoticon).

Il poster della volta celeste è stato realizzato con un piano e un paio di deformatori per realizzare le pieghe, mentre per i pianeti sono bastate delle semplici sfere. Ho "attaccato" il poster e i "pianeti" allo scaffale con fili realizzati con delle Splines dentro una SweepNUrbs e con dello scotch, realizzato anch'esso con delle Splines, ma questa volta utilizzando una LoftNurbs.
Poster e pianeti sono stati texturizzati utilizzando delle texture trovate in rete, opportunamente modificate per lo scopo con Photoshop.

La fase di modellazione è terminata, non volevo rischiare che la scena fosse troppo piena, distraendo l'occhio con troppi particolari.
Adesso non resta che assemblare tutti gli oggetti nella scena in modo che la disposizione sia armonica ed equilirata, quindi si passa alla fase di illuminazione e inquadratura.



Per le luci ho immaginato un set piuttosto semplice. Un'illuminazione a 2 punti disposti ai lati della camera, con uguale intensità, ma con distanze diverse dal soggetto. Questo tipo di illuminazione garantisce delle ombre più preponderanti dalla parte opposta alla luce più vicina, ammorbidite dalla luce più distante.

Tutte le luci utilizzate sono di tipo Area che fornicono delle ombre più aderenti alla realtà e, oltre alle 2 già menzionate, ho inserito 3 ulteriori luci - sempre area, ma con forma sferica - in corrispondenza di ciascuno dei 3 pianeti.
L'illuminazione globale (o GI) farà i resto calcolando le riflessioni secondarie della luce sui vari oggetti, conferendo più realismo alla scena.



L'inquadratura è stata realizzata in modo da coprire con i soggetti modellati l'intero "fotogramma" evitandomi di dover modellare anche un ambiente circostante. Allo scopo, per simulare un ambiente esterno, benchè non fosse estremamente necessario, ho usato una mappa angolare HDRI che oltre a riflettersi sugli oggetti che possiedono riflessioni, fornisce anche un'illuminaziona aggiuntiva diffusa.



E' giunto il momento dei materiali. Per il razzo, ho cercato di rimanere fedele alla reference, riproducendo più verosimilmente possibile il materiale plastico rosso e dorato della fusoliera. Per i pianeti ho utilizzato un materiale plastico lucido che avesse un po' di luminanza, per aiutare un po' le luci disposte in corrispondenza di essi dando l'impressione che la luce venga da dentro, piuttosto che dalla loro superficie.
Per tutti gli altri materiali, ho attinto un po' dal mio archivio di materiali, compiendo le necessarie modifiche perchè risultassero coerenti con l'ambiente.

Ecco una prova di tutti i materiali nelle condizioni di illuminazione della scena.



Dopo 1 ora e una cinquantina di minuti di "cottura", questo è il render finale dell'immagine, sulla quale non ho ritenuto necessario effettuare eccessiva post-produzione, se non per una leggerissima aggiustatina ai livelli, realizzata in Photoshop.



Spero sia stato di vostro gradimento Sorriso

7月10日

Estrarre Extended Properties da un File in Visual Basic 6


Mi piacerebbe condividere con voi una funzione che ho realizzato per rispondere ad una richiesta posta nella Sezione Visual Basic 6 su MasterDrive.it.

La richiesta puntava a conoscere una metodologia per rilevare, da un qualsiasi file, in linguaggio VB6, le informazioni contenute nel riepilogo di Esplora Risorse di Windows, ovvero i cosiddetti Metadata o Extended File Properties.
In realtà per ottenere le classiche informazioni come Data Ultimo Accesso, Ultima Modifica e Dimensione, esiste l'efficiente e performante API GetFileInformationByHandle oppure il FileSyestemObject, ma la metodologia che sto per descrivere permette di avere indietro molte più informazioni come Proprietario, Autore, Titolo oppure Artista, Titolo album, Anno, Numero brano, Genere, Durata, etc. per i file audio, o ancora Modello fotocamera, Data immagine scattata, Formato per le foto, e molte altre informazioni.

Ci si avvarrà di un oggetto Shell creato via codice, utile per stabilire un "contatto" con il sistema, facendoci restituire tutte le proprietà che è possibile riscontrare in un qualsiasi file presente nel nostro sistema.
Per comodità ho preferito incapsulare il codice necessario al reperimento delle informazioni in una Function in modo che sia possibile dichiararla in un qualsiasi modulo .bas sfruttandone le potenzialità da qualsiasi punto del codice.

Utilizzeremo 3 variabili oggetto "chiave" atte a contenere i riferimenti agli oggetti necessari: Shell, Folder e Item.
Servirà, inoltre, un Array per conenere i nomi delle Proprietà che ci interessa ottenere dal File.
In realtà non sarebbe necessario, visto che le proprietà (circa 40) sono indicizzabili numericamente, ma considerato che la Function che realizzeremo restituirà una Collection, è opportuno inserire come chiave di ognuna delle proprietà il corrispondente testuale, per richiamarle con maggiore naturalezza all'interno del codice.
Ovviamente questo discorso andrebbe a cadere nel caso in cui il nostro programma dovesse girare su un sistema non italiano, poichè la descrizione di queste proprietà è strettamente legata alla lingua del sistema. In questo caso, converrebbe affidarsi esclusivamente all'indice numerico.

Il principio di funzionamento è il seguente:

vengono dichiarati gli oggetti Shell, Folder e Item, l'array dei nomi delle proprietà e la Collection

Dim oShell As Object
Dim oFolder As Object
Dim oItem As Object
Dim arrProps(41)
Dim tmpCol As New Collection
 
A questo punto assegnamo gli oggetti, creando l'oggetto shell e assegnando l'oggetto folder attraverso di esso:

Set oShell = CreateObject("Shell.Application")
Set oFolder = oShell.NameSpace(sPath & "")

sPath è una delle variabili che la funzione prenderà in ingresso.
Popoliamo l'array delle proprietà ciclando, attraverso un indice, le proprietà disponibili:

For i = 0 To 40
    arrProps(i) = oFolder.GetDetailsOf(oFolder.Items, i)
Next
Questo è il momento in cui possiamo popolare la Collection con i valori delle proprietà precedentemente immagazzinate nell'array, utilizzando un ciclo For Each oItem selezionando solo il nome del File che ci interessa e che avremo passato alla Function attraverso il parametro sFilename

For Each oItem In oFolder.Items
    If LCase(oItem.Name) = LCase(sFileName) Then
        For i = 0 To 40
            If arrProps(i) <> "" Then
                tmpCol.Add oFolder.GetDetailsOf(oItem, i), arrProps(i)
            End If
        Next
        Exit For
    End If
Next

Assegnamo la collection popolata risultante alla Funcion per restituirla al codice chiamante

Set FileProperties = tmpCol

e annulliamo tutti gli oggetti utilizzati per restituire le risorse al sistema

Set tmpCol = Nothing
Set oFolder = Nothing
Set oShell = Nothing

Questa è la Function completa:

Public Function FileProperties(sPath As String, sFileName As String) As Collection
Dim oShell As Object
Dim oFolder As Object
Dim oItem As Object
Dim arrProps(41)
Dim tmpCol As New Collection
Set oShell = CreateObject("Shell.Application")
Set oFolder = oShell.NameSpace(sPath & "")
For i = 0 To 40
    arrProps(i) = oFolder.GetDetailsOf(oFolder.Items, i)
Next
For Each oItem In oFolder.Items
    If LCase(oItem.Name) = LCase(sFileName) Then
        For i = 0 To 40
            If arrProps(i) <> "" Then
                tmpCol.Add oFolder.GetDetailsOf(oItem, i), arrProps(i)
            End If
        Next
        Exit For
    End If
Next
Set FileProperties = tmpCol
Set tmpCol = Nothing
Set oFolder = Nothing
Set oShell = Nothing
End Function

Si potrà utilizzare la function in qualsiasi punto del codice con una semplice riga, ma facendo attenzione al fatto che servirà una Collection di appoggio per ricevere il risultato:

Dim clProps As Collection
Set clProps = FileProperties("C:\TuaCartella\TuaSottoCartella\", "TuoFile.xyz")

Una volta ottenuta la collection è possibile "interrogarla" con la chiave relativa alla proprietà da analizzare:
Debug.Print clProps("Autore")
Debug.Print clProps("Data Creazione")

oppure, ancora meglio, per evitare problemi con i sistemi in lingua differente, con un indice numerico:
Debug.Print clProps(7)
Debug.Print clProps(15)

In un sistema Italiano, l'elenco delle chiavi delle proprietà ottenibili è questo:

Nome
Dimensione
Tipo
Data ultima modifica
Data creazione
Data ultimo accesso
Attributi
Stato
Proprietario
Autore
Titolo
Oggetto
Categoria
Pagine
Commenti
Copyright
Artista
Titolo album
Anno
Numero brano
Genere
Durata
Velocità in bit
Protetto
Modello fotocamera
Data immagine scattata
Formato
Titolo puntata
Descrizione programma
Dimensioni campione audio
Velocità campione audio
Canali
Società
Descrizione
Versione file
Nome prodotto
Versione del prodotto
Parole chiave



6月6日

DBExplorer


Mi capita spesso di avere a che fare con la realizzazione di un software che utilizzi dei Database Access.
Spesso, durante la programmazione, è necessario analizzare le tabelle del Database, per rendersi conto di quale Campo si trova in quale Tabella, o magari con che tipo di dati è stato dichiarato quel Campo... insomma, ci si trova ad aprire e chiudere la finestra di Access, molte volte! Le cose si complicano, ancora di più, se si utilizzano diversi Database per un unico programma edi è necessario analizzarli contemporaneamente!Ho pensato, quindi, di realizzare un piccolo tool che è in grado di aprire diversi Database Access, anche contemporaneamente, analizzarne la struttura e suddividerla in un diagramma ad albero mostrandone per ogni Database, le Tabelle (con i rispettivi Campi, Chiavi Primarie e Indici), le Query e le Procedure.

Il programma consente anche di visualizzare i dati contenuti nelle Tabelle e nelle Query eventualmente già presenti nel Database, inoltre, per le Query, può mostrare anche la stringa SQL utilizzata. Per i Campi invece può mostrare il tipo di formato dei dati contenuti, e l'eventuale lunghezza di caratteri se si tratta di un campo Testo.



Per facilitare la consultazione della struttura dei DB è presente anche una funzione di stampa con anteprima a video, che permette di elencare le Tabelle e, opzionalmente, di stamparne anche tutti i Campi dipendenti.



Per facilitare al programmatore la consultazione del Database e per il fatto che il programma dovrebbe essere utilizzato durante la digitazione del codice, ho ritenuto che, al pari di Access, sarebbe stato scomodo cambiare continuamente finestra per consultare la struttura del DB. Allo scopo è stata introdotta anche un'interfaccia ridotta, una sorta di Side-Bar, in modo che la finestra, decisamente più piccola della di quella principale, possa essere allineata a destra o a sinistra dello schermo, e posizionata opzionalmente "AlwaysOnTop". Con l'interfaccia ridotta, quindi, lo spazio occupato è minore, e risulta più agevole lavorare. Ancora minore è lo spazio occupato se la SideBar viene richiusa come un menù a tendina tramite l'apposito pulsante.



Altra caratteristica saliente del tool è rappresentata dal Query Tester.
Questo piccolo tool integrato nell'applicazione consente di comporre una query di selezione utilizzando come riferimento il Database selezionato, usando le funzioni SQL standard catalogate in un diagramma ad albero presente nella stessa finestra. Una volta creata la Query si può vederne il risultato nella griglia presente sul Form.



Un piccolo aiuto per i programmatori VB, consiste nella possibilità di copiare la Query creata, in modo che sia possibile incollarla così com'è nel codice, senza doversi preoccupare di suddividerla in stringhe concatenate... in pratica questa operazione è compiuta dal programma tramite un semplice click sull'apposito pulsante, e questo è il risultato:

Query = "SELECT DISTINCTROW [Order Details].OrderID, [Order Details].ProductID, " & _
        "Products.ProductName, [Order Details].UnitPrice, [Order " & _
        "Details].Quantity, [Order Details].Discount, CCur([Order " & _
        "Details].[UnitPrice]*[Quantity]*(1-[Discount])/100)*100 AS " & _
        "ExtendedPrice FROM Products INNER JOIN [Order Details] ON " & _
        "Products.ProductID = [Order Details].ProductID ORDER BY [Order " &_
        "Details].OrderID;"

Purtroppo, al momento, il programma è sprovvisto di Help, ma credo che in fin dei conti, l'utilizzo sia abbastanza intuitivo.
Conto comunque di realizzarlo, prima o poi.

Potete prelevare il programma da qui: DOWNLOAD


6月5日

MasterDrive.it Meeting 2007


Si è concluso da qualche giorno il


Si è svolto a Prato, ho partecipato con piacere e, nell'occasione, ho avuto modo di conoscere delle persone simpaticissime che fino a poco tempo fa conoscevo solo per qualche intervento sul Forum, o da qualche chiacchiera in MSN.

Eventi come questo "umanizzano" un po' una community web, e permettono di apprezzare anche il lato personale degli utenti oltre a quello prettamente tecnico. Un'occasione per discutere di argomenti frivoli, prendere decisioni sulle migliorie da apportare alla Community e, perchè no, farsi anche una buona mangiata approfittando dell'ottima cucina toscana!

L'organizzazione logistica è stata impeccabile, per cui non posso che complimentarmi con Alextyx (Alessandro) e Bottomap (Matteo) che, essendo del luogo, sono riusciti a trovare un posto eccellente per riunirci: Villa Fiorelli.

Per chi volesse dare un'occhiata, pubblico in Gallery alcune delle foto scattate al Meeting.

5月27日

TheTruster's Box Starts Here!


Eccomi qui.

Ho deciso di aprire finalmente il mio spazio. Non credo sia tanto per moda... si creano blog da così tanto tempo, che ormai potrei essere definito anche piuttosto retrogrado, non di certo trendy!

In ogni caso, ho deciso di ritagliarmi questo piccolo spazio nel web, senza pretese di alti contenuti, nè aggiornamenti continui; solo un piccolo cassetto dove depositare, ogni tanto, qualche pensiero, se mai sarà meritevole di essere condiviso, o qualche immagine prodotta con il mio software di modellazione 3D preferito (Cinema 4D) oppure qualche pezzetto di codice Visual Basic 6 prodotto dal basso della mia esperienza.
La grafica e la programmazione sono due tra le mie passioni quindi, quale miglior modo di condividerle col mondo?

Spero apprezzerete ciò che avrò intenzione di condividere con voi, cari i miei navigatori del web... e lasciate un commento, o voi ch'entrate!