Запретить пересчет определяемых пользователем функций, используемых в именованных диапазонах - PullRequest
2 голосов
/ 30 мая 2020

У меня есть три UDF:

Private Function IsInArray(stringToBeFound As Variant, arr As Variant) As Boolean
    IsInArray = Not IsError(Application.Match(stringToBeFound, arr, 0))
End Function

Эта функция проверяет, есть ли что-то в массиве.

Private Function data_to_array(data As Range)

Dim arrArray As Variant
Dim cell As Range
Dim z As Integer

z = 0

ReDim arrArray(1 To data.Cells.Count)
For Each cell In data
    z = z + 1
    arrArray(z) = cell.Value
Next cell

data_to_array = arrArray
End Function

Эта функция извлекает выбранные значения диапазона и помещает их в массив.

Private Function plot_vals(data As Variant, custom_arr As Variant)

Dim arrPlot As Variant
ReDim arrPlot(1 To UBound(data)) As Variant
Dim c As Integer
Dim cl As Integer

cl = 0

For c = 1 To UBound(data)
    cl = cl + 1
    If IsInArray(cl, custom_arr) Then
        arrPlot(cl) = data(cl)
    Else
        arrPlot(cl) = CVErr(xlErrNA)
    End If
Next c

plot_vals = arrPlot
End Function

Последний UDF выполняет цикл по массиву данных из второго UDF, и если индекс / позиция значения в data_array находится в custom_array, то возвращается его значение. В противном случае он помещает ошибку в массив.

Данные выглядят так: enter image description here

Эти функции используются в Excel следующим образом:

data_to_array(A1:A5) - этот UDF создает массив (от 1 до 5) со значениями из ячеек A1: A5.

plot_vals(data_to_array(A1:A5), {1,5}) - этот UDF создает массив (от 1 до 5) и использует второй аргумент для получения первого и пятые значения при внесении ошибок в другие индексы. Результатом является массив, например: {5,error,error,error,1}

Если бы я использовал функцию для данных выше, например: plot_vals(data_to_array(A1:A5), {1,2}), то результатом был бы массив {5,4,error,error,error}

Этот UDF plot_val используется в именованном диапазоне, и этот именованный диапазон используется для отображения значений на диаграмме. Данные хранятся в именованном диапазоне myData, а функция во втором именованном диапазоне используется следующим образом: plot_vals(myData,{1,5}).

Все работает, я могу отобразить это на диаграмме, все хорошо, но когда именованные диапазоны используются на диаграммах, каждый раз, когда я что-то меняю в своей книге, все функции пересчитываются, как ... 10 раз каждая вместо один раз . Это заставляет Excel замедляться / зависать, если эти функции используются много раз. Я попытался найти информацию о волатильности функций и о том, как ее отключить (по умолчанию она должна быть отключена?), Но, похоже, ничего не работает, и я не знаю, как это предотвратить. Я попытался воссоздать это в Excel, используя стандартные функции Excel в именованных диапазонах, но не могу найти правильную функцию, которая бы делала то, что хочу. UDF - это именно то, что мне нужно.

Когда эти именованные диапазоны НЕ используются в диаграммах, тогда ничего не происходит, но как только я использую именованный диапазон на диаграмме, он пересчитывает все даже при незначительном изменении рабочая тетрадь . Незначительное изменение значения - копирование / вставка / добавление строк и т. Д. c.

Как я могу этого избежать? Как пересчитать UDF только один раз?

ИЗМЕНИТЬ при дальнейших исследованиях :

Я пробовал потенциальные решения, предоставленные Чарльзом Вильямсом: https://fastexcel.wordpress.com/2011/11/25/writing-efficient-vba-udfs-part-7-udfs-calculated-multiple-times/

Его потенциальные решения ничего не меняют.

Я также пробовал использовать событие Sheet_Change, меняя вычисления на ручные, а затем обратно на автоматические c. Это помогает, но очищает буфер обмена (неприемлемо) и вызывает проблемы с другими моими макросами, поэтому это решение "no- go".

Также стоит отметить, что как только диаграмма удаляется, а UDF остаются в именованных диапазонах, все работает нормально и гладко. Но когда эти именованные диапазоны находятся в формулах серии диаграмм, все пересчитывается 100 раз.

Ответы [ 2 ]

1 голос
/ 30 мая 2020

Помимо попытки исправить основную c проблему, связанную с количеством вызовов ваших функций, вы можете частично решить проблему замедления, оптимизируя базовую c производительность:

  • Application.Match является относительно медленным, если только данные не ищутся на листе
  • Чтение диапазона в массив происходит медленнее, чем чтение всего диапазона сразу с использованием его .Value (при условии, что диапазон - это одна область)

Итак:

Sub PerfTester()
    Const ARR_SZ As Long = 10

    Dim arr(1 To ARR_SZ), i, n, t, v, m

    'populate a test array
    For i = 1 To ARR_SZ
        arr(i) = i
    Next i

    t = Timer
    For n = 1 To 100000
        v = Round(Rnd * ARR_SZ, 0)
        m = IsInArray(v, arr)  'using match
    Next n
    Debug.Print Timer - t   '~ 1.7 sec

    t = Timer
    For n = 1 To 100000
        v = Round(Rnd * ARR_SZ, 0)
        m = IsInArray2(v, arr) 'using a loop
    Next n
    Debug.Print Timer - t  '~0.11 sec

    t = Timer
    For n = 1 To 100000
        v = data_to_array(Range("A1:A50"))  'using cell-by-cell
    Next n
    Debug.Print Timer - t   '~ 11.5 sec

    t = Timer
    For n = 1 To 100000
        v = data_to_array2(Range("A1:A50"))  'using single read from range
    Next n
    Debug.Print Timer - t  '~ 2.8 sec


End Sub


Private Function IsInArray(stringToBeFound As Variant, arr As Variant) As Boolean
    IsInArray = Not IsError(Application.Match(stringToBeFound, arr, 0))
End Function

Private Function IsInArray2(stringToBeFound As Variant, arr As Variant) As Boolean
    Dim i
    For i = LBound(arr) To UBound(arr)
        If arr(i) = stringToBeFound Then
            IsInArray2 = True
            Exit For
        End If
    Next i
End Function

Private Function data_to_array(data As Range)
    Dim arrArray As Variant, cell As Range, z As Integer
    z = 0
    ReDim arrArray(1 To data.Cells.Count)
    For Each cell In data
        z = z + 1
        arrArray(z) = cell.Value
    Next cell
    data_to_array = arrArray
End Function

Private Function data_to_array2(data As Range)
    Dim arrArray As Variant, cell As Range, z As Long, v
    v = data.Value
    ReDim arrArray(1 To UBound(v, 1))
    For z = 1 To UBound(v, 1)
        arrArray = v(z, 1)
    Next z
    data_to_array2 = arrArray
End Function
0 голосов
/ 30 мая 2020

Вы должны иметь возможность предотвратить ненужные дополнительные вычисления, включив

Application.EnableEvent = False
Application.Calculation = xlManual 

в начале ваших функций и

Application.EnableEvents = True
Application.Calculation = xlAutomatic

в конце ваших функций. Это предотвращает попытки обновления и пересчета вашей электронной таблицы всякий раз, когда вы вносите незначительные изменения. Если вы считаете это необходимым, вы можете добавить

Worksheet.Calculate

в какой-то момент своего кода, чтобы принудительно выполнить пересчет текущего листа.

...