Подтвердить что ты не робот

VBA: Что происходит с объектами Range, если пользователь удаляет ячейки?

Предположим, что у меня есть какой-то модуль в с некоторой переменной r типа Range. Предположим, что в какой-то момент я сохраняю объект Range там (например, активную ячейку). Теперь мой вопрос: что происходит со значением r, если пользователь удаляет ячейку (ячейку, а не только ее значение)?

Я попытался понять это в VBA, но безуспешно. Результат странный. r не Nothing, сообщается, что значение r имеет тип Range, но если я попытаюсь просмотреть его свойства в окне отладчика, каждое значение свойства будет указано как "требуемый объект".

Как я могу программным образом определить, находится ли переменная r в этом состоянии или нет?

Могу ли я сделать это, не создавая ошибку и не поймав ее?

4b9b3361

Ответ 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