Автоматическая нумерация строк в Excel по порядку с помощью VBA
Каждый знает, как проставить нумерацию строк в excel. Для этого достаточно поставить первые две цифры, выделить их и потянуть за появившийся в правом нижнем углу выделенной области черный квадрат. Все нумерация готова.
Но когда возникает необходимость делать эту процедуру часто, или ввиду обилия нескольких разделов и подразделов – ручная нумерация строк в таблице excel превращается в скучную рутинную работу. И гораздо приятнее написать макрос нумерации строк excel и наслаждаться результатом через долю секунды.
Работоспособный макрос нумерации строк в excel по порядку приведен ниже:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
' Обновить нумерацию
If ActiveCell.Column = 10 And Cells(ActiveCell.Row, ActiveCell.Column).Value = "Обновить нумерацию" And ActiveCell.Row = 2 Then
' ссылка на лист книги в котором обновляем нумерацию
Set pr = Workbooks("planero-s-avtomaticheskoj-numeraciej-strok.xlsm").Worksheets("Проекты")
i = 0 ' для работоспособности цикла
j = 1 ' номер задачи
k = 1 ' номер подзадачи
' проходим вниз по странице до тех пор, пока есть записи в ячейке "B3" или "C3"
Do While pr.Range("B3").Offset(i, 0) > 0 Or pr.Range("D3").Offset(i, 0) > 0
' если ячейка "В3"+i содержит текст
If pr.Range("B3").Offset(i, 0) > 0 Then
' нумеруем ее
pr.Range("A3").Offset(i, 0) = j
' выделяем номер жирным
pr.Range("A3").Offset(i, 0).Font.Bold = True
' увеличиваем переменную j на единицу
j = j + 1
' обнуляем номер подзадачи
k = 1
' если ячейка "B3"+i пустая, следовательно мы имеем дело с подзадачей
Else
' если ячейка "D3"+i содержит текст
If pr.Range("D3").Offset(i, 0) > 0 Then
' нумеруем ее
pr.Range("C3").Offset(i, 0) = k
' увеличиваем номер подзадачи на единицу
k = k + 1
End If
End If
' увеличиваем i на +1 чтобы проверить следующию ячейку (расположенную ниже) на наличие записи
i = i + 1
Loop
' убираем курсор с кнопки
pr.Cells(ActiveCell.Row, ActiveCell.Column - 2).Select
End If
End Sub
Первым делом привязываем запуск макроса при нажатии на кнопку «Обновить нумерацию». Далее запускаем цикл и выполняем его до тех пор, пока ячейка B3 и D3 содержит текст. При этом последовательно перемещаемся к ниже идущим ячейкам от B3 и D3, используя Offset и переменную i: .offset(i, 0)
. Одновременно с этим, нумеруем задачи (если B3+i содержит текст) и подзадачи (если B3+i не содержит текст, при этом содержит текст D3+i). Ну и напоследок убираем курсор с кнопки «Обновить нумерацию».
Саму экселевсую книгу с макросом автоматической нумерации можно скачать по этой ссылке. На этом все. Остались вопросы – задавайте их в комментариях. С другими кейсами по автоматизации можно ознакомиться здесь.
Комментарии 2
© планеро.ru
Olya1985 32 / 2 / 0 Регистрация: 31.12.2010 Сообщений: 91 |
||||
1 |
||||
Макрос для автоматической нумерации строк07.01.2011, 13:27. Показов 32903. Ответов 15 Метки нет (Все метки)
Подскажите, пожалуйста, как исправить макрос чтобы строки нумеровались автоматически на листе, а также при удалении строки нумерация также смещалась.
0 |
Частенько бываю 749 / 330 / 42 Регистрация: 20.06.2007 Сообщений: 854 |
|
07.01.2011, 17:40 |
2 |
А чем же вас этот код не устраивает конкретно?
0 |
32 / 2 / 0 Регистрация: 31.12.2010 Сообщений: 91 |
|
07.01.2011, 17:42 [ТС] |
3 |
он зависает после нумерации первой строки…
0 |
Vlanib Частенько бываю 749 / 330 / 42 Регистрация: 20.06.2007 Сообщений: 854 |
||||
07.01.2011, 18:03 |
4 |
|||
Естественно! Исправьте макрос следующим образом:
0 |
32 / 2 / 0 Регистрация: 31.12.2010 Сообщений: 91 |
|
07.01.2011, 18:07 [ТС] |
5 |
Vlanib, теперь строки не нумеруются…
0 |
Частенько бываю 749 / 330 / 42 Регистрация: 20.06.2007 Сообщений: 854 |
|
07.01.2011, 18:15 |
6 |
Скиньте ваш файлик.
0 |
32 / 2 / 0 Регистрация: 31.12.2010 Сообщений: 91 |
|
07.01.2011, 18:24 [ТС] |
7 |
файлик прилагаю
0 |
Частенько бываю 749 / 330 / 42 Регистрация: 20.06.2007 Сообщений: 854 |
|
07.01.2011, 18:30 |
8 |
Видимо не приложился…
0 |
32 / 2 / 0 Регистрация: 31.12.2010 Сообщений: 91 |
|
07.01.2011, 18:32 [ТС] |
9 |
еще раз попробую…
0 |
Частенько бываю 749 / 330 / 42 Регистрация: 20.06.2007 Сообщений: 854 |
|
07.01.2011, 18:56 |
10 |
Так, Оленька, давайте обстоятельно. В этом вашем посте вы просили перевести формулу на ВБА, что я и сделал дословно. Данная вами формула возвращает в ячейку количество аргументов в столбце «D». Если бы вы изначально правильно сформулировали задачу, то и вопросов было бы меньше.
1 |
32 / 2 / 0 Регистрация: 31.12.2010 Сообщений: 91 |
|
07.01.2011, 19:01 [ТС] |
11 |
Vlanib, спасибо! только осталась еще одна проблема.. при удалении строки в столбце B номер все равно остается. при использовании же формулы он исчезает вместе с удаленным текстом. можно ли в макросе сделать также?
0 |
Vlanib Частенько бываю 749 / 330 / 42 Регистрация: 20.06.2007 Сообщений: 854 |
||||
07.01.2011, 19:25 |
12 |
|||
1 |
32 / 2 / 0 Регистрация: 31.12.2010 Сообщений: 91 |
|
07.01.2011, 20:26 [ТС] |
13 |
теперь отлично. спасибо! Добавлено через 13 минут извиняюсь, но еще одна небольшая проблемка. последнее введенное значение не пронумеровывается. как можно это исправить? Добавлено через 16 минут прошу мое предыдущее сообщение считать недействительным. у меня овт еще вопрос. я не совсем понимаю для чего используется Application.EnableEvents = False или True. Не могли бы вы вкратце объяснить? Добавлено через 6 минут
0 |
1904 / 781 / 31 Регистрация: 11.02.2010 Сообщений: 1,567 |
|
07.01.2011, 22:41 |
14 |
EnableEvents, как следует из перевода, отключает или включает обработку событий приложения (т. е события могут либо игнорироваться, либо при возникновении событий могут вызываться соответствующие обработчики этих событий) http://support.microsoft.com/kb/213720 Не по теме:
я что то порядком запуталась ВОЗМОЖНО, выполнение кода было остановлено пользователем между инструкциями Application.EnableEvents = False/True и события перестали обрабатываться вплоть до повторного открытия приложения
0 |
91 / 1 / 1 Регистрация: 09.01.2013 Сообщений: 2 |
|
09.01.2013, 11:15 |
15 |
Сообщение было отмечено как решение РешениеУважаемый Vlanib Спасибо — Очень интересное решение , а можно дописать макрос чтоб в » A » счет шел с первой пустой ячейки ( так как обычно бывают шапки в таблицах — получится макрос на все случаи жизни
0 |
8 / 8 / 0 Регистрация: 22.02.2012 Сообщений: 159 |
|
20.02.2021, 14:05 |
16 |
Vlanib, а как переписать макрос, чтобы он нумеровал строку не по приницпу «количество столбцов выше+1», а «значение в предыдущей ячейке+1». У меня нумерация строк просто 10001, 10002..
0 |
Содержание
- Автоматическая нумерация строк в Excel по порядку с помощью VBA
- Сборник полезностей
- пятница, 1 февраля 2013 г.
- Excel VBA. На старт
- Основы
- Автоматическая нумерация строк таблицы
- Кнопки вставки и удаления строк
- Проверка листа на правильное заполнение
- Vba excel пронумеровать строки таблицы
- Vba excel пронумеровать строки таблицы
Автоматическая нумерация строк в Excel по порядку с помощью VBA
Каждый знает, как проставить нумерацию строк в excel. Для этого достаточно поставить первые две цифры, выделить их и потянуть за появившийся в правом нижнем углу выделенной области черный квадрат. Все нумерация готова.
Вот так делается ручная нумерация строк
Но когда возникает необходимость делать эту процедуру часто, или ввиду обилия нескольких разделов и подразделов – ручная нумерация строк в таблице excel превращается в скучную рутинную работу. И гораздо приятнее написать макрос нумерации строк excel и наслаждаться результатом через долю секунды.
excel vba автоматическая нумерация строк
Работоспособный макрос нумерации строк в excel по порядку приведен ниже:
Первым делом привязываем запуск макроса при нажатии на кнопку «Обновить нумерацию». Далее запускаем цикл и выполняем его до тех пор, пока ячейка B3 и D3 содержит текст. При этом последовательно перемещаемся к ниже идущим ячейкам от B3 и D3, используя Offset и переменную i: .offset(i, 0) . Одновременно с этим, нумеруем задачи (если B3+i содержит текст) и подзадачи (если B3+i не содержит текст, при этом содержит текст D3+i). Ну и напоследок убираем курсор с кнопки «Обновить нумерацию».
Саму экселевсую книгу с макросом автоматической нумерации можно скачать по этой ссылке. На этом все. Остались вопросы – задавайте их в комментариях. С другими кейсами по автоматизации можно ознакомиться здесь.
Источник
Сборник полезностей
пятница, 1 февраля 2013 г.
Excel VBA. На старт
Основы
‘ в ячейку А1 пишем «add note» + значение ячейки B1
Cells(1,1).Value = «add note » & Cells(1,2).Value
For Each oCell In Range(«A1:A10»).Cells
‘обработка
oCell.Value = oCell.Row ‘ присвоили номер строки
Next
Public Function insertRow()
Dim position As Integer
‘.
insertRow = position ‘возвращаемое значение
После написания нескольких функций во встроенном редакторе, меня нешуточно заинтересовало, как перенести строку кода на следующую физическую строку. Если посреди строки кода нажать enter, кроме ошибки мы ничего не добьемся. Оказывается, надо использовать «_»:
If ActiveSheet.Index = 1 _
Or ActiveSheet.Index = 4 Then
‘ действия
End If
Нарочно и не придумаешь.
Помимо массивов, в VBA следует обратить внимание на коллекции. Работать с ними просто:
Dim сoll As New Collection
сoll .Add («элемент1») ‘добавляем элемент
сoll .Add («элемент2») ‘размер увеличивается динамически
‘ по умолчанию индекс начинается с 1, можно изменить
MsgBox ( сoll .Item(1)) ‘ элемент1
сoll . Remove (1) ‘ удаляем первый элемент
MsgBox ( сoll .Count) ‘выводим размер коллекции
Автоматическая нумерация строк таблицы
Это потому, что формула в строке А9 крайне смущена тем, что ссылается на удаленную ячейку. Поэтому если строки в таблице буду удаляться и добавляться, лучше сделать ссылку на ячейки другой страницы (=СТРОКА(Лист2!А1)).
Кнопки вставки и удаления строк
Само по себе добавление и удаление по имени кнопки сложности не представляет:
Private Sub InsertButton1_Click( position as Integer )
‘вставка строки перед строкой с номером position
Rows(position).Insert
End Sub
Private Sub DeleteButton1_Click()
Dim position as Integer
‘ ищем номер строки кнопки удаления
position = _ ActiveSheet.Shapes(Application.Caller).TopLeftCell.Row
‘ удаление кнопки
ActiveSheet.Shapes(Application.Caller).Delete
‘ удаление строки с номером position
Rows(position).Delete
End Sub
Для нахождения позиции первой строки таблицы будем использовать именованные ячейки.
Маловероятно, что пользователь создаст за секунду более одной строки (в комплекте с кнопкой ее удаления) с одинаковым начальным номером строки.
Проверка листа на правильное заполнение
В голову пришло два слегка костыльных способа решения (и честно говоря, если судить по форумам с обсуждением подобных проблем, кажется, что применительно к Excel по-другому их и не решишь).
Первый способ: в одном столбце, напротив каждой строки таблицы, формулой вычисляется, правильно ли заполнена строка. Тогда, проходясь по этому столбцу, мы можем сказать, в какой строке проблема. А при помощи условного форматирования проблемные ячейки могут подсвечиваться красным. Этого достаточно, если не надо точно знать, в какой ячейке проблема. При необходимости можно дальше обрабатывать эту строку по ячейкам. Способ хорош, если разные строки проверяются на разные условия (даже в рамках одной таблицы).
Источник
Vba excel пронумеровать строки таблицы
Подскажите, пожалуйста, как исправить макрос чтобы строки нумеровались автоматически на листе, а также при удалении строки нумерация также смещалась.
данный макрос зависает после нумерации первой строки.
Private Sub worksheet_change (byval target as range)
For Each oCell In Range([D1], Cells(Rows.Count, «D»)).Cells
If Not IsEmpty(oCell) Then iCount = iCount + 1: oCell.Previous = iCount
Next
данный макрос зависает после нумерации первой строки
Этот макрос будет зависать после нумерации любой строки.
Почему?
Да потому, что вы не прислушиваетесь к советам.
Я вам уже говорил, что надо дописать в коде, чтобы не было зависаний:
http://programmersforum.ru/showpost. 53&postcount=2
но когда я это дописала, то нумерация вообще пропала.
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal target As Range)
Application.EnableEvents = False
For Each oCell In Range([D1], Cells(Rows.Count, «D»)).Cells
If Not IsEmpty(oCell) Then iCount = iCount + 1: oCell.Previous = iCount
Next
Application.EnableEvents = True
End Sub
Мы не видели ваш файл, не знаем, что и как там надо нумеровать, так что, извините, ответа вы вряд ли дождётесь.
Почитайте правила раздела — там есть рекомендация каждый раз выкладывать пример файла.
Источник
Vba excel пронумеровать строки таблицы
= Мир MS Excel/Нумерация строк — Мир MS Excel
Войти через uID
Войти через uID
Модератор форума: китин, _Boroda_
Мир MS Excel » Вопросы и решения » Вопросы по VBA » Нумерация строк (Макросы Sub)
Нумерация строк
Гисер | Дата: Пятница, 04.10.2013, 08:13 | Сообщение № 1 | |
|
Hashtag Пользователь Сообщений: 145 |
#1 08.02.2019 15:48:43 Добрый день. Помогите создать нумерацию не пустых строк средствами VBA, без формул в ячейках. Нумерация должна начинаться с 7 строки в колонке B. При очистке строки нумерация в строке должна удаляться и продолжить нумерацию со следующей не пустой строки.
Прикрепленные файлы
|
||
Ігор Гончаренко Пользователь Сообщений: 13746 |
#2 08.02.2019 16:03:35
Программисты — это люди, решающие проблемы, о существовании которых Вы не подозревали, методами, которых Вы не понимаете! |
||
vikttur Пользователь Сообщений: 47199 |
#3 08.02.2019 16:06:39
Событие листа… |
||
Hashtag Пользователь Сообщений: 145 |
#4 08.02.2019 19:33:11 Ігор Гончаренко Посмотрите, пожалуйста, что в коде не так?
Прикрепленные файлы
Изменено: Hashtag — 08.02.2019 19:36:13 |
|
Vintic Пользователь Сообщений: 10 |
#5 08.02.2019 19:44:16
|
||
Hashtag Пользователь Сообщений: 145 |
Vintic |
Юрий М Модератор Сообщений: 60575 Контакты см. в профиле |
Файл не смотрел… Попробуйте увеличить переменную номера последней строки на единичку: |
Vintic Пользователь Сообщений: 10 |
#8 08.02.2019 21:25:38 Подлечил немного) Надеюсь сейчас все нормально
|
||
Vintic Пользователь Сообщений: 10 |
#9 08.02.2019 21:27:45
Спасибо, сделал почти так же |
||
Hashtag Пользователь Сообщений: 145 |
#10 08.02.2019 21:28:43 Юрий М
Если снизу очищать несколько строк, номера остаются кроме одного |
||
см.вложение Программисты — это люди, решающие проблемы, о существовании которых Вы не подозревали, методами, которых Вы не понимаете! |
|
Hashtag Пользователь Сообщений: 145 |
Vintic |
Hashtag Пользователь Сообщений: 145 |
Ігор Гончаренко |
Юрий М Модератор Сообщений: 60575 Контакты см. в профиле |
#14 08.02.2019 21:56:22
В каких ячейках (столбцах) Вы удаляете значения? |
||
Hashtag Пользователь Сообщений: 145 |
Юрий М Изменено: Hashtag — 08.02.2019 22:04:01 |
skais675 Пользователь Сообщений: 2177 |
|
Юрий М Модератор Сообщений: 60575 Контакты см. в профиле |
#17 08.02.2019 22:37:52
|
||
Hashtag Пользователь Сообщений: 145 |
skais675 |
ocet p Пользователь Сообщений: 438 |
#19 09.02.2019 01:38:10 Пожалуйста, проверьте:
|
||
skais675 Пользователь Сообщений: 2177 |
Ну раз такое дело, тогда так. |
Hashtag Пользователь Сообщений: 145 |
#21 09.02.2019 13:32:11 skais675
От себя хочу поблагодарить всех, кто отозвался решить эту задачу и внес свой вклад в ее разрешение, не оставили наедине с проблемой и довели дело до конца. Спасибо всем! |
||
vikttur Пользователь Сообщений: 47199 |
#22 09.02.2019 13:39:15
Код в модуле листа? Нужно показать, тогда и на ошибку укажут. |
||
Hashtag Пользователь Сообщений: 145 |
#23 09.02.2019 14:16:43 vikttur
Если в модуле листа, ошибку не выдает, но и нумерация не происходит. Прикрепленные файлы
|
||
vikttur Пользователь Сообщений: 47199 |
#24 09.02.2019 14:22:01
Название процедуры указывает на то, что это — процедура отслеживания события листа (Worksheet_Change) — код должен быть в модуле листа. По коду Worksheet_Change. Вы изменили предложенный макрос.
Если изменения произошли не в диапазоне C3:F3, уходим. Т.е. до проверки диапазона Range(«c7:f» & maks) никак не дойдет Это вообще лишнее:
|
||||||
Юрий М Модератор Сообщений: 60575 Контакты см. в профиле |
Hashtag, в #17 я предложил минимальную правку (к старым файлам), которая решала проблему. |
Hashtag Пользователь Сообщений: 145 |
#26 09.02.2019 17:06:07 vikttur Прикрепленные файлы
|
Here is my code to add line numbers in the VBE IDE. It is an improvement of the solution provided here by Excel MVP mikerickson. I have worked on this, because in some rare cases I have already met, VBE can’t enter in debug mode, for example when you have a .ReplaceLine method in your code. Indeed, you can’t enter in debug mode once it has been executed, so Erl might be usefully for debug (instead of Debug.Print). I have added several feature such as:
- possibility to either add line numbers as labels:
10: Dim foo as bar
or as single numbers seperated from code by a tab:10 Dim foo as bar
- possibility to add line numbers to End of procedures statements, and to match the indent of the procedure declaration lines to its End statement line once numberered. Or not.
- possibility of add line numbers to empty lines or not
- [WIP] possibility to add line numbers to a specific procedure in a module
- [WIP] match all indentations of code lines with line numbers to match the indent of the last line indented. If last line is
200: End Sub
, the line30: With ActiveSheet
will be re-indented as30: ActiveSheet
- [WIP] add of a VBE IDE command to directly make the calls with the current module/proc as a parameter
Public Enum vbLineNumbers_LabelTypes
vbLabelColon ' 0
vbLabelTab ' 1
End Enum
Public Enum vbLineNumbers_ScopeToAddLineNumbersTo
vbScopeAllProc ' 1
vbScopeThisProc ' 2
End Enum
Sub AddLineNumbers(ByVal wbName As String, _
ByVal vbCompName As String, _
ByVal LabelType As vbLineNumbers_LabelTypes, _
ByVal AddLineNumbersToEmptyLines As Boolean, _
ByVal AddLineNumbersToEndOfProc As Boolean, _
ByVal Scope As vbLineNumbers_ScopeToAddLineNumbersTo, _
Optional ByVal thisProcName As String)
' USAGE RULES
' DO NOT MIX LABEL TYPES FOR LINE NUMBERS! IF ADDING LINE NUMBERS AS COLON TYPE, ANY LINE NUMBERS AS VBTAB TYPE MUST BE REMOVE BEFORE, AND RECIPROCALLY ADDING LINE NUMBERS AS VBTAB TYPE
Dim i As Long
Dim j As Long
Dim procName As String
Dim startOfProcedure As Long
Dim lengthOfProcedure As Long
Dim endOfProcedure As Long
Dim strLine As String
With Workbooks(wbName).VBProject.VBComponents(vbCompName).CodeModule
.CodePane.Window.Visible = False
If Scope = vbScopeAllProc Then
For i = 1 To .CountOfLines
strLine = .Lines(i, 1)
procName = .ProcOfLine(i, vbext_pk_Proc) ' Type d'argument ByRef incompatible ~~> Requires VBIDE library as a Reference for the VBA Project
If procName <> vbNullString Then
startOfProcedure = .ProcStartLine(procName, vbext_pk_Proc)
bodyOfProcedure = .ProcBodyLine(procName, vbext_pk_Proc)
countOfProcedure = .ProcCountLines(procName, vbext_pk_Proc)
prelinesOfProcedure = bodyOfProcedure - startOfProcedure
'postlineOfProcedure = ??? not directly available since endOfProcedure is itself not directly available.
lengthOfProcedure = countOfProcedure - prelinesOfProcedure ' includes postlinesOfProcedure !
'endOfProcedure = ??? not directly available, each line of the proc must be tested until the End statement is reached. See below.
If endOfProcedure <> 0 And startOfProcedure < endOfProcedure And i > endOfProcedure Then
GoTo NextLine
End If
If i = bodyOfProcedure Then InProcBodyLines = True
If bodyOfProcedure < i And i < startOfProcedure + countOfProcedure Then
If Not (.Lines(i - 1, 1) Like "* _") Then
InProcBodyLines = False
PreviousIndentAdded = 0
If Trim(strLine) = "" And Not AddLineNumbersToEmptyLines Then GoTo NextLine
If IsProcEndLine(wbName, vbCompName, i) Then
endOfProcedure = i
If AddLineNumbersToEndOfProc Then
Call IndentProcBodyLinesAsProcEndLine(wbName, vbCompName, LabelType, endOfProcedure)
Else
GoTo NextLine
End If
End If
If LabelType = vbLabelColon Then
If HasLabel(strLine, vbLabelColon) Then strLine = RemoveOneLineNumber(.Lines(i, 1), vbLabelColon)
If Not HasLabel(strLine, vbLabelColon) Then
temp_strLine = strLine
.ReplaceLine i, CStr(i) & ":" & strLine
new_strLine = .Lines(i, 1)
If Len(new_strLine) = Len(CStr(i) & ":" & temp_strLine) Then
PreviousIndentAdded = Len(CStr(i) & ":")
Else
PreviousIndentAdded = Len(CStr(i) & ": ")
End If
End If
ElseIf LabelType = vbLabelTab Then
If Not HasLabel(strLine, vbLabelTab) Then strLine = RemoveOneLineNumber(.Lines(i, 1), vbLabelTab)
If Not HasLabel(strLine, vbLabelColon) Then
temp_strLine = strLine
.ReplaceLine i, CStr(i) & vbTab & strLine
PreviousIndentAdded = Len(strLine) - Len(temp_strLine)
End If
End If
Else
If Not InProcBodyLines Then
If LabelType = vbLabelColon Then
.ReplaceLine i, Space(PreviousIndentAdded) & strLine
ElseIf LabelType = vbLabelTab Then
.ReplaceLine i, Space(4) & strLine
End If
Else
End If
End If
End If
End If
NextLine:
Next i
ElseIf AddLineNumbersToEmptyLines And Scope = vbScopeThisProc Then
End If
.CodePane.Window.Visible = True
End With
End Sub
Function IsProcEndLine(ByVal wbName As String, _
ByVal vbCompName As String, _
ByVal Line As Long) As Boolean
With Workbooks(wbName).VBProject.VBComponents(vbCompName).CodeModule
If Trim(.Lines(Line, 1)) Like "End Sub*" _
Or Trim(.Lines(Line, 1)) Like "End Function*" _
Or Trim(.Lines(Line, 1)) Like "End Property*" _
Then IsProcEndLine = True
End With
End Function
Sub IndentProcBodyLinesAsProcEndLine(ByVal wbName As String, ByVal vbCompName As String, ByVal LabelType As vbLineNumbers_LabelTypes, ByVal ProcEndLine As Long)
Dim procName As String
Dim startOfProcedure As Long
Dim endOfProcedure As Long
With Workbooks(wbName).VBProject.VBComponents(vbCompName).CodeModule
procName = .ProcOfLine(ProcEndLine, vbext_pk_Proc)
bodyOfProcedure = .ProcBodyLine(procName, vbext_pk_Proc)
endOfProcedure = ProcEndLine
strEnd = .Lines(endOfProcedure, 1)
j = bodyOfProcedure
Do Until Not .Lines(j - 1, 1) Like "* _" And j <> bodyOfProcedure
strLine = .Lines(j, 1)
If LabelType = vbLabelColon Then
If Mid(strEnd, Len(CStr(endOfProcedure)) + 1 + 1 + 1, 1) = " " Then
.ReplaceLine j, Space(Len(CStr(endOfProcedure)) + 1) & strLine
Else
.ReplaceLine j, Space(Len(CStr(endOfProcedure)) + 2) & strLine
End If
ElseIf LabelType = vbLabelTab Then
If endOfProcedure < 1000 Then
.ReplaceLine j, Space(4) & strLine
Else
Debug.Print "This tool is limited to 999 lines of code to work properly."
End If
End If
j = j + 1
Loop
End With
End Sub
Sub RemoveLineNumbers(ByVal wbName As String, ByVal vbCompName As String, ByVal LabelType As vbLineNumbers_LabelTypes)
Dim i As Long
With Workbooks(wbName).VBProject.VBComponents(vbCompName).CodeModule
For i = 1 To .CountOfLines
procName = .ProcOfLine(i, vbext_pk_Proc)
If procName <> vbNullString Then
If i = .ProcBodyLine(procName, vbext_pk_Proc) Then InProcBodyLines = True
LenghtBefore = Len(.Lines(i, 1))
If Not .Lines(i - 1, 1) Like "* _" Then
InProcBodyLines = False
.ReplaceLine i, RemoveOneLineNumber(.Lines(i, 1), LabelType)
Else
If IsInProcBodyLines Then
' do nothing
Else
.ReplaceLine i, Mid(.Lines(i, 1), RemovedChars_previous_i + 1)
End If
End If
LenghtAfter = Len(.Lines(i, 1))
LengthBefore_previous_i = LenghtBefore
LenghtAfter_previous_i = LenghtAfter
RemovedChars_previous_i = LengthBefore_previous_i - LenghtAfter_previous_i
If Trim(.Lines(i, 1)) Like "End Sub*" Or Trim(.Lines(i, 1)) Like "End Function" Or Trim(.Lines(i, 1)) Like "End Property" Then
LenOfRemovedLeadingCharacters = LenghtBefore - LenghtAfter
procName = .ProcOfLine(i, vbext_pk_Proc)
bodyOfProcedure = .ProcBodyLine(procName, vbext_pk_Proc)
j = bodyOfProcedure
strLineBodyOfProc = .Lines(bodyOfProcedure, 1)
Do Until Not strLineBodyOfProc Like "* _"
j = j + 1
strLineBodyOfProc = .Lines(j, 1)
Loop
LastLineBodyOfProc = j
strLastLineBodyOfProc = strLineBodyOfProc
strLineEndOfProc = .Lines(i, 1)
For k = bodyOfProcedure To j
.ReplaceLine k, Mid(.Lines(k, 1), 1 + LenOfRemovedLeadingCharacters)
Next k
i = i + (j - bodyOfProcedure)
GoTo NextLine
End If
Else
' GoTo NextLine
End If
NextLine:
Next i
End With
End Sub
Function RemoveOneLineNumber(ByVal aString As String, ByVal LabelType As vbLineNumbers_LabelTypes)
RemoveOneLineNumber = aString
If LabelType = vbLabelColon Then
If aString Like "#:*" Or aString Like "##:*" Or aString Like "###:*" Then
RemoveOneLineNumber = Mid(aString, 1 + InStr(1, aString, ":", vbTextCompare))
If Left(RemoveOneLineNumber, 2) Like " [! ]*" Then RemoveOneLineNumber = Mid(RemoveOneLineNumber, 2)
End If
ElseIf LabelType = vbLabelTab Then
If aString Like "# *" Or aString Like "## *" Or aString Like "### *" Then RemoveOneLineNumber = Mid(aString, 5)
If aString Like "#" Or aString Like "##" Or aString Like "###" Then RemoveOneLineNumber = ""
End If
End Function
Function HasLabel(ByVal aString As String, ByVal LabelType As vbLineNumbers_LabelTypes) As Boolean
If LabelType = vbLabelColon Then HasLabel = InStr(1, aString & ":", ":") < InStr(1, aString & " ", " ")
If LabelType = vbLabelTab Then
HasLabel = Mid(aString, 1, 4) Like "# " Or Mid(aString, 1, 4) Like "## " Or Mid(aString, 1, 4) Like "### "
End If
End Function
Function RemoveLeadingSpaces(ByVal aString As String) As String
Do Until Left(aString, 1) <> " "
aString = Mid(aString, 2)
Loop
RemoveLeadingSpaces = aString
End Function
Function WhatIsLineIndent(ByVal aString As String) As String
i = 1
Do Until Mid(aString, i, 1) <> " "
i = i + 1
Loop
WhatIsLineIndent = i
End Function
Function HowManyLeadingSpaces(ByVal aString As String) As String
HowManyLeadingSpaces = WhatIsLineIndent(aString) - 1
End Function
You can make calls like this :
Sub AddLineNumbers_vbLabelColon()
AddLineNumbers wbName:="EvaluateCall.xlsm", vbCompName:="ModLineNumbers_testDest", LabelType:=vbLabelColon, AddLineNumbersToEmptyLines:=True, AddLineNumbersToEndOfProc:=True, Scope:=vbScopeAllProc
End Sub
Sub AddLineNumbers_vbLabelTab()
AddLineNumbers wbName:="EvaluateCall.xlsm", vbCompName:="ModLineNumbers_testDest", LabelType:=vbLabelTab, AddLineNumbersToEmptyLines:=True, AddLineNumbersToEndOfProc:=True, Scope:=vbScopeAllProc
End Sub
Sub RemoveLineNumbers_vbLabelColon()
RemoveLineNumbers wbName:="EvaluateCall.xlsm", vbCompName:="ModLineNumbers_testDest", LabelType:=vbLabelColon
End Sub
Sub RemoveLineNumbers_vbLabelTab()
RemoveLineNumbers wbName:="EvaluateCall.xlsm", vbCompName:="ModLineNumbers_testDest", LabelType:=vbLabelTab
End Sub
And as a reminder, here as some compile rules about about line numbers:
- not allowed before a Sub/Function declaration statement
- not allowed outside of a proc
- not allowed on a line following a line continuation character «_» (underscore)
- not allowed to have more than one label/line number per code line ~~> Existing labels other than line numbers must be tested otherwise a compile error will occur trying to force a line number.
- not allowed to use characters that already have a special VBA meaning ~~> Allowed characters are [a-Z], [0-9], é, è, ô, ù, €, £, § and even «:» alone !
- compiler will trim any space before a label ~~> So if there is a label, the first char of the line is the first char of the label, it cannot be a space.
- appending a line number with a colon will result in having a space inserted between the «:» and the fist next char if there is none
- when appending a line number with a tab/space, there must be at least one space between the last digit and the first next char, compiler won’t add it as it does for a label with a colon separator
- the .ReplaceLine method will overide the compile rules without displaying any compile error as it does in design mode when selecting a new line or when manually relaunching compilation
- the compiler is ‘quicker than the VBA environment/system’: for example, just after a line number with colon and without any space has been inserted with .ReplaceLine, if the .Lines property is called to get the new string, the space (between the colon character and the first character of the string) is already appended in that string !
- it is not possible to enter debug mode after a .ReplaceLine has been called (from within or outside the module it is editting), not till the code is running, and execution reset.
Нумерация строк |
||||||||
Ответить |
||||||||
Ответить |
||||||||
Ответить |
||||||||
Ответить |
||||||||
Ответить |
||||||||
Ответить |
||||||||
Ответить |
||||||||
Ответить |
||||||||
Ответить |
||||||||
Ответить |
||||||||
Ответить |
||||||||
Ответить |
Последнее время на работе занималась приведением excel-файлов к виду, при котором пользователь может допустить наименьшее количество ошибок при заполнении. До этого первый и последний раз я сталкивалась с visual basic в школе, причем не применительно к Ms Excel, поэтому потребовалось некоторое время на то, чтобы понять, как делать простые и, и тем более не очень, вещи. Эта статейка может помочь людям, оказавшимся в похожей ситуации, быстрее войти в курс дела. Демо-экселька прилагается.
Основы
Самый первый вопрос, который у меня возник, после того как вкладка «Разработчик» была добавлена (подробнее тут, например) и открыт встроенный vba-редактор, это куда собственно писать код. По умолчанию в книге есть модули страниц, модуль книги и один общий модуль. Существуют также модули формы и классов, но сейчас речь не о них. Все вполне логично: обработку событий на объекты страницы пишем в соответствующий лист страницы, обработку событий для книги — в модуль книги, а общие функции для всех листов — в общий модуль. При этом имеет смысл создать еще модули для отделения функционала по смыслу.
Также сразу возникает вопрос, а как элементарно обратиться к ячейке, найти ее значение и перезаписать его. Очень просто:
‘ в ячейку А1 пишем «add note» + значение ячейки B1
Cells(1,1).Value = «add note » & Cells(1,2).Value
For Each oCell In Range(«A1:A10»).Cells
‘обработка
oCell.Value = oCell.Row ‘ присвоили номер строки
Next
Моим следующим вопросом было каким образом вернуть значение из функции. В vba есть два типа функций: sub и function. Разница в том, что function как раз и умеет возвращать значение, для этого в коде функции надо написать ИмяФункции = возвращаемое_значение:
Public Function insertRow()
Dim position As Integer
‘…
insertRow = position ‘возвращаемое значение
End Function
После написания нескольких функций во встроенном редакторе, меня нешуточно заинтересовало, как перенести строку кода на следующую физическую строку. Если посреди строки кода нажать enter, кроме ошибки мы ничего не добьемся. Оказывается, надо использовать «_»:
If ActiveSheet.Index = 1 _
Or ActiveSheet.Index = 4 Then
‘ действия
End If
Нарочно и не придумаешь.
Помимо массивов, в VBA следует обратить внимание на коллекции. Работать с ними просто:
Dim сoll As New Collection
сoll.Add («элемент1»)‘добавляем элемент
сoll.Add («элемент2»)‘размер увеличивается динамически
‘ по умолчанию индекс начинается с 1, можно изменить
MsgBox (сoll.Item(1)) ‘элемент1
сoll.Remove (1) ‘ удаляем первый элемент
MsgBox (сoll.Count)‘выводим размер коллекции
Чтобы когда мы творим беспредел с нашим excel-файликом посредством vba, он предательски не корчился и напоказ не дергал строками и столбцами перед пользователем (например, при удалении и скрытии интервалов), можно отключать прорисовку:
Application.ScreenUpdating = False ‘ значение True включает
Автоматическая нумерация строк таблицы
Одной из первых моих мелких подзадач стала нумерация строк таблицы. Можно, конечно, при добавлении строки вставлять номер функцией, но хотелось по возможности чтобы это происходило само собой. Как выяснилось, нумеровать ячейки можно формулой =СТРОКА(ячейка), которая возвращает номер строки ячейки-параметра. Например, формула =СТРОКА(А1), вставленная в произвольную ячейку, выведет 1, строка =СТРОКА(А3) — цифру 3.
Таким образом, при копировании формулы в следующую строку нумерация таблицы продолжится:
‘проставляем формулу нумерации
Range(«A7»).FormulaR1C1 = Range(«A6»).FormulaR1C1
Если нужно также копировать формат ячейки, используется метод AutoFill.
Тут может возникнуть одна неприятность. Пусть наша последовательность ячеек с формулами начинается не с первой строки, а например, с третьей. Тогда в третьей строке будет формула =Строка(А1), во второй — =Строка(А2), в 10 — =Строка(А10) и т.д. Тогда при удалении строки 6 формула в теперешней строке 9 выдаст ошибку «неверная ссылка».
Это потому, что формула в строке А9 крайне смущена тем, что ссылается на удаленную ячейку. Поэтому если строки в таблице буду удаляться и добавляться, лучше сделать ссылку на ячейки другой страницы (=СТРОКА(Лист2!А1)).
Кнопки вставки и удаления строк
Также неочевидными оказались некоторые нюансы при вставке и удалении строк в таблице по нажатию на соответствующие кнопки (под таблицей будем понимать визуально выделенный диапазон ячеек, содержащий связанные по смыслу данные). Например, у нас на листе есть две таблицы, у каждой есть кнопка «добавить строку». При добавлении для каждой строки появляется кнопка «удалить».
Само по себе добавление и удаление по имени кнопки сложности не представляет:
Private Sub InsertButton1_Click(position as Integer)
‘вставка строки перед строкой с номером position
Rows(position).Insert
End Sub
Private Sub DeleteButton1_Click()
Dim position as Integer
‘ ищем номер строки кнопки удаления
position = _ ActiveSheet.Shapes(Application.Caller).TopLeftCell.Row
‘ удаление кнопки
ActiveSheet.Shapes(Application.Caller).Delete
‘ удаление строки с номером position
Rows(position).Delete
End Sub
Для нахождения позиции первой строки таблицы будем использовать именованные ячейки.
Но вот проблемка, которую по внимательности можно сразу и не разглядеть. Вот мы добавили некоторое количество строк с кнопками в обеих таблицах, потом поудаляли и снова добавили. Возникает ситуация, при которой у нас на листе появляются кнопки с одинаковым именем (т.к. они создавались на строке с одним и тем же номером). В связи с этим возникают проблемы при их удалении (удаляется последняя кнопка из всех с одинаковым именем).
Нам нужен уникальный идентификатор. Чтобы не заниматься генерацией уникальных хэшей, можно, например, использовать имяКнопки_номерСтроки_текущаяДата.
newButtonId = «delButton» & insertedRowNumber & Now()
Маловероятно, что пользователь создаст за секунду более одной строки (в комплекте с кнопкой ее удаления) с одинаковым начальным номером строки.
Проверка листа на правильное заполнение
Задача состоит в том, чтобы при сохранении файла проверять содержимое ячеек на всех листах и если какие-то помеченные как обязательные остались незаполненными, выдавать предупреждение пользователю. Для визуального выделения в зависимости от результата проверки содержимого ячеек на соответствие требованиям в excel можно использовать условное форматирование. Казалось бы, все просто: если мы используем условное форматирование и незаполненные ячейки (или неправильно заполненные, которые мы не смогли проверить при вводе с помощью меню «проверка данных») подкрашиваем, например, красным, то при сохранении все что нам нужно сделать — пройтись по всем ячейкам и проверить их цвет.
Но проблема вот в чем — если мы берем colorIndex или color ячейки, нам возвращается исходный цвет, не тот, на который он изменился при помощи условного форматирования.
В голову пришло два слегка костыльных способа решения (и честно говоря, если судить по форумам с обсуждением подобных проблем, кажется, что применительно к Excel по-другому их и не решишь).
Первый способ: в одном столбце, напротив каждой строки таблицы, формулой вычисляется, правильно ли заполнена строка. Тогда, проходясь по этому столбцу, мы можем сказать, в какой строке проблема. А при помощи условного форматирования проблемные ячейки могут подсвечиваться красным. Этого достаточно, если не надо точно знать, в какой ячейке проблема. При необходимости можно дальше обрабатывать эту строку по ячейкам. Способ хорош, если разные строки проверяются на разные условия (даже в рамках одной таблицы).
usedRows = ActiveSheet.UsedRange.Rows.Count
usedCols = ActiveSheet.UsedRange.Columns.Count
‘ переводим последний столбец из цифры в букву
usedColsInNumb = Split(Cells(1, usedCols).Address, «$»)(1)
For Each cell In ActiveSheet.Range(«A1:» & usedColsInNumb & usedRows).Cells
‘ если колонка с результатом формулы проверки заполнена
If Len(cell.Value) > 0 Then
‘ если строка неправильно заполнена
If cell.Value = «ЛОЖЬ» Then
‘ сохраняем в массив, отражающий правильность
‘ заполнения каждой строки
notFilled(cell.Row) = True
End If
End If
Next
Второй способ: изначально ячейки, значения которых необходимо проверять, выделять цветом. Далее, проходясь по всем ячейкам, искать выделенные и проверять на соответствие условиям. Тут также при помощи условного форматирования проблемные ячейки могут подсвечиваться красным. Этот способ проще, если все ячейки проверяем на одни и те же условия, иначе надо вводить несколько цветов или как-то иначе метить ячейки. Например, при помощи именованных диапазонов для каждой области с одинаковой проверкой.
For Each cell In Sheets(i).Range(«A2:» & usedColsInNumb & usedRows).Cells
‘ проверяем выделенные цветом ячейки на пустоту
If cell.Interior.Color = Sheets(5).Cells(1, 6).Interior.Color And Len(cell.Value) = 0 Then
‘ обработка незаполненной ячейки
End If
Next
Тут надо учитывать, что при использовании объединенных ячеек Excel искренне полагает, что содержимое находится в первой из них. Поэтому проверять на пустоту нужно только первую ячейку из диапазона.
‘ ищем смежные с текущей ячейки в виде интервала, eg «A1:B10»
mergedInterv = Cells(cell.Row, cell.Column).MergeArea.Address(RowAbsolute:=False, ColumnAbsolute:=False)
‘ разбиваем адрес на первую и последнюю из смежных
splitAr = Split(mergedInt, «:»)
‘ если текущая ячейка входит в объединенные
If UBound(splitAr) = 1 Then
splitAr2 = Split(cell.Address, «$»)
‘ если первая из диапазона смежных равна текущей ячейке
If splitAr(0) = splitAr2(1) & splitAr2(2) Then
‘ обработка незаполненной смежной ячейки
End If
Else
‘ если текущая ячейка не является смежной
End If
При нахождении последней используемой строки или столбца следует учесть, что если вы каким-то образом успели «поиспользовать» интервал (пусть даже выделили и очистили формат), то он все равно считается использованным. Придется удалить эти строки и столбцы.
Прилагаю демо-эксельку. Тестировалась в русифицированном MS Excel 2007.