Entra

View Full Version : [VBA Excel] Codice corretto che talvolta dà errore


superciccio14
13-12-2009, 21:34
Ciao, io sto lavorando con un macro di Excel. Sintatticamente è corretta e difatti funziona, tuttavia talvolta far girare il codice mi dà errore. Da che cosa può dipendere?

Ad esempio ecco il codice snellito al minimo che però ha il medesimo comportamento del codice completo:

Sub test()

Sheets("foglio").Range("A5").Select

End Sub


Questo codice a seconda dei casi mi scatena un errore, come mai? Che dipenda dalla selezione attiva al momento del richiamo della macro?

Ciao!!!

MarcoGG
14-12-2009, 14:34
Public Sub SelezionaFoglio(nomeFoglio As String, indirizzoRange As String)

Sheets(nomeFoglio).Activate
Range(indirizzoRange).Select

End Sub

In un Modulo o in una UserForm. ;)

superciccio14
14-12-2009, 17:08
Ciao Marco, grazie per la risposta, ma sai qual è la cosa curiosa? E' che se spezzo il codice

Sub test()
Sheets("foglio").Select
Range("A5").Select
end sub

sembra che invece l'errora non mi si presenti più...

come mai?

Ciao!

MarcoGG
14-12-2009, 17:28
Activate ( su Foglio ) + Select ( su Range ) è il metodo espressamente consigliato dalla Guida VBA di Excel, perciò mi atterrei a quello. Poi, ci sono casi in cui il Select su Range fallisce, casi in cui sembra funzionare, ma alla prima modifica ti pianta in asso.
Ha certamente a che fare con questioni di Focus...
Personalmente non mi sono mai preso la briga di studiare a fondo la casistica, dal momento che il 99% dei .Select così come escono dal registratore Macro si possono tranquillamente cestinare, e inoltre tutte le operazioni possibili in VBA con Celle e Range, si possono eseguire ( e anzi, si DEVONO eseguire ) senza previa selezione degli elementi interessati.

superciccio14
14-12-2009, 18:00
Cosa intendi per "questioni di Focus"?

Se ho capito bene mi consigli di non usare una selezione ma di effettuare le operazioni direttamente. Ad esempio dovessi usare un filtro sarbbe melgio fare:


Sheets("foglio").Range("A5").AutoFilter Field:=13, Criteria1:="paperino"

pittosto che:

Sheets("foglio").Activate
Range("A5").Select
Selection.AutoFilter Field:=13, Criteria1:="paperino"

Però non trovi che, in generale, VBA presenti una sintassi poco robusta?

MarcoGG
14-12-2009, 19:44
Cosa intendi per "questioni di Focus"?

Se ho capito bene mi consigli di non usare una selezione ma di effettuare le operazioni direttamente. Ad esempio dovessi usare un filtro sarbbe melgio fare:


Sheets("foglio").Range("A5").AutoFilter Field:=13, Criteria1:="paperino"

pittosto che:

Sheets("foglio").Activate
Range("A5").Select
Selection.AutoFilter Field:=13, Criteria1:="paperino"

Però non trovi che, in generale, VBA presenti una sintassi poco robusta?

- Questioni di Focus : se da un pulsante che sta su Foglio1 fai eseguire direttamente operazioni su Foglio2 che includono Select, ci sono alte probabilità che falliscano. Una routine di selezione locale non deve mai riferirsi ad elementi su altri fogli. Se proprio non si può fare a meno di selezionare, meglio farlo fare a routine che "vedono dall'alto" tutti gli Sheets, ossia da un Modulo, o da una UserForm. Ripeto, non ho una spiegazione esatta su tutti i casi in cui .Select vada in errore, perchè non faccio MAI uso di .Select, quindi elimino il problema alla radice.

- Esatto. Tra l'altro la prima AutoFilter è più corretta, compatta, e leggibile della seconda, perciò perchè mai usare .Select e poi Selection.AutoFilter ?
Quello è tipico del codice generato dal macro recorder, che è grezzo, prolisso, e anche abbastanza "stupido".

- Più che sintassi, direi che il vero problema è l'IDE. L'editor VBA di Office non brilla certo per robustezza ed evidenzia solo una parte degli errori in fase di scrittura. Perfino alcuni errori di sintassi vengono fuori solo a runtime, e non è raro vedere l'intellisense che non parte dopo un punto "." .
Per me non è mai stato un problema, dato che mi sono avvicinato a VBA conoscendo già VB6, ma per altri posso capire che sia una gran bella scocciatura.

superciccio14
15-12-2009, 18:32
Ho capito!

