Более простой/чистый способ указать диапазоны при использовании большого количества именованных ячеек в VBA?

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

'Examples of ugly range references:
Range(Range("GS_BeginData").Offset((counter + m), -1), Range("GS_BeginData").Offset((counter + m), 2))

Range(Range("GS_BeginData").Offset(counter, 1), Range("GS_BeginData").Offset((counter + fileCount), 1))

Range("SS_Unit", Range("SS_Unit").Offset(0, 1))

Range("SS_BeginData", Range("SS_BeginData").End(xlDown))

Cells((Range("SS_BeginData").Row + i), (Range("SS_BeginData").Column + 1))

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


person FredGooch    schedule 08.12.2015    source источник
comment
Рассматривали ли вы возможность использования диапазонных или строковых переменных в ссылках на диапазоны?   -  person kolcinx    schedule 09.12.2015
comment
Вы можете загрузить все именованные диапазоны в более управляемые переменные. Set GSBD = Range("GS_BeginData"). Тогда все, что вам нужно использовать, это переменная.   -  person Scott Craner    schedule 09.12.2015
comment
Вы, ребята, быстро! Как я только что добавил в редактирование к исходному сообщению, у меня их довольно много, и я бы предпочел не иметь строку за строкой объявлений переменных в начале моих сабвуферов, но если нет другого способа, я думаю, что это будет иметь работать.   -  person FredGooch    schedule 09.12.2015
comment
Почему бы вам не использовать таблицу? Вы можете ссылаться на его части почти так же, как если бы вы ссылались на таблицу в базе данных.   -  person vacip    schedule 09.12.2015
comment
Вы действительно можете очистить его, используя With операторы. Объедините это с предложением @ScottCraner установить Range переменных, и вы уже в пути!   -  person BruceWayne    schedule 09.12.2015


Ответы (5)


более удобочитаемый способ -- да. Объявляйте и назначайте объектные переменные по мере необходимости. Как минимум, вы можете определить диапазоны для «GS_BeginData», «SS_Unit» и «SS_BeginData», что сделает ваш код более читабельным и удобным в сопровождении.

Таким образом, если вы когда-нибудь измените имена ссылок на именованные диапазоны, вам нужно будет обновить только несколько коротких строк кода, которые присваиваются исходным объектам, а не каждой литеральной ссылке, например, на "GS_BeginData", и т.п.

Dim GSBegin as Range, SSBegin as Range, SSUnit as Range

Set GSBegin = Range("GS_BeginData")
Set SSBegin = Range("SS_BeginData")
Set SSUnit = Range("SS_Unit")

Затем ваши уродливые ссылки могут быть изменены:

Range(GSBegin.Offset((counter + m), -1), GSBegin.Offset((counter + m), 2))

Range(GSBegin.Offset(counter, 1), GSBegin.Offset((counter + fileCount), 1))

SSUnit.Offset(0, 1)

Range(SSBegin, SSBegin.End(xlDown))

Cells((SSBegin.Row + i), (SSBegin.Column + 1))
person David Zemens    schedule 08.12.2015
comment
Он мог бы даже добавить With GSBegin перед этими первыми двумя строками, а затем End With прямо перед концом, не так ли? - person BruceWayne; 09.12.2015
comment
@BruceWayne да, конечно, и, как отмечает ChrisNielsen, метод Resize также полезен в подобных случаях. - person David Zemens; 09.12.2015

  1. Используйте переменные! (Dim это небольшая цена)
  2. Используйте другие свойства Range. В этих случаях полезно Resize

.

Dim GS_BeginData As Range
Set GS_BeginData = Range("GS_BeginData")
'Set r = Range(Range("GS_BeginData").Offset((counter + m), -1), Range("GS_BeginData").Offset((counter + m), 2))
Set r = GS_BeginData.Offset(counter + m, -1).Resize(, 3)

'Set r = Range(Range("GS_BeginData").Offset(counter, 1), Range("GS_BeginData").Offset((counter + fileCount), 1))
Set r = GS_BeginData.Offset(counter, 1).Resize(fileCount, 1)

Dim SS_Unit As Range
Set SS_Unit = Range("SS_Unit")
'Set r = Range("SS_Unit", Range("SS_Unit").Offset(0, 1))
Set r = SS_Unit.Resize(, 2)
Set r = SS_Unit.Resize(, SS_Unit.Columns.Count + 1) ' if SS_Unit has more than 1 column

Dim SS_BeginData As Range
Set SS_BeginData = Range("SS_BeginData")
'Set r = Range("SS_BeginData", Range("SS_BeginData").End(xlDown))
Set r = Range(SS_BeginData, SS_BeginData.End(xlDown))

'Set r = Cells((Range("SS_BeginData").Row + i), (Range("SS_BeginData").Column + 1))
Set r = SS_BeginData.Offset(i, 1).Resize(1, 1)
person chris neilsen    schedule 08.12.2015
comment
Dim As Range также будет быстрее, если вы вызываете Range() несколько раз, поскольку Range() будет получен динамически, а переменная будет напрямую указывать на объект Range. - person caiohamamura; 09.12.2015

Если вы действительно используете его в нескольких разных Subs, вы также можете создать функцию для DRY свой код, например.

Function GSBeginOffsetRange(a As Long, b As Long, c As Long, d As Long) As Range
    Dim GSBegin As Range
    Set GSBegin = Range("GS_BeginData")
    Set GSBeginOffsetRange = Range(GSBegin.Offset(a, b), GSBegin.Offset(c, d))
End Function

Это сделает ваш пример первой строки:

GSBeginOffsetRange((counter + m), -1, (counter + m), 2)
person caiohamamura    schedule 08.12.2015
comment
Хорошо, DRY — очень полезный навык для изучения (или девиз, по которому нужно жить). Один комментарий. Использование целочисленных переменных бесполезно. Хотя целые числа используют меньше памяти, чем переменные типа Long, компилятор преобразует целые числа в значения типа Long, так что в конце концов память не выигрывает. Компилятору нужно конвертировать, поэтому вы платите небольшую цену за производительность. Вдобавок ко всему, написание слова «Integer» в вашем коде занимает больше времени, чем «Long» ;-). - person Jzz; 09.12.2015
comment
Хорошая точка зрения. Не знал о преобразовании компилятором Integer в Long. :-) - person caiohamamura; 09.12.2015
comment
@jzz не только это, вы не можете получить доступ ко всему листу в Integer - person chris neilsen; 09.12.2015
comment
Как это DRY, когда все, что он делает, это повторяет функциональность смещения? - person chris neilsen; 09.12.2015
comment
Это повторяется Range(Range("GS_BeginData").Offset((counter + m), -1), Range("GS_BeginData").Offset((counter + m), 2)) и Range(Range("GS_BeginData").Offset(counter, 1), Range("GS_BeginData").Offset((counter + fileCount), 1)) - person caiohamamura; 09.12.2015
comment
GSBeginOffsetRange предотвратит повторение Range(Range, Range) материала, который представляет собой сложный синтаксис, который станет трудно поддерживать. GSBeginOffsetRange, безусловно, легче поддерживать. - person caiohamamura; 09.12.2015
comment
Кроме того, если он изменит имя GS_BeginData по какой-либо причине, ему нужно будет изменить его только один раз в коде. И он может повторно использовать функцию в разных подпрограммах, в то время как Dim не может, хотя я знаю, что Dim будет работать лучше, это не проблема, если он не находится в цикле while или что-то в этом роде. - person caiohamamura; 09.12.2015
comment
Разработчик должен создавать функции для СУХОГО кода, который лучше всего соответствует его потребностям, у меня нет доступа к его коду, чтобы сделать это, это просто глупый пример, который удовлетворит вопрос Более простой/чистый способ указать диапазоны при использовании лотов именованных ячеек в VBA?. Не могу сказать, что это не проще и чище. - person caiohamamura; 09.12.2015

Можете ли вы просто перечислить все Named Rnages, а затем просмотреть каждый элемент в этом списке?

http://www.mrexcel.com/forum/excel-questions/44907-list-out-named-ranges-visual-basic-applications.html

person Community    schedule 08.12.2015

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

Function RowNumber(Header As String, _
             Optional Sh As Worksheet, _
             Optional FromColumn As Integer = 1, _
             Optional IgnoreError As Boolean = False) As Integer
  If Sh Is Nothing Then Set Sh = ActiveSheet

  Dim R As Integer
  For R = 1 To Sh.UsedRange.Row + Sh.UsedRange.Rows.Count - 1
    If Sh.Cells(R, FromColumn) = Header Then RowNumber = R: Exit Function
  Next R

  If Not IgnoreError Then MsgBox "Row """ & Header & """ not found", vbCritical
End Function

Function ColumnNumber(Header As String, _
             Optional Sh As Worksheet, _
             Optional FromRow As Integer = 1, _
             Optional IgnoreError As Boolean = False) As Integer
  If Sh Is Nothing Then Set Sh = ActiveSheet

  Dim C As Integer
  For C = 1 To Sh.UsedRange.Column + Sh.UsedRange.Columns.Count - 1
    If Sh.Cells(FromRow, C) = Header Then ColumnNumber = C: Exit Function
  Next C

  If Not IgnoreError Then MsgBox "Column """ & Header & """ not found", vbCritical
End Function

Затем я получаю значение с чем-то вроде:

Value = Sh.Cells(RowNumber("Header", Sh), 2)

Иногда я использую другую версию с memoization.

person stenci    schedule 08.12.2015