Как получить / установить уникальный идентификатор для ячейки в Excel через VBA - PullRequest
5 голосов
/ 19 июня 2009

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

Я думаю об использовании атрибута идентификатора Range ( msdn link )

Итак, у меня есть пользовательская функция (UDF), которую я помещаю в каждую строку, которая получает / устанавливает идентификатор следующим образом:

Dim gNextUniqueId As Integer

Public Function rbGetId(ticker As String)
    On Error GoTo rbGetId_Error
    Dim currCell As Range
    'tried using Application.Caller direct, but gives same error
    Set currCell = Range(Application.Caller.Address)
    If currCell.id = "" Then
        gNextUniqueId = gNextUniqueId + 1
        'this line fails no matter what value I set it to.
        currCell.id = Str(gNextUniqueId)
    End If
    rbGetId = ticker & currCell.id
    Exit Function

    rbGetId_Error:
    rbGetId = "!ERROR:" & Err.Description
End Function

Но это не сработает в строке, указанной с

«Ошибка приложения или объекта»

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

Любые другие предложения о том, как сохранить согласованные идентификаторы - возможно, мне следует заполнить ячейки с помощью кнопки на ленте, найти ячейки без идентификаторов и сгенерировать / установить значение ячейки этих ...

EDIT: Как думал Муравей, лист защищен, но даже в незапертой камере он все равно не работает. Снятие защиты листа устраняет проблему .... но я использовал «Protect UserInterFaceOnly: = True», что должно позволить мне сделать это. Если я вручную разрешаю «Редактировать объекты», когда защищаю лист, он также работает, но я не вижу программной опции для этого - и мне нужно вызвать функцию защиты в AutoOpen, чтобы включить функцию UserInterfaceOnly ...

