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! 
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.
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.
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.
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.
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:
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:
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.
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:
Private Sub Command1_Click() tProgress.Enabled = Not tProgress.Enabled If tProgress.Enabled Then Command1.Caption = "Stop" Else Command1.Caption = "Start" End If End Sub
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 -> Progetti, oppure cliccando qui: 
Non esitate a contattarmi per farmi presenti inesattezze o chiedere ulteriori chiarimenti. 
