Beh che brutto però che sintassi apparentemente equivalenti (e probabilmente logicamente corrette) scatenino errori...ti chiedo ancora una cosa. Se dovessi fare un filtro congiunto (cioè filtrare la colonna A per un criterio e la B per un altro), come posso fare?

Io ho provato così

Sheets("foglio").Range("A1:C1").AutoFilter Field:=1, Criteria1:="paperino"
Sheets("foglio").Range("A1:C1").AutoFilter Field:=2, Criteria1:="pippo"

però mi genera un errore. Se invece "seleziono" il tutto sembra funzionare (per poi darmi però i problemi che mi han indotto ad aprire questo topic):


Selection.AutoFilter Field:=1, Criteria1:="paperino"
Selection.AutoFilter Field:=2, Criteria1:="pippo"

eppure le due logiche mi sembrano identiche...:muro:

MarcoGG
16-12-2009, 09:54
- VBA non è colpevole in questo. Ripeto, semmai sono i limiti del VBE di Office... Il fatto che ci siano errori logici non individuati dall'editor è cosa comune a tutti i linguaggi del mondo. Se crei un array di 5 elementi e poi vai a leggere il sesto, sintatticamente è ok, ma poi in debug va in errore, e in questo la stragrande maggioranza degli Ide non ti aiuta...

- No, non funziona così. Se hai una tabella dati su foglio Excel che va dalla colonna A alla C per N righe, con l'AutoFilter devi avere una riga di intestazione campi all'inizio, in questo caso la riga "A1:C1".
Inoltre è inutile specificare tutto il range, basta solo la prima cella in alto a sinistra della tabella dati, in questo caso "A1" :

Sheets("Foglio1").Range("A1").AutoFilter Field:=1, Criteria1:="paperino"
Sheets("Foglio1").Range("A1").AutoFilter Field:=2, Criteria1:="pippo"

Altro ennesimo esempio in cui .Select e Selection non servono.

superciccio14
16-12-2009, 18:11
Ciao, in primis ti ringrazio nuovamente per l'aiuto.

ho testato il tuo codice e se lo lancio con selezionato foglio 1 funziona bene, se lo lancio con selezionato un altro foglio invece mi restituisce dei valori completamente sballati. Come mai secondo te?

L'esempio sull'array è stato esaustivo, tuttavia secondo me lo si potrebbe annoverare tra gli errori "logici".

Per fare un parallelo è coome se in Java facessi

Persona p;
p = new("Nome","Cognome");

oppure

Persona p = new("Nome","Cognome");


ed in un caso andasse bene mentre nell'altro no.


PS.
Esiste una guida esustiva e bene fatta su Visual Basic for Application (quello che io abbrevio VBA :) )?

PPS:
Sulla guida di excel ho trovato dei "metodi" Select applicati ad oggetti Sheets. Che macello...

MarcoGG
16-12-2009, 19:56
ho testato il tuo codice e se lo lancio con selezionato foglio 1 funziona bene, se lo lancio con selezionato un altro foglio invece mi restituisce dei valori completamente sballati. Come mai secondo te?


Non mi risulta. Quel codice fa riferimento al foglio e alla cella, perciò non può dare adito ad ambiguità, e da me infatti funziona perfettamente, indipendentemente da dove venga lanciato...


L'esempio sull'array è stato esaustivo, tuttavia secondo me lo si potrebbe annoverare tra gli errori "logici".


Non capisco il "tuttavia". Io infatti ho parlato di errori non-sintattici.


PS.
Esiste una guida esustiva e bene fatta su Visual Basic for Application (quello che io abbrevio VBA :) )?


Non credo. VB For Application non ha senso se lo si studia al di fuori dell'application "di cui è For". Con VBA non puoi certo compilare un .exe o una .dll come in VB6.
Quindi puoi trovare manuali su Excel + VBA, Access + VBA, ecc., ma alla fine li definirei in molti modi, eccetto "esaustivi".


PPS:
Sulla guida di excel ho trovato dei "metodi" Select applicati ad oggetti Sheets. Che macello...


A dire il vero quello è uno dei pochi casi in cui capisco il .Select. Se devo selezionare semplicemente un Foglio per portarlo all'attenzione dell'utente, ci può stare.
In generale la mia tendenza a non usare e a sconsigliare i .Select e i Selection è mia personale. Potrebbero esserci manuali onesti che invece illustrano come usarli regolarmente. Secondo la mia esperienza con Excel e VBA la trovo una pratica errata.

superciccio14
16-12-2009, 23:09
Non mi risulta. Quel codice fa riferimento al foglio e alla cella, perciò non può dare adito ad ambiguità, e da me infatti funziona perfettamente, indipendentemente da dove venga lanciato...

Difatti hai ragione, avevo un piccolo baco sul prosieguo del codice che m iinficiava i riusltati. (Scorie della mia vecchia impostazione col .Select :D )



Non capisco il "tuttavia". Io infatti ho parlato di errori non-sintattici.


Io dico che se il metodo .Select non è definito per l'oggetto Sheet dovrebbe darmi un errore subito in fase di scrittura (come fa ad esempio Eclipse per Java). Invece in questo caso, come ti dicevo nel primo post, a volte la macro girava a volte mi dava errore.



Non credo. VB For Application non ha senso se lo si studia al di fuori dell'application "di cui è For". Con VBA non puoi certo compilare un .exe o una .dll come in VB6.
Quindi puoi trovare manuali su Excel + VBA, Access + VBA, ecc., ma alla fine li definirei in molti modi, eccetto "esaustivi".


Infatti, la cosa frustrante è quella. :muro: Che peccato. Comunque io non ne so nulla nemmeno di VB, dici che mi potrebbe aiutare leggere una guida per comprendere meglio il VBA?



A dire il vero quello è uno dei pochi casi in cui capisco il .Select. Se devo selezionare semplicemente un Foglio per portarlo all'attenzione dell'utente, ci può stare.
In generale la mia tendenza a non usare e a sconsigliare i .Select e i Selection è mia personale. Potrebbero esserci manuali onesti che invece illustrano come usarli regolarmente. Secondo la mia esperienza con Excel e VBA la trovo una pratica errata.

A quel punto potresti anche solo fare

Sheets("foglio che voglio in primo piano").Activate
Range("A1").Select

e salvi capra e cavoli :D :D :D


Grazie Ancora!

MarcoGG
17-12-2009, 09:12
Io dico che se il metodo .Select non è definito per l'oggetto Sheet dovrebbe darmi un errore subito in fase di scrittura (come fa ad esempio Eclipse per Java). Invece in questo caso, come ti dicevo nel primo post, a volte la macro girava a volte mi dava errore.



A quel punto potresti anche solo fare

Sheets("foglio che voglio in primo piano").Activate
Range("A1").Select


e salvi capra e cavoli :D :D :D


No, non è per quel motivo. Per questo dico che uno dei pochi casi in cui condivido il .Select è la selezione semplice del Foglio. In che altro modo potresti selezionare un Foglio se non con .Select o .Activate ?

In realtà .Select è disponibile su Sheets(), basta che cerchi "Select" nel visualizzatore oggetti del VBE, e ottieni l'elenco di tutte le classi che lo espongono.
Se crei un pulsante su Foglio1 e associ questo codice :
Sheets("Foglio2").Select
infatti funziona.
I problemi veri insorgono quando si fanno selezioni con .Select e Selection di Celle, Range, Grafici, ecc., da un foglio all'altro, mentre bisognerebbe creare, come ti ho suggerito in precedenza, delle routine pubbliche, a livello di Modulo o di UserForm.


Infatti, la cosa frustrante è quella. :muro: Che peccato. Comunque io non ne so nulla nemmeno di VB, dici che mi potrebbe aiutare leggere una guida per comprendere meglio il VBA?


Se conosci bene VB6, hai già praticamente in tasca il VBA ( sempre con le dovute limitazioni di quest'ultimo, sia chiaro ), ti rimane solo di apprendere il modello ad oggetti specifico dell'applicazione Office che vuoi usare.

superciccio14
17-12-2009, 12:36
Ok, capito :D

Ti chiedo un'ultima cosa. Se io dovessi contare le celle filtrate secondo un certo criterio, lo farei con questo codice:

Range("A2:A1500").SpecialCells(xlCellTypeVisible).Count

(La tabella ha 1500 righe e la prima è di intestazione)

ma come faccio a gestire l'eccezione in cui ho 0 celle selezionate? VBE mi darebbe un errore...:mc:

Ciao!

MarcoGG
17-12-2009, 14:14
Dim cnt As Integer
On Error Resume Next
cnt = Range("A2:A1500").SpecialCells(xlCellTypeVisible).Count
MsgBox cnt
;)

superciccio14
17-12-2009, 14:24
Ahh ok, fondamentalmente .Count riconosceva lo 0 solo che mi bloccava l'esecuzione perché lo riconosceva come un errore a runtime!

Cosi facendo gli si dice di ignorare l'errore salvare comunque lo 0.

Corretto?

Allora mi dichiaro

On Error Resume Next

in testa cosi mi vale per tutto il codice. Grandissimo!

Grazie. Ciao!

:D

superciccio14
17-12-2009, 14:44
Però così facendo mi ignora eventuali altri errori...:(

La soluzione artigianale sarebbe quella di usare i subtotali...

MarcoGG
17-12-2009, 15:07
Però così facendo mi ignora eventuali altri errori...:(

La soluzione artigianale sarebbe quella di usare i subtotali...

Ma puoi sempre metterla in un Function esterna, in tal modo il Resume Next vale solo nell'ambito della Function.
Se vuoi imparare VBA, accetta un consiglio : cerca da subito di parametrizzare le cose che fai creando Function e Sub generiche e riutilizzabili, non fare tutto quanto in un unico flusso di codice... ;)

Altra alternativa, comunque, è di fare un On Error Goto...

superciccio14
17-12-2009, 15:39
Cioè tu dici ad esempio

Function Pippo(s As Sheets, d As Sheet)
Dim cnt As Integer
On Error Resume Next
cnt = Sheets(s).Range("A2:A1500").SpecialCells(xlCellTypeVisible).Count
Sheets(d).Range(A1).Value = cnt;
end Function

Così costringo il resume next nella funzione, ma poi come la richiamo nel "main"? non so a che oggetto applicarlo...

(conta che io sto lavorando in un unico modulo ed ho messo la funzione in testa al codice, prima di sub Modulo1())

Pippo("foglio1","foglio2")

Mi manca un passaggio (scusa se faccio domande banali)

MarcoGG
17-12-2009, 17:44
Cioè tu dici ad esempio

Function Pippo(s As Sheets, d As Sheet)
Dim cnt As Integer
On Error Resume Next
cnt = Sheets(s).Range("A2:A1500").SpecialCells(xlCellTypeVisible).Count
Sheets(d).Range(A1).Value = cnt;
end Function

Così costringo il resume next nella funzione, ma poi come la richiamo nel "main"? non so a che oggetto applicarlo...

(conta che io sto lavorando in un unico modulo ed ho messo la funzione in testa al codice, prima di sub Modulo1())

Pippo("foglio1","foglio2")

Mi manca un passaggio (scusa se faccio domande banali)

No, intendo questo :

Public Function ContaRigheFiltrate(foglio As String, intervallo As String)

Dim cnt As Integer
On Error Resume Next
cnt = Sheets(foglio).Range(intervallo).SpecialCells(xlCellTypeVisible).Count
ContaRigheFiltrate = cnt

End Function

che può essere messa in un Modulo, ad esempio.
E viene usata, ad esempio così :

MsgBox ContaRigheFiltrate("Foglio1", "A2:A1500")

Non so cosa intendi per "sub Modulo1()". Non esistono intestazioni di modulo in VBA. Un Modulo VBA dovrebbe contenere solo dichiarazioni di variabili e metodi ( Sub e Function ).

superciccio14
18-12-2009, 09:37
Fantastico! Ho capito, funziona! :D :D :D

Posso fare ancora un'ultima domanda (spero): ma se volessi fare invece una funzione che mi somma i valori delle celle filtrate? C'è il solito problema dello 0 che ho incontrato per count?

Ciao

io ho trovato quest'esempio http://ennius.altervista.org/vba/vba59.php ma non so...non usa SpecialCells.

Io pensavo:

Sub Modulo2()
Dim cl As Range
For Each cl In Sheets("foglio").Range("A2:A1500").SpecialCells(xlCellTypeVisible)
'vado a dargli un offset per individuare la colonna col valore
tot = tot + cl.Offset(0, 3).Value
Next
MsgBox (tot)
End Sub

MarcoGG
18-12-2009, 12:06
Ma scusa, fai un For Each diretto sull'intervallo valori, senza Offset :

Public Function SommaValoriFiltrati(foglio As String, intervalloValori As String)

Dim cnt As Integer
Dim R As Range
On Error Resume Next
For Each R In Sheets(foglio).Range(intervalloValori).SpecialCells(xlCellTypeVisible)
cnt = cnt + R.Text
Next R
SommaValoriFiltrati = cnt

End Function

e la usi così :

MsgBox SommaValoriFiltrati("Foglio1", "C2:C1500")
;)

superciccio14
20-12-2009, 22:42
Ciao, bene! Ho risolto. Ok ora dimmi dove posso farti recapitare un tir di prodotti DOP per sdebitarmi

:sofico: :sofico: :sofico:

(grazie mille!!!)