Domanda

Diciamo che creo un Sub (non una funzione) la cui missione nella vita è quella di prendere la cellula attiva (cioè Selezione) e impostare una cella adiacente su un valore. Funziona bene.

Quando provi a convertire quel Sottotitolo in una Funzione e provi a valutarlo dal foglio di calcolo (cioè impostando la sua formula su " = MyFunction () ") Excel abbaia al fatto che tu stanno cercando di influenzare il valore della cella non attiva e di forzare semplicemente la funzione a restituire #VALORE senza toccare la cella adiacente.

È possibile disattivare questo comportamento protettivo? In caso contrario, qual è un buon modo per aggirarlo? Sto cercando qualcosa che uno sviluppatore competente possa realizzare in un periodo di 1-2 settimane, se possibile.

Saluti, Alan.

Nota: sto usando il 2002, quindi preferirei una soluzione che funzionasse per quella versione. Detto questo, se le versioni future lo renderanno notevolmente più semplice, mi piacerebbe saperlo anche io.

È stato utile?

Soluzione

Non può essere fatto, il che ha senso perché:

  • Quando viene chiamata una funzione del foglio di lavoro, la cella contenente la funzione non è necessariamente la cella attiva. Quindi non puoi trovare la cella adiacente in modo affidabile.

  • Quando Excel ricalcola un foglio di lavoro, deve mantenere le dipendenze tra le celle. Quindi non può consentire alle funzioni del foglio di lavoro di modificare arbitrariamente altre celle.

Il meglio che puoi fare è uno di:

  • Gestisce l'evento SheetChange. Se una cella contenente la tua funzione sta cambiando, modifica la cella adiacente.

  • Inserisci una funzione del foglio di lavoro nella cella adiacente per restituire il valore desiderato.

Aggiorna

Per quanto riguarda il commento: " Vorrei che questa funzione funzionasse su un foglio di calcolo 'vuoto', quindi non posso davvero fare affidamento sull'evento SelectionChange di fogli di calcolo che potrebbero non esistere ancora, ma dovrò chiama questa funzione " ;:

  • Puoi inserire la tua funzione in un componente aggiuntivo XLA? Quindi il componente aggiuntivo XLA può gestire l'evento Application SheetChange (*) per tutte le cartelle di lavoro aperte in quell'istanza di Excel?

Per quanto riguarda il commento: " Tuttavia, se mantieni Excel in CalculationMode = xlManual e inserisci solo i valori, dovresti andare benissimo "

  • Anche quando CalculationMode è xlManual, Excel deve mantenere un albero delle dipendenze di riferimenti tra celle in modo che possa calcolare nell'ordine giusto. E se una delle funzioni può aggiornare una cella arbitraria, ciò rovinerà l'ordine. Questo è presumibilmente il motivo per cui Excel impone questa restrizione.

(*) Originariamente ho scritto SelectionChange sopra, corretto ora - ovviamente l'evento corretto è SheetChange per l'oggetto Workbook o Application o Change per l'oggetto Worksheet.

Aggiornamento 2 Alcune osservazioni su Post di AlanR che descrivono come 'kinda' per farlo funzionare usando un timer:

  • Non è chiaro come la funzione timer (" Woohoo ") saprà quali celle aggiornare. Non hai informazioni che indicano quale cella contiene la formula che ha attivato il timer.

  • Se la formula esiste in più di una cella (nella stessa o in diverse cartelle di lavoro), l'UDF verrà chiamato più volte durante un ricalcolo, sovrascrivendo il timerId. Di conseguenza, non riuscirai a distruggere il timer in modo affidabile e perderai risorse di Windows.

Altri suggerimenti

Secondo Come creare funzioni Excel personalizzate definite dall'utente :

  

Limitazioni di UDF

     
      
  • Impossibile inserire un valore in una cella diversa dalla cella (o intervallo) contenente   la formula. In altre parole, lo sono le UDF   intendeva essere usato come " formule " ;, no   necessariamente " macro " ;.
  •   

Quindi, sembra che non possa essere fatto.

Sto usando Excel 2007 e non funziona. Excel menziona che crea un riferimento circolare. Non penso che tu possa alterare altre celle da una funzione, solo restituire un valore.

È una specie di programmazione funzionale, senza effetti collaterali. Se potessi semplicemente modificare altre celle all'interno di una funzione (utilizzata da un foglio di lavoro), Excel non ha modo di conoscere l'ordine e cosa ricalcolare se una cella cambia.

Questo articolo contiene anche molto di informazioni su come Excel esegue il ricalcolo. Ma non afferma mai che le altre celle sono congelate.