Полагаю, мне нужно включить / выключить защиту для моей настройки идентификатора - при условии, что это можно сделать в UDF ... что, похоже, не может, поскольку это не работает - ни ActiveSheet.unprotect, ни ActiveWorkbook.unprotect (

Заранее спасибо. Chris

Ответы [ 5 ]

4 голосов
/ 24 июня 2009

Хорошо ...

Похоже, что если лист заблокирован, у макросов нет доступа на запись к низкоуровневой информации, такой как идентификатор.

Однако я не думаю, что можно снять защиту листа в UDF. По своей конструкции UDF сильно ограничены; Я думаю, что наличие формулы ячейки, контролирующей защиту листа, нарушило бы парадигму формулы, согласно которой формула ячейки влияет только на ячейку. Подробнее см. на этой странице на веб-сайте Microsoft.

Я думаю, что это ограничивает ваши возможности. Вы должны либо:

  • отказаться от защиты листа
  • отказаться от UDF, использовать событие Worksheet_Change, чтобы зафиксировать изменения в ячейке и записать туда ID *
  • использовать UDF, который записывает идентификатор в значение ячейки, а не сохранять в ID

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

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

Public Function CellMark()

    Dim currCell As Range
    Set currCell = Range(Application.Caller.Address)

    Dim myId As String
    ' must be text; using .value will cause the formula to be called again
    ' and create a circular reference
    myId = currCell.Text

    If (Trim(myId) = "" Or Trim(myId) = "0") Then
       myId = "ID-" & Format(CStr(gNextUniqueId), "00000")
       gNextUniqueId = gNextUniqueId + 1
    End If

    CellMark = myId

End Function

Это довольно некорректно. Использование копирования или поля заполнения, однако, сохранит предыдущее скопированное значение. Только явно установив ячейки в качестве новой формулы, она будет работать. Но если вы снова введете формулу в ячейку (просто щелкните ее, нажмите ВВОД), будет вычислено новое значение - стандартное поведение ячейки.

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

Private Sub Worksheet_Change(ByVal Target As Range)

    Dim currCell As Range
    Set currCell = Target.Cells(1, 1)

    Dim currId As String
    currId = currCell.ID

    If Trim(currCell.ID) = "" Then
        Target.Parent.Unprotect
        currCell.ID = CStr(gNextUniqueId)
        Target.Parent.Protect
        gNextUniqueId = gNextUniqueId + 1
    End If

End Sub

Последнее примечание; во всех случаях ваш счетчик идентификаторов будет сброшен, если вы снова откроете лист (по крайней мере, в соответствии с ограниченными подробностями, представленными в вашем примере).

Надеюсь, это поможет.

1 голос
/ 25 июня 2009

Проблема с Application.Caller.

Поскольку вы вызываете ее из пользовательской функции, она передаст вам описание ошибки. Вот замечание в файле справки.

Замечания

Это свойство возвращает информацию о том, как вызывался Visual Basic, как показано в следующей таблице.

Caller - Возвращаемое значение

  • Пользовательская функция, введенная в одну ячейку - объект Range, определяющий эту ячейку
  • Пользовательская функция, являющаяся частью формулы массива в диапазоне ячеек - объект Range, задающий этот диапазон ячеек
  • Макрос Auto_Open, Auto_Close, Auto_Activate или Auto_Deactivate - имя документа в виде текста
  • Макрос, установленный с помощью свойства OnDoubleClick или OnEntry - Имя идентификатора объекта диаграммы или ссылки на ячейку (если применимо), к которой применяется макрос
  • Диалоговое окно «Макрос» (меню «Инструменты») или любой вызывающий абонент, не описанный выше - #REF! значение ошибки

Поскольку вы вызываете ее из пользовательской функции, происходит следующее: Application.Caller возвращает строку кода ошибки в переменную диапазона curCell. Это НЕ вызывает ошибку, которую обработает ваш обработчик ошибок. После этого вы ссылаетесь на curCell, это больше не диапазон. На моей машине он пытается установить curCell = Range («Ошибка 2023»). Независимо от того, что это за объект, он может не иметь атрибута ID больше, и когда вы пытаетесь установить его, он выдает ошибку этого объекта.

Вот что я бы попробовал ...

  1. Попробуйте удалить обработчик ошибок и посмотрите, не вызывает ли VBA какие-либо исключения для Range (Application.Caller.Address). Это не исправит это, но может указать вам правильное направление.

  2. Либо с помощью логики, либо с помощью Application.ActiveCell или, как вы хотите, обращайтесь непосредственно к ячейке. Например Range («A1») или Cells (1,1). Application.Caller.Address просто не кажется хорошим вариантом для использования.

  3. Попробуйте использовать Option Explicit. Это может привести к тому, что строка, в которой вы установили curCell, выдаст ошибку, поскольку Range (Application.Caller.Address) не выглядит так, как будто он передает диапазон назад, который является типом данных curCell.

1 голос
/ 19 июня 2009

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

1 голос
/ 19 июня 2009

Согласен с Ant - ваш код отлично работает здесь в Excel 2003 SP3.

Я также смог использовать:

Set currCell = Application.Caller
If Application.Caller.ID = "" Then
    gNextUniqueId = gNextUniqueId + 1
    'this line fails no matter what value I set it to.
    currCell.ID = Str(gNextUniqueId)
End If

Aha! Я думаю, что у меня есть.

Я думаю, что вы вызываете это из формулы массива, и он вызывается только ОДИН РАЗ в полном диапазоне. Вы не можете получить идентификатор для диапазона - только одну ячейку. Это объясняет, почему Application.Caller.ID не работает для вас, потому что Range ("A1: B9"). ID генерирует Application-defined or object-defined error.

Когда вы используете Range(Application.Caller.Address), чтобы получить «ячейку», вы просто откладываете эту ошибку до строки currCell.ID.

0 голосов
/ 26 июня 2009

Я обнаружил, что если я защищаю лист с помощью «Protect DrawingObjects: = False», UDF может установить Id. Странно.

Спасибо за помощь в этом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...