Visore di fogli Excel 2007 in una griglia DatagridView
Transcript
Visore di fogli Excel 2007 in una griglia DatagridView
Visore di f ogli Excel 2007 in una griglia DatagridView Di Gianni Giaccaglini Il piccolo ma utile programma, di cui vanto la paternità e l’originalità è una versione didattica (che mi sono proposto di migliorare). Con esso si visualizza in una Form che incorpora un controllo DataGridView i contenuti del primo foglio di una cartella di lavoro Excel 2007, con tanto di intestazioni delle righe e colonne, che si aggiornano nel corso della navigazione nelle celle con opportuni pulsanti. Figura 1 L’utility Visore di Excel con tasti di navigazione su un foglio Excel attinto con l’API OpenXml SDK. Si noti anche il pulsante per la selezione anche discontinua di celle con conseguente calcolo, e segnalazione, della somma dei soli dati numerici ivi contenuti. Nota. Questa versione, ripeto, si limita al primo foglio. La versione (semi) professionale permette di navigare, visualizzandoli, su tutti i fogli della cartella di lavoro. Considerazioni preliminari L’avvento del formato Open XML delle versioni 2007 di Word, Excel (e Project) ha aperto orizzonti, che chi scrive ha in particolare illustrato sul libro FAG Open XML, guida allo sviluppo nonché sul prestigioso sito Visual Studio Tips & Tricks. Tale formato, è per l’appunto, “aperto” in quanto i nuovi file .docx/.docm o .xlsx/.xlsm sono una collezione di testi puri che aderiscono a uno schema XML. Conoscendone la composizione e l’articolazione si può così accedere, modificare e persino creare tali archivi anche senza che Office 2007 – o Office tout court! - sia installato sulla macchina ospite. Nota. Cito qui fugacemente due miei articoli specifici, il primo relativo a una tabella incorporata in un documento Word 2007, trasferita in un foglio elettronico (tramite i VSTO, Visual Studio Tools per Office, ed. 2008), il secondo descrive un procedimento (da me escogitato) per accedere ai contenuti relativi a un intervallo di date coordinate di un foglio di lavoro Excel. In due parole, comunque, il mio algoritmo crea una dictionary avente come chiave il riferimento delle celle e il contenuto (non formattato) di ciascuna come valore. Questo avviene grazie allo sfruttamento della specifica proprietà r del nodo <c> che fornisce il riferimento della cella, mentre il valore è quello del suo figlio <v>: <c r="B4"> ..<v>100.5</v> </c> Per massima comodità dei meno zelanti (ma buoni intenditori), riporto senza commenti il cuore dell’algoritmo (d’altronde ripreso più avanti quasi di peso, salvo una modifica): Dim Dim Dim Dim For ListaConChiaveRif As New Dictionary(Of String, String) MioFile = "C:\MiaCart\Semplice.xlsx" Cella1 = "B2", Cella2 = "G7" ZonaValori = ZonaAcquist(MioFile, Cella1, Cella2) Each CellaVal In ZonaValori Dim Rifer = CellaVal.Parent.@r Dim Dato As String If CellaVal.Parent.@t = "s" Then Dato = VettSharStrings(CellaVal.Value) Else Dato = CellaVal.Value End If ListaConChiaveRif.Add(Rifer, Dato) Next Il rompicapo del data binding Nella fase di analisi mi sono presto reso conto che il pur comodo data binding ottenibile in modo automatico nel familiare controllo DataGridView è a dir poco estremamente difficoltoso se non impossibile. Sull’impossibilità non oso esprimere giudizi lapidari, lasciando aperta la discussione nei caffè più o meno web bazzicati dai professionisti più accreditati. Uno di costoro, da me interpellato, mi ha un po’ deluso. Non dirò di chi si tratta ma è davvero un grosso guru di Visual Studio... Di fatto mi è parso che non si rendesse ben conto che: Il famoso data binding automatico si applica senza problemi a tabelle relazionali e a liste di oggetti in memoria, in entrambi i casi caratterizzati da precisi nomi dei campi, le une, e proprietà dell’oggetto, le altre, tutte e due tradotte in testa alle colonne della griglia; Uno spreadsheet, ahimè, può essere un insieme anche molto vasto, sovente dotato di molti buchi con intere colonne che possono mancare (e a me interessava visualizzare qualsiasi spreadsheet); Né va dimenticato che l’OOXML, tramite l’SDK specifica Open XML (ver. 1.1 o 2.0) fornisce un file XML, a sua volta contenente nodi relativi alle sole celle “piene”!, ora non risulta che il data “binder” del DataGridView sia in grado di esibire oggetti XML. La mia soluzione, ovvero il toro per le corna Il guru di cui sopra suggeriva la traduzione di tale file XML in una tabella o lista di oggetti. Entrambe le strade non nego in assoluto che siano percorribili. Ma come ottenere le intestazioni di colonne e righe? E il limite di colonne gestibili col DataGridView (ampio, circa 500, ma di gran lunga inferiore a quelle possibili in un foglio che in Excel 2007 sono ben 16.384)? Nota. D’ora in avanti si parlerà quasi sempre semplicemente di “griglia” anziché “DataGridView”. Ci sarebbe comunque da scrivere codice a monte (per tradurre lo spreadsheet in una lista o simili) e magari a valle (per inserire intestazioni di colonne e righe). Così ho deciso di prendere il toro per le corna, dopo aver appreso una cosa che molti sanno e tutti si dimenticano: l’oggetto DataGridView è programmabile, adattandolo ovviamente con codice ad hoc alle caratteristiche di una qualsiasi fonte di dati, inclusa quella particolare chiamata spreadsheet. Nota. Detto di passaggio, quanto verrà esposto costituisce un utile richiamo alle proprietà e metodi del controllo griglia, tutt’altro che ben documentati in giro. Utilizzabile altrove, direi. Prima di illustrare le varie routine, oso anticipare ai buoni intenditori, dotati altresì di una certa fantasia, le grandi linee del progetto, a valle della creazione di una Form con controllo DataGridView incorporato più i pulsanti per caricare un file xlsx o xlsm, mostrarne una “finestra” nelle colonne e righe predisposte (una sola riga, v. figura 2). Figura 2 L’utility VisoreDiExcel al lancio del programma. Il codice si articola nelle linee seguenti: 1. Caricamento in memoria del foglio e creazione della dictionary riferimento / valore ( ListaConChiaveRif nella fattispecie); 2. Creazione nella griglia di un certo numero di righe, con contestuale assegnazione delle intestazioni laterali di riga da 1 in avanti e di colonna, “A”, “B”, ... ”Z”, “AA”, “AB”,... eccetera (sequenza familiare quanto atipica); 3. Esplorazione delle celle di tale griglia, assegnando a ciascuna il valore attinto dalla dictionary sulla chiave riferimento. Il clou del punto 3 corrisponde a una inattesa (almeno per me) ma piacevole sorpresa: il DataGridView offre due utili proprietà HeaderText e .HeaderCell, relativa l’una all’intestazione di colonna, l’altra a quella di riga. Concatenandole (operatore &) si ottiene proprio il riferimento di quella cella! Risulta così possibile inserire nella cella giusta della griglia tutti e soli i valori prelevati dalla dictionary con l’esatto riferimento. Caso didattico introduttivo: caricamento di una matrice Dando per scontate le operazioni di creazione a project-time della Form col controllo griglia e gli altri pulsanti dalle etichette di limpida semantica, fornisco subito la sezione Dichiarazioni a monte della classe UserForm1: Imports Imports Imports Imports System.IO <xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"> Packaging = DocumentFormat.OpenXml.Packaging System.Xml.Linq Circa tali direttive ricordo ancora a ignari e immemori che prima si debbono fissare i riferimenti giusti, previo caricamento della libreria DocumentFormat.OpenXml (v. precedenti articoli su Visual Basic Tips & Trics). Ed ecco subito l’esordio della Classe Form1: Public Class Form1 Dim Matr(,) As String = {{"1", "2", "3", "4", "5", "6", {"10", "11", "12", "13", "14", {"19", "20", "21", "22", "23", {"28", "29", "30", "31", "32", {"37", "38", "39", "40", "41", {"46", "47", "48", "49", "50", {"55", "56", "57", "58", "59", {"64", "65", "66", "67", "68", "7", "8", "9"}, _ "15", "16", "17", "24", "25", "26", "33", "34", "35", "42", "43", "44", "51", "52", "53", "60", "61", "52", "69", "70", "71", Function SuccIntest(ByVal Intest As String) As String Dim i As Integer Dim Car As Char i = Intest.Length While i >= 1 Car = Intest(i - 1) If Car <> "Z"c Then Dim CodAsc = Char.ConvertToUtf32(Car, 0) Dim SuccCar = Char.ConvertFromUtf32(Codasc + 1) Intest = Intest.Substring(0, i - 1) & _ SuccCar & Intest.Substring(i) Return Intest Else ' Car è "Z" Intest = Intest.Substring(0, i - 1) & "A" _ & Intest.Substring(i) If i = 1 Then Return "A" & Intest End If i = i - 1 End If End While Return Intest End Function Function PrecedIntest(ByVal Intest As String) As String Dim i As Integer Dim Car As Char i = Intest.Length While i >= 1 Car = Intest(i - 1) If Car <> "A"c Then Dim CodAsc = Char.ConvertToUtf32(Car, 0) Dim PrecCar = Char.ConvertFromUtf32(CodAsc - 1) Intest = Intest.Substring(0, i - 1) & _ PrecCar & Intest.Substring(i) Return Intest Else If Intest.Length = 1 Then Return Intest "18"}, "27"}, "36"}, "45"}, "54"}, "63"}, "72"}} _ _ _ _ _ _ If i = 1 Then Intest = Intest.Remove(0, 1) Return Intest End If Intest = Intest.Substring(0, i - 1) & "Z" _ & Intest.Substring(i) i = i - 1 End If End While Return Intest End Function Function IntestCol(ByVal Rifer As String) As String Dim Col As String = Nothing Dim Lung As Integer = Rifer.Length Dim i = 0 Dim Car = Rifer(i) While Not Integer.TryParse(Car, 0) And i < Lung - 1 Col &= Car i += 1 Car = Rifer(i) End While Return Col End Function La matrice bidimensionali di costanti Matr è stata inserita a scopo dimostrativo. Il caso didattico introduttivo prevede infatti di caricarla nella griglia con inserimento sperimentale di intestazioni in stile Excel. Il pulsante specifico è quello di sinistra, etichettato “Carica matrice”. Comunque si focalizzi l’attenzione sulle tre funzioni (originali) utilizzate in tutto il progetto. Scopo delle prime due è quello di fornire l’intestazione di colonna successiva e precedente a un’intestazione data, mentre la terza, lasciata al commento autogestito, preleva da una stringa tipo “A12”, “BC123” la parte alfabetica - “A” e “BC” nei due casi – ovvero l’intestazione di colonna del riferimento Rifer. Commenti veloci sulla funzione SuccIntest (mentre la PrecedIntest, che esercita l’opposto mestiere è affidata al commento fai-da-te), che deve generare, successivamente, A, B, ..., Z poi AA, AB,... , AZ, BA,... , BZ,... , ZZ, AAA, AAB ecc., fino all’estrema testata che in Excel 2007 è XFD. Nota. Sottolineo che tale particolare sequenza (ereditata dal progenitore degli spreadsheet, Visicalc) non segue una progressione alfabetica, perché ad esempio AA precede Z nell’ordinamento alfabetico. Il procedimento spazzola con l’indice i che parte dall’ultimo carattere Car dell’argomento Intest percorrendoli tutti (finché i >= 1). Se Car non è “Z” se ne prende l’ASCII (CodAsc) con la proprietà ConvertToUtf32 applicata come si deve all’oggetto Car per subito tradurlo nel carattere successivo con l’opportuno ricorso a ConvertFromUtf32, infilandolo al posto dell’altro con un gioco di concatenamenti che sfruttano la proprietà Substring di una stringa. Di conseguenza, con Return Intest il carattere esaminato di quest’ultima passa da A a B, da B a C, ... , da Y a Z. Lascio all’esame autonomo del paziente lettore, l’impiego adottato della proprietà SubStringa. Cito solo il seguente caso, che sostituisce con un asterisco la lettera poszta nel mezzo del cammin di nostra stringa (indice i): NostraStr = NostraStr.Subtring(0, i - 1) & "*" & NostraStr.SubString(i) Se invece Car è “Z” esso viene tradotto in “A” con anteposizione di un’altra “A” ottenendo così “AA” a seguito di Z. Che cosa accade con due o più caratteri? Chi legge può sincerarsene con la riflessione e/o il debug sulla carta o nell’IDE Visual Studio. Con questi prolegomeni la routine dell’evento Click del pulsante di caricamento matrice Matr nella griglia non dovrebbe destare sorprese: Private Sub btnMostraMatrice_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnMostraMatrice.Click Dim DGridV As DataGridView = DataGridView1 Dim Intestaz = InizIntest & "A" ' Per test. Provare anche con "AA" o altro For i = 0 To DGridV.Columns.Count - 1 With DGridV.Columns(i) .HeaderText = Intestaz MessageBox.Show("Intestazione " & .Name & _ " è stata modificata in " & .HeaderText) Intestaz = SuccIntest(Intestaz) End With Next With DGridV .Rows.Add(Matr.GetUpperBound(0)) MessageBox.Show("Numero attuale di righe:" & .Rows.Count()) Dim Riga = InizRiga ' Per maggior chiarezza... For i = 0 To Matr.GetUpperBound(0) With .Rows(i) .HeaderCell.Value = Riga.ToString For j = 0 To Matr.GetUpperBound(1) .Cells(j).Value = DGridV.Columns(j).HeaderText & _ .HeaderCell.Value & ": " & Matr(i, j) Next End With Riga = Riga + 1 Next End With End Sub Lo scopo, ripeto, didattico è quello di esibire l’inserimento delle testate di colonna e riga della griglia nonché dei valori di Matr nelle sue celle. A tal fine la routine presenta messaggi opportuni nelle varie fasi. Va prima detto che – per semplicità, ma anche per motivi meglio chiariti più avanti – il progetto prevede un numero fisso, 18, di colonne della griglia, adeguato comunque a quello della Matr, mentre il numero delle righe della griglia stessa (che parte con 1, ricordate?) è eguagliato alla prima dimensione di Matr, con l’istruzione Rows.Add(Matr.GetUpperBound(0)) dell’oggetto DGridV ossia la nostra griglia DataGridView1. A questo punto è opportuno precisare che l’oggetto DataGridView si articola negli insiemi Columns e Rows le sue colonne e righe ovviamente. Le due proprietà dei membri Column e Row che più ci stanno a cuore sono HeaderText (intestazione di colonna) e rispettivamente HeaderCell (intestazione di riga, più che di “cella”). Nota. Non disturbi l’istruzione Riga = InizRiga. Come si vedrà InizRiga è un valore default = 1. Andava bene anche Riga = 1? Beh è una finezza per eventuali esperimenti alternativi... Analogamente, InizIntest è un default definito = “A”. Dopo aver posto la variabile Intestaz = InizIntest & "A" in modo da esordire con “AA” (un valore come un altro, modificabile in sede di debugging per ogni variante), il primo ciclo spazzola le Columns fissandone via via la proprietà HeaderText a Intestaz¸ quest’ultima predisposta alla successiva con Intestaz = SuccIntest(Intestaz) per il prossimo giro. Così l’utente assiste alla trasformazione delle varie “Column1”, “Column2”, ecc. le testate standard a project time, in “AA”, “AB”, “AC” e via di seguito. Il successivo loop che percorre le Rows inserendo sulla sinistra i numeri progressivi delle righe lo lascio per pigrizia all’esegesi autogestita. Preciso però che ogni oggetto Row è dotato di celle, insieme Cells e faccio notare che nel ciclo più interno di indice j che percorre le celle della riga di indice i il valore di ciascuno non è semplicemente il corrispettivo di Matr(i, j) bensì quest’ultimo con anteposizione del concatenamento fra l’header di colonna e di riga più “:”. Insomma la situazione è del tipo seguente: AA AB AC AD AE AF AG AH 1 AA1:1 AB1:2 AC1:3 AD1:4 AE1:5 AF1:6 ecc. ecc. 2 AA2:10 AB2:11 AC2:12 AD2:13 AE2:14 AF2:15 ecc. ecc. 3 AA3:19 AB3:20 AC3:21 ecc. ecc. ecc. ecc. ecc. L’ammaestramento che se ne ricava, già anticipato, è che con tale concatenamento si possono sempre ottenere le coordinate in stile Excel delle celle della griglia! (buono a sapersi, anche in futuro). Caricamento di un foglio di lavoro Prima di proseguire, esaminiamo le variabili definite a livello modulo (anziché Dim qui andrebbe bene anche Private o persino Public) e che pertanto si possono gestire conservando il loro valore nel corso dell’elaborazione: Dim ListaConChiaveRif As New Dictionary(Of String, String) Dim SpreadSh As String = Nothing ' Il file Excel 2007 da caricare Dim PrecedSpreadCaricato As String = Nothing Dim SpreadCaricato As Boolean = False Dim UltimaIntestFoglio As String Dim NumRigheFoglio As Integer ' Valori default Dim NumRigheDataGrid As Integer = 50 ' Un numero come un altro Dim InizRiga As Integer = 1 ' o altri valore, per varianti di debug Dim InizIntest As String = "A" ' oppure "ZY" e altre ' intestazioni usabili nei vari debug Di InizRiga e InizIntest si è già parlato. Un altro valore default è NumRigheFoglio che poi di fatto viene adattata a quelle del foglio caricato. Le altre variabili, diciamo così, comunitarie hanno un nome che ne lascia intuire la missione. Le esamineremo assieme al codice che le utilizza. Cominciamo con la dictionary ListaConChiaveRif già citata, nella quale viene stoccata la coppia riferimento / valore. Ecco la prima funzione che carica un Array VettSharedStrings, sulla base dell’argomento SharedStringPart del tipo relativo alla parte del pacco (Packaging) dell’SDK per Open XML: Function VettSharedStrings(ByVal SharedStringpart As _ Packaging.SharedStringTablePart) As Array 'Accesso alle SharedStrings di un file xls* Using StrRead = New StreamReader(SharedStringpart.GetStream()) Dim SharStrDoc = XDocument.Load(StrRead) Dim SharStrs = SharStrDoc...<t> Dim VettShStrs(0) As String ReDim VettShStrs(SharStrs.Count - 1) Dim i As Integer = 0 For Each SharStr In SharStrs VettShStrs(i) = SharStr.Value.ToString i += 1 Next 'Dim Vettshstrs As Array = SharStrs.ToArray StrRead.Close() Return VettShStrs End Using End Function Poiché tale Sub è identica a quella dell’articolo precedente per accesso a un intervallo di una cartella Excel 2007, rimando ad esso per i commenti. Allo stesso articolo rinvio per l’analisi della seguente routine volta a caricare col metodo Open del “pacco” il file definito dall’argomento SpreadSh (prima variabile a livello modulo, passata come si vedrà fra poco) e attingerne un foglio di lavoro, il Foglio1 per semplicità e che a sua volta richiama la precedente funzione, per creare tra l’altro la ListaConChiaveRif: Sub CaricaFoglioEListaConChiave() Try Using PackExcel = _ Packaging.SpreadsheetDocument.Open(SpreadSh, True) Dim SharedStrPart = PackExcel.WorkbookPart.SharedStringTablePart Dim VettSharStrings = VettSharedStrings(SharedStrPart) Dim PrimoFoglio = PackExcel.WorkbookPart.WorksheetParts.LastOrDefault ' N.B. LastOrDefault in realtà punta al PRIMO foglio di lavoro Using StrRead = New StreamReader(PrimoFoglio.GetStream()) Dim Foglio = XDocument.Load(StrRead) 'MessageBox.Show(Foglio.ToString) ' Servito per debug Dim DimensFoglio = Foglio...<dimension>.@ref Dim PrimaCella = "", i As Integer = 0 While DimensFoglio(i) <> ":" PrimaCella &= DimensFoglio(i) i += 1 End While Dim UltimaCella = DimensFoglio.Replace(PrimaCella & ":", Nothing) UltimaIntestFoglio = IntestCol(UltimaCella) NumRigheFoglio = UltimaCella.Replace(UltimaIntestFoglio, Nothing) Dim CelleValori = Foglio...<v> For Each CellaVal In CelleValori Dim Rifer = CellaVal.Parent.@r Dim Dato As String If CellaVal.Parent.Attributes.Last.Value = "s" Then Dato = VettSharStrings(CellaVal.Value) Else Dato = CellaVal.Value Dato = Dato.Replace("."c, ","c) 'Per l'edizione italiana End If ListaConChiaveRif.Add(Rifer, Dato) Next ' Servito per debug: ' MessageBox.Show("Numero elementi ListaConChiaveRif: " & _ ' ListaConChiaveRif.Count) Foglio = Nothing StrRead.Close() End Using PackExcel.Close() End Using Catch MessageBox.Show("File di tipo errato o corrotto", "Probabile errore", MessageBoxButtons.OK, MessageBoxIcon.Error) End Try End Sub E se voglio accedere a uno qualsiasi dei fogli di lavoro dell’archivio Excel (che potrebbe averne anche molti)? Il segreto di cui mi ero dimenticato è subito visto (e davvero semplice quanto inatteso): Dim MioFoglio = PackExcel.WorkbookPart.WorksheetParts(indFgl) con (indFgl) in luogo di .Last. Si tenga solo presente che indFoglio opera per così dire a ritroso, per cui in uno spreadsheet con tre fogli Foglio1, Foglio2 e Foglio3 l’indice indFgl va posto =3, 2 e 1, rispettivamente. (Insomma l’ampliamento a tutti i possibili fogli di lavoro è possibile, però con la pazienza del caso (tenendo presente la proprietà Count dell’insieme WorksheetParts e, magari, adottando una casella a discesa opportuna) Tornando alla versione precedente, rispetto ad essa, sono state apportate alcune modifiche e adattamenti al nuovo caso. La prima è la struttura Try / Catch / End Try introdotta per catturare gli errori nel caricamento, indicandone i più probabili. Ma la più sostanziosa è data dalle righe in grassetto, che per massima comodità di chi legge riporto nuovamente: Dim DimensFoglio = Foglio...<dimension>.@ref Dim PrimaCella = "", i As Integer = 0 While DimensFoglio(i) <> ":" PrimaCella &= DimensFoglio(i) i += 1 End While Dim UltimaCella = DimensFoglio.Replace(PrimaCella & ":", Nothing) UltimaIntestFoglio = IntestCol(UltimaCella) NumRigheFoglio = UltimaCella.Replace(UltimaIntestFoglio, Nothing) Vi si sfrutta la proprietà ref del nodo <dimension> del foglio .xml. Davvero utile e da non dimenticare, offre una stringa del tipo "A1:BC27” o “C2:AF123” e simili che indica l’intervallo compreso fra la prima e l’ultima cella non vuote. L’analisi del procedimento è lasciata al paziente lettore, che si renderà conto magari col tasto F8 che esso fornisce l’UltimaIntestFoglio e il NumRigheFoglio variabili a livello modulo (rivedere) di eloquente significato. Ad esempio nei due casi testé citati si avranno la coppia “BC” e “27” e, rispettivamente, “AF” e “123”. Nota. Come sarà più chiaro fra poco, la PrimaCella viene usata solo come passaggio intermedio, avendo preferito indicare in ogni casi A1 come prima cella della griglia. Caricamento del foglio Il cerchio si chiude con la centralissima routine CaricaFoglio che giustappunto carica il Foglio1 della cartella di lavoro Excel 2007 inserita nella casella di testo della nostra Form (Me), indicata con la label “File Excel da caricare” e denominata txtFileExcel: Sub CaricaFoglio() SpreadSh = Me.txtFileExcel.Text If SpreadSh = Nothing Then Exit Sub If SpreadSh = PrecedSpreadCaricato And Not SpreadCaricato Then MessageBox.Show("Spreadsheet " & SpreadSh & " già caricato!", "", _ MessageBoxButtons.OK, MessageBoxIcon.Information) Exit Sub Else ListaConChiaveRif.Clear() End If Dim DGridV As DataGridView = DataGridView1 DGridV.ScrollBars = ScrollBars.Both If ListaConChiaveRif.Count = 0 Then CaricaFoglioEListaConChiave() End If Dim Intestaz = InizIntest For i = 0 To DGridV.Columns.Count - 1 With DGridV.Columns(i) .HeaderText = Intestaz Intestaz = SuccIntest(Intestaz) End With Next With DGridV .Rows.Clear() ' Elimina righe create in precedenza (e.g. con Carica Matrice) .Rows.Add(NumRigheDataGrid) Dim Riga = InizRiga ' Per maggior chiarezza... For i = 0 To .Rows.Count - 1 With .Rows(i) .HeaderCell.Value = Riga.ToString Dim RifCella = "" For j = 0 To .Cells.Count - 1 RifCella = _ DGridV.Columns(j).HeaderText & .HeaderCell.Value If ListaConChiaveRif.ContainsKey(RifCella) Then .Cells(j).Value = ListaConChiaveRif(RifCella) End If Next End With Riga = Riga + 1 Next End With PrecedSpreadCaricato = Me.txtFileExcel.Text End Sub La precedente Sub viene utilizzata da vari pulsanti, a partire da quello sottostante la casella di testo, etichettato “Carica foglio Excel” e denominato btnCaricaFoglio: Private Sub btnCaricaFoglio_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCaricaFoglio.Click SpreadCaricato = False CaricaFoglio() End Sub Commenti stringati a CaricaFoglio. Esso esordisce prelevando in SpreadSh il contenuto della casella di testo già vista. Se vengono superati controlli vari, dati per comprensibili, viene richiamata la già vista CaricaFoglioEListaConChiave rivedendo la quale ci si rende conto che utilizzerà SpreadSh come si deve. A quel punto si innestano due cicli, il primo analogo a quello esaminati nella routine didattica relativa al caricamento della Matr nella griglia (notare che per maggior compattezza l’oggetto DataGridView1 viene stoccato in DGridV di tipo DataGridView). Anche qui l’iterazione For i = 0 To DGridV.Count – 1 ... Next genera le intestazioni relative alle 18 colonne prefissate nel progetto. Dopo il Clear delle righe attuali e l’aggiunta di un NumRigheDataGrid pari a quelle del Foglio1, parte il secondo loop For i = 0 To DGridV.Rows.Count ... Next in cui per ogni DGridV.Rows(i) viene posto un valore progressivo di Riga nella testata di riga (HeaderCell.Value). Ma lo snippet di maggior pregio è quello che sfrutta il trucco originale anticipato fin dall’apertura. Ripetiamolo: For j = 0 To DGridV.Cells.Count - 1 RifCella = _ DGridV.Columns(j).HeaderText & .HeaderCell.Value If ListaConChiaveRif.ContainsKey(RifCella) Then .Cells(j).Value = ListaConChiaveRif(RifCella) End If Next Come oramai tutti dovrebbero capire, in RifCella viene inserito il riferimento, come dire?, de facto della cella corrente per concatenamento dei due HeaderText e HeaderCell già “scoperti” nell’esempio didattico di caricamento Matr. Il ciclo in questione spazzola tutte le celle della griglia, inserendovi il valore dell’ormai celebre dictionary sulla chiave chiave RifCella però se e solo se la RifCella è presente in ListaConChiaveRif. Nota. Non si poteva procedere spazzolando invece la dictionary? Alternativa a dir poco problematica, perché l’accesso a una cella della griglia in base alle sue (pseudo) coordinate non è direttamente supportato. Si noti poi che la proprietà ContainsKey(RifCella) di non immediata reperibilità con l’Help on line è risultata qui davvero provvidenziale! Navigazione nella griglia Avviene con quattro pulsanti etichettati e denominati in modo che indica inequivocabilmente il mestiere di ciascuno, pertanto ne riporto il codice facendolo seguire da annotazioni essenziali. Prima un’osservazione circa il già menzionato limite di 18 colonne della griglia. Esso si è reso necessario, dopo diversi benchmark, non tanto per il numero massimo (500) supportato dal DataGridView, insufficiente solo con fogli davvero grandi, bensì perché il refresh di un numero elevato di celle crea rallentamenti notevoli. D’altra parte all’utente la vista parziale del foglio basta e avanza. Colonna avanti. Aggiunge una colonna a destra, eliminando quella di sinistra (conservando le 18 colonne prefissate) e aggiornando opportunamente i contenuti. Private Sub btnColonnaAvanti_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnColonnaAvanti.Click Dim DGridV = DataGridView1 With DGridV Dim Ncol = .Columns.Count If .Columns(Ncol - 1).HeaderText = UltimaIntestFoglio Then MessageBox.Show("Siamo all'ultima colonna del foglio") Exit Sub End If End With If DGridV.Rows.Count = 1 Then CaricaFoglio() Exit Sub End If Dim Nc = DGridV.Columns.Count With DGridV.Columns Dim NuovaIntest = SuccIntest(.Item(Nc - 1).HeaderText) .RemoveAt(0) Nc = .Count ' Dà il prec.te Nc - 1 (istr.ne messa per > chiarezza) .Add(.Item(Nc - 1).Name, NuovaIntest) ' Senza l'istr. prec. si poteva fare _ ' item(Nc - 2) End With With DGridV.Rows For i As Integer = 0 To .Count - 1 Dim RifCella = _ DGridV.Columns(Nc - 1).HeaderText & .Item(i).HeaderCell.Value If ListaConChiaveRif.ContainsKey(RifCella) Then .Item(i).Cells(0).Value = ListaConChiaveRif(RifCella) End If Next End With End Sub È la più sofisticata delle quattro routine d’evento, perché richiama CaricaFoglio solo al primo clic su tale Button, mentre in seguito aggiunge una colonna aggiornando solamente questa ed eliminando poi la prima. Colonna indietro. Aggiunge una colonna a sinistra, eliminando quella di destra (conservando le 18 colonne prefissate) e aggiornando i contenuti. Private Sub btnColonnaIndietro_Click(ByVal sender System.EventArgs) Handles btnColonnaIndietro.Click Dim DGridV = DataGridView1 If DGridV.Rows.Count = 1 Then CaricaFoglio() Exit Sub End If With DGridV.Columns If .Item(0).HeaderText = "A" Then Exit Sub SpreadSh = Nothing ' NON VA...???! InizIntest = PrecedIntest(.Item(0).HeaderText) SpreadCaricato = True CaricaFoglio() End With End Sub As Object, ByVal e As Stavolta, purtroppo, non sono riuscito a limitare l’aggiornamento in analogia al caso precedente, per cui è stato giocoforza l’applicazione brute-force di CaricaFoglio (peraltro senza rallentamenti sensibili). Questo perché, si direbbe, non c’è verso di inserire una colonna a sinistra. Se qualcuno vi riesce, faccia un fischio. Riga avanti e Riga indietro. Le più semplici da capire, entrambe col ricorso brutale a CaricaFoglio. Ergo le trascrivo senza commenti di sorta. Private Sub btnRigaAvanti_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnRigaAvanti.Click InizRiga += 1 If InizRiga + NumRigheDataGrid > NumRigheFoglio Then MessageBox.Show("Siamo all'ultima riga del foglio") End If SpreadCaricato = True CaricaFoglio() ' Soluzione brute-force... End Sub Private Sub btnRigaIndietro_Click(ByVal sender System.EventArgs) Handles btnRigaIndietro.Click If InizRiga > 1 Then InizRiga -= 1 SpreadCaricato = True CaricaFoglio() ' Soluzione brute-force... End Sub As Object, ByVal e As Altre procedure Somma celle selezionate. Queste routine derivano dalla scoperta della proprietà SelectedCells di chiara semantica. Ho così ottenuto abbastanza la funzione SommaCelleSelez, che totalizza i valori numerici di una selezione anche discontinua di celle della griglia. Il pulsante denominato btnSommaCelleSel la richiama segnalando al mondo il risultato (con indicazione delle celle via via esaminate, istruzioni tutoriali che si possono togliere). Private Function SommaCelleSelez() As Double 'Somma Celle selzionate Dim strCella As String = Nothing Dim S = 0.0 With DataGridView1 If .SelectedCells.Count = 0 Then Exit Function ' Ma di fatto c'è sempre una cella selez.ta For Each Cella In .SelectedCells Try strCella = Cella.Value.ToString Catch ex As Exception Continue For End Try MessageBox.Show(strCella) ' Servita per debug ' strCella = strCella.Replace(","c, "."c) _ ' Per l'edizione italiana? Ma di fatto NON serve If Double.TryParse(strCella, 0) Then S = S + CType(strCella, Double) End If Next Return S End With End Function Private Sub btnSommaCelleSel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSommaCelleSel.Click MsgBox("Somma celle numeriche Selezionate: " & SommaCelleSelez.ToString) End Sub Carica cella. Viene lanciata dal clic sul pulsante omonimo, e fa sì che nella griglia venga caricato il Foglio1 avente come cella d’angolo in alto a sinistra la cella di coordinate pari a quelle della casella Prima cella. Si richiede una digitazione esatta, non avendo previsto di intrappolare errori. La sua routine d’evento Click è interamente affidata all’autoanalisi (non freudiana!) dei più o meno esperti. Private Sub btnCaricaCella_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCaricaCella.Click Dim Rif = TxtPrimaCella.Text.ToUpper ' Solo per > chiarezza If Rif = "" Then Exit Sub If Integer.TryParse(Rif(0), 0) Then MessageBox.Show("Cifra all'inizio", "ERRORE!", MessageBoxButtons.OK, MessageBoxIcon.Error) TxtPrimaCella.Text = "" Exit Sub End If Dim Intest = IntestCol(Rif) Dim Riga = Rif.Replace(Intest, Nothing) If Not Integer.TryParse(Riga, 0) Then MessageBox.Show("L'indicazione di riga (" & Riga & ") deve contenere solo cifre", "ERRORE!", MessageBoxButtons.OK, MessageBoxIcon.Error) TxtPrimaCella.Text = "" Exit Sub End If ' MsgBox("Colonna: " & Intest & vbLf & "Riga: " & Riga) 'Servito pèer debug InizIntest = Intest InizRiga = Riga SpreadCaricato = True CaricaFoglio() End Sub Scelta del file, last but not least. Questo pulsante si affida a un controllo OpenFileDialog, equivalente alla finestra di Dialogo Apri di Windows, caratterizzato in più da un filtro che limita l’esplorazione ai soli file Excel 2007 (*) presenti sul PC ospite. Nota. Per l’esattezza il filtro *.xls? potrebbe catturare anche i particolari file binari .xlsb di fatto illeggibili. Facile il rimedio, chi ci tenesse, ma si tratta di bestie davvero rare... Il controllo OpenFileDialog lo do per noto, ricordando, ai più distratti, che va inserito nella Form definendone le proprietà essenziali, dopo di che risulta invisibile a run-time per cui il più delle volte conviene sfruttarlo con una routine, che ne impone i caratteri a run-time. Come quella da me usata, connessa all’ultimo bottone del progetto, per pigrizia Button1: Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click ' Attiva il controllo OpenFileDialog, sui soli file .xlsx / . xlsm With OpenFileDialog1 .Filter = "File Excel 2007(*.xls?)|*.xls?" .FileName = "" .ShowDialog() ' Mostra la finestra dei file nel ns. PC If .ShowDialog = Windows.Forms.DialogResult.OK Then Me.txtFileExcel.Text = .FileName End If End With End Su Si riveda la figura 1. Come dovrebbe essere evidente, sulla scelta del file e il clic sul pulsante Apri della finestra omonima il nome del file (pathname completo) viene riversato nella variabile txtFileExcel.Text e a quel punto l’utente può decidersi a lanciare tale file cliccando sul pulsante Carica foglio Excel. Qualcuno troverà farraginoso questo doppio passaggio. Non ha tutti i torti benché ciò consenta pure la scorciatoia della digitazione diretta del file nella casella. Eliminare quest’ultima e il suo pulsante btnCaricaFoglio e modificare sia la precedente routine che la routine CaricaFoglio ecco un non arduo esercizio che lascio ai più volonterosi. Bibliografia essenziale - Per Microsoft LINQ: A. Del Sole Programmare Microsoft LINQ – Ed. FAG http://www.fag.it/scheda.aspx?ID=28759 P. Pialorsi, M. Russo Programmare Microsoft LINQ – Ed. Mondadori (http://education.mondadori.it/ v. anche, sul sito devleap.com, http://introducinglinq.com/ ) - Per nozioni generali e articoli su OOXML nonché download dell’SDK 1.0 http://openxmldeveloper.org/articles/1970.aspx - Per esempi applicativi (gestiti in DOM + XPath in ambiente VBA) G. Giaccaglini Open XML Guida allo sviluppo – Ed. FAG http://www.fag.it/scheda.aspx?ID=28500
Documenti analoghi
in offerta vbj 68
sicuramente non è applicabile al progetto su cui sto lavorando al momento, che
consta di molte migliaia di righe di codice e che è frutto di numerosi anni-uomo di
lavoro. Di certo non è semplice ef...