Non so cosa stai cercando di fare, ma perché non inserisci semplicemente un'altra funzione nella cella adiacente, che accetta la prima cella come parametro?

Esempio:

Public Function Bar(r As Range) As Integer
  If r.Value = 2 Then
    Bar = 0
  Else
    Bar = 128
  End If
End Function

Grazie a tutti per aver risposto. È possibile farlo! Tipo. Dico "kinda" perché tecnicamente parlando la "funzione" non influisce sulle cellule circostanti. In pratica, tuttavia, nessun utente potrebbe distinguere.

Il trucco è usare un'API Win32 per avviare un timer, e non appena si spegne fai quello che vuoi su qualunque cella e spegni il timer.

Ora non sono un esperto di come funziona il threading COM (anche se so che VBA è Single Apartment Threaded), ma fai attenzione a che il tuo timer scappi con il tuo processo Excel e lo blocchi. Questo non è davvero qualcosa che suggerirei come soluzione per ogni altro foglio di calcolo.

Crea un modulo con questi contenuti:

Option Explicit

Declare Function SetTimer Lib "user32" (ByVal HWnd As Long, _
  ByVal IDEvent As Long, ByVal mSec As Long, _
  ByVal CallFunc As Long) As Long

Declare Function KillTimer Lib "user32" (ByVal HWnd As Long, _
  ByVal timerId As Long) As Long

Private timerId As Long

Private wb As Workbook
Private rangeName As String
Private blnFinished As Boolean

Public Sub RunTimer()

    timerId = SetTimer(0, 0, 10, AddressOf Woohoo)


End Sub


Public Sub Woohoo()

    Dim i As Integer

'    For i = 0 To ThisWorkbook.Names.Count - 1
'        ThisWorkbook.Names(i).Delete
'    Next

     ThisWorkbook.Worksheets("Sheet1").Range("D8").Value = "Woohoo"

     KillTimer 0, timerId

End Sub

Anche se non puoi farlo in Excel, è possibile in Resolver One (anche se è ancora piuttosto cosa strana da fare).

È un foglio di calcolo che ti consente di definire funzioni personalizzate in Python che puoi chiamare da una formula di cella nella griglia.

Come esempio di ciò che stai chiedendo, potresti voler definire una funzione safeDivide che (invece di sollevare un ZeroDivisionError) ti ha detto del problema colorando la cella del denominatore e mettendo un messaggio di errore accanto esso. Puoi definirlo in questo modo:

def safeDivide(numerator, cellRange):
    if not isinstance(cellRange, CellRange):
        raise ValueError('denominator must be a cell range')
    denominator = cellRange.Value
    if denominator == 0:
        cell = cellRange.TopLeft
        cell.BackColor = Color.Red
        cell.Offset(1, 0).Value = 'Tried to divide by zero'
        return 0
    return numerator / denominator

C'è una grinza in più: le funzioni che ottengono il passaggio delle celle vengono semplicemente superate il valore della cella, quindi per ovviare a ciò insistiamo sul fatto che venga passato un intervallo di celle di una cella per il denominatore.

Se stai provando a fare cose insolite con fogli di calcolo che non si adattano perfettamente a Excel, o sei interessato a utilizzare la potenza di Python per lavorare con i tuoi dati di foglio di calcolo, vale la pena dare un'occhiata a Resolver One.

Ecco una semplice soluzione alternativa VBA che funziona. Per questo esempio, apri una nuova cartella di lavoro di Excel e copia il seguente codice nell'area del codice per Sheet1 (non ThisWorkbook o un VBA Module). Quindi vai in <=> e inserisci qualcosa in una delle celle in alto a sinistra del foglio di lavoro. Se digiti un numero e premi Invio, la cella a destra verrà aggiornata con 4 volte il numero e lo sfondo della cella diventerà azzurro. Qualsiasi altro valore causa l'eliminazione della cella successiva. Ecco il codice:

Dim busy As Boolean
Private Sub Worksheet_Change(ByVal Target As Range)
  If busy Then Exit Sub
  busy = True
  If Target.Row <= 10 And Target.Column <= 10 Then
    With Target.Offset(0, 1)
      If IsNumeric(Target) Then
        .Value = Target * 4
        .Interior.Color = RGB(212, 212, 255)
      Else
        .Value = Empty
        .Interior.ColorIndex = xlColorIndexNone
      End If
    End With
  End If
  busy = False
End Sub

La subroutine acquisisce tutti gli eventi di cambio cella nel foglio. Se la riga e la colonna sono entrambe & Lt; = 10, la cella a destra viene impostata su 4 volte la cella modificata se il valore è numerico; altrimenti la cella a destra viene cancellata.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top