VBA: Что происходит с объектами Range, если пользователь удаляет ячейки?
Предположим, что у меня есть какой-то модуль в vba с некоторой переменной r
типа Range
. Предположим, что в какой-то момент я сохраняю объект Range там (например, активную ячейку). Теперь мой вопрос: что происходит со значением r
, если пользователь удаляет ячейку (ячейку, а не только ее значение)?
Я попытался понять это в VBA, но безуспешно. Результат странный. r
не Nothing
, сообщается, что значение r
имеет тип Range
, но если я попытаюсь просмотреть его свойства в окне отладчика, каждое значение свойства будет указано как "требуемый объект".
Как я могу программным образом определить, находится ли переменная r
в этом состоянии или нет?
Могу ли я сделать это, не создавая ошибку и не поймав ее?
Ответы
Ответ 1
Хороший вопрос! Я никогда не думал об этом раньше, но эта функция, я думаю, идентифицирует диапазон, который был инициализирован - это не ничего, - но теперь он находится в состоянии "Обязательный объект", потому что его ячейки были удалены:
Function RangeWasDeclaredAndEntirelyDeleted(r As Range) As Boolean
Dim TestAddress As String
If r Is Nothing Then
Exit Function
End If
On Error Resume Next
TestAddress = r.Address
If Err.Number = 424 Then 'object required
RangeWasDeclaredAndEntirelyDeleted = True
End If
End Function
Вы можете протестировать так:
Sub test()
Dim r As Range
Debug.Print RangeWasDeclaredAndEntirelyDeleted(r)
Set r = ActiveSheet.Range("A1")
Debug.Print RangeWasDeclaredAndEntirelyDeleted(r)
r.EntireRow.Delete
Debug.Print RangeWasDeclaredAndEntirelyDeleted(r)
End Sub
Ответ 2
Я считаю, что когда вы используете ключевое слово Set в VBA, он создает указатель в фоновом режиме для объекта Range в рабочем листе в указанном вами листе (каждая ячейка является объектом в коллекции ячеек рабочего листа для заданного диапазона). Когда диапазон удаляется, пока вы все еще ссылаетесь на него в памяти, память для объекта, на который указана переменная Range, была освобождена.
Тем не менее, ваша переменная Range, скорее всего, все еще содержит указатель на недавно удаленный объект Range, поэтому это не что иное, но все, что указывает на это, больше не существует, что вызывает проблемы при попытке использовать переменная снова.
Проверьте этот код, чтобы увидеть, что я имею в виду:
Public Sub test2()
Dim r As Excel.Range
Debug.Print ObjPtr(r) ' 0
Set r = ActiveSheet.Range("A1")
Debug.Print ObjPtr(r) ' some address
r.Value = "Hello"
r.Delete
Debug.Print ObjPtr(r) ' same address as before
End Sub
Ознакомьтесь с этой статьей для получения дополнительной информации об ObjPtr():
http://support.microsoft.com/kb/199824
Итак, если у вас есть действительный адрес для объекта, к сожалению, объект больше не существует, поскольку он был удален. И кажется, что "Nothing is" просто проверяет адрес в указателе (который, я думаю, VBA считает, что переменная "Set" ).
Что касается того, как обойти эту проблему, к сожалению, я не вижу чистого способа сделать это в данный момент (если кто-нибудь найдет элегантный способ справиться с этим, отправьте его!). Вы можете использовать On Error Resume Next следующим образом:
Public Sub test3()
Dim r As Excel.Range
Debug.Print ObjPtr(r) ' 0
Set r = ActiveSheet.Range("A1")
Debug.Print ObjPtr(r) ' some address
r.Value = "Hello"
r.Delete
Debug.Print ObjPtr(r) ' same address as before
On Error Resume Next
Debug.Print r.Value
If (Err.Number <> 0) Then
Debug.Print "We have a problem here..."; Err.Number; Err.Description
End If
On Error GoTo 0
End Sub
Ответ 3
Как я могу программным образом определить, находится ли переменная r в этом состоянии или нет?
Могу ли я сделать это, не создавая ошибку и не поймав ее?
Нет.
Насколько я знаю, вы не можете проверить это условие надежно: не поднимать и не ловить ошибку.
Ваш вопрос был замечен и обсужден в другом месте: два больших имени в блогах Excel/VBA (Дик Куслайка и Роб Бови) посмотрели в него, и вы можете найти там что-то информативное. Но ответ №.
В целом, хороший вопрос с довольно тревожным ответом.
Ответ 4
Чтобы проверить, является ли объект диапазона недоступным, я использую эту функцию:
Public Function InvalidRangeReference(r As Range) As Boolean
On Error Resume Next
If r.Count = 0 Then
InvalidRangeReference = Err
End If
End Function