Заставить функции Excel влиять на «другие» ячейки

StackOverflow https://stackoverflow.com/questions/201261

  •  03-07-2019
  •  | 
  •  

Вопрос

Допустим, я создаю Sub (не функцию), жизненная миссия которой — захватить активную ячейку (т.е.Выбор) и присвойте соседней ячейке какое-то значение.Это работает нормально.

Когда вы пытаетесь преобразовать этот Sub в функцию и попытаться оценить его из электронной таблицы (т.установив для формулы значение «=MyFunction()») Excel будет лаять на тот факт, что вы пытаетесь повлиять на значение неактивной ячейки, и просто заставит функцию вернуть #VALUE, не касаясь соседней ячейки.

Можно ли отключить это защитное поведение?Если нет, то какой хороший способ обойти это?Я ищу что-то, что компетентный разработчик мог бы сделать за 1-2 недели, если это возможно.

С уважением, Алан.

Примечание:Я использую 2002, поэтому я бы предпочел решение, которое подойдет для этой версии.При этом, если будущие версии сделают это значительно проще, я тоже хотел бы об этом знать.

Это было полезно?

Решение

Это невозможно сделать, и это имеет смысл, потому что:

  • Когда вызывается функция рабочего листа, ячейка, содержащая функцию, не обязательно является активной ячейкой.Таким образом, вы не можете надежно найти соседнюю ячейку.

  • Когда Excel пересчитывает лист, ему необходимо поддерживать зависимости между ячейками.Поэтому он не может позволить функциям рабочего листа произвольно изменять другие ячейки.

Лучшее, что вы можете сделать, это одно из:

  • Обработка события SheetChange.Если ячейка, содержащая вашу функцию, меняется, измените соседнюю ячейку.

  • Поместите функцию рабочего листа в соседнюю ячейку, чтобы вернуть нужное значение.

Обновлять

По поводу комментария:«Мне бы хотелось, чтобы эта функция работала с «пустой» электронной таблицей, поэтому я не могу полностью полагаться на событие SelectionChange электронных таблиц, которые, возможно, еще не существуют, но должны будут вызвать эту функцию»:

  • Можете ли вы поместить свою функцию в надстройку XLA?Тогда ваша надстройка XLA может обрабатывать событие Application SheetChange (*) для всех книг, открытых в этом экземпляре Excel?

По поводу комментария:«Тем не менее, если вы сохраните Excel в CalculationMode = xlManual и заполните только значения, все будет в порядке»

  • Даже если CalculationMode имеет значение xlManual, Excel необходимо поддерживать дерево зависимостей ссылок между ячейками, чтобы выполнять вычисления в правильном порядке.А если одна из функций сможет обновить произвольную ячейку, это испортит порядок.Вероятно, именно поэтому Excel накладывает это ограничение.

(*) Первоначально я написал SelectionChange выше, теперь исправил - конечно, правильное событие — SheetChange для объектов Workbook или Application или Change для объекта Worksheet.

Обновление 2Некоторые замечания по Сообщение AlanR описывающее, как «вроде» заставить его работать с помощью таймера:

  • Непонятно, как функция таймера («Woohoo») будет знать, какие ячейки обновлять.У вас нет информации о том, какая ячейка содержит формулу, которая активировала таймер.

  • Если формула существует более чем в одной ячейке (в одной или разных книгах), то пользовательская функция будет вызываться несколько раз во время пересчета, перезаписывая timerId.В результате вам не удастся надежно уничтожить таймер и произойдет утечка ресурсов Windows.

Другие советы

В соответствии с Как создавать пользовательские функции Excel:

Ограничения UDF

  • Не может поместить значение в ячейку, кроме ячейки (или диапазона), содержащей формулу.Другими словами, UDF предназначены для использования как «формулы», а не «макросы».

Итак, похоже, что это невозможно сделать.

Я использую Excel 2007, и он не работает.Excel упоминает, что создает циклическую ссылку.Я не думаю, что вы можете изменить другие ячейки из функции, просто верните значение.

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

Эта статья также содержит много информации о том, как Excel выполняет перерасчет.Но здесь никогда не говорится, что другие ячейки заморожены.

Я не знаю, что вы пытаетесь сделать, но почему бы вам просто не поместить в соседнюю ячейку другую функцию, которая принимает первую ячейку в качестве параметра?

Пример:

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

Спасибо всем за ответ.Это возможно сделать!Как бы.Я говорю «вроде как», потому что технически говоря, «функция» не влияет на клетки вокруг нее.Однако на практике ни один пользователь не заметил разницы.

Хитрость заключается в том, чтобы использовать API Win32 для запуска таймера, и как только он сработает, вы делаете то, что хотите, с любой ячейкой и выключаете таймер.

Я не эксперт в том, как работает многопоточная обработка COM (хотя я знаю, что VBA имеет одноквартирную многопоточность), но будьте осторожны, чтобы ваш таймер не убежал вместе с вашим процессом Excel и не привел к его сбою.На самом деле я бы не предложил это решение для любой другой электронной таблицы.

Просто создайте модуль со следующим содержимым:

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

Хотя вы не можете сделать это в Excel, это возможно в Резолвер Один (хотя это все еще довольно странная вещь).

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

В качестве примера того, что вы спрашиваете, вы можете определить safeDivide функция, которая (вместо того, чтобы поднимать ZeroDivisionError) рассказал вам о проблеме, раскрасив ячейку знаменателя и поместив рядом с ней сообщение об ошибке.Вы можете определить это так:

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

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

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

Вот простой обходной путь VBA, который работает.В этом примере откройте новую книгу Excel и скопируйте следующий код в область кода для Sheet1 (нет ThisWorkbook или VBA Module).Затем войдите в Sheet1 и поместите что-нибудь в одну из верхних левых ячеек листа.Если вы введете число и нажмете Enter, то в ячейке справа будет увеличено в 4 раза число, а фон ячейки станет голубым.Любое другое значение приводит к очистке следующей ячейки.Вот код:

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

Подпрограмма фиксирует все события изменения ячеек на листе.Если строка и столбец оба <= 10, то ячейка справа устанавливается в 4 раза больше измененной ячейки, если значение числовое;в противном случае ячейка справа очищается.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top