вторник, 25 мая 2010 г.

Табуляция в ячейке таблицы

Сегодня столкнулся с задачкой: выровнять по разделителю цифры в столбце таблицы. Всё бы ничего, но таблица была сложная, с множеством объединённых ячеек и на 1500 строк (да, бывают и такие таблицы).
Естественно, я не стал это делать вручную, а за 5 минут наваял простенький макрос, который сделал это за меня:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Sub CellAlignDecimal() Dim oTbl As Table 'Таблица, в которой работаем Dim oCell As Cell 'Ячейка таблицы Dim c As Integer 'Количество столбцов в таблице Set oTbl = Selection.Tables(1) 'Работаем в таблице, где находится курсор Set oCell = oTbl.Range.Cells(1) 'Первая ячейка таблицы c = oTbl.Columns.Count 'Перебор всех ячеек Do 'Обрабатываем ячейки только в последнем столбце If oCell.ColumnIndex = c Then With oCell.Range.ParagraphFormat .Alignment = wdAlignParagraphJustify 'Абзац выравниваем по ширине .FirstLineIndent = 0 'Отступ первой строки убираем .TabStops.ClearAll 'Убираем все отступы табуляции в ячейке 'Ставим позицию табуляции по разделителю посередине ячейки .TabStops.Add oCell.Width / 2, wdAlignTabDecimal, wdTabLeaderSpaces End With End If Set oCell = oCell.Next DoEvents Loop Until oCell Is Nothing End Sub

Макрос простенький, но имеет ряд нюансов работы с таблицей, на которые стоит обратить внимание:

  1. Перебор ячеек я делаю не циклом For или For Each, а циклом Do…Loop Until, выбирая следующую ячейку методом Next. Оказывается, этот способ работает быстрее для таблиц.

  2. Ячейки перебирать нужно во всей таблице, а не только в интересующем нас столбце. Это вызвано тем, что в таблице присутствуют ячейки, объединённые по столбцам и доступ к отдельным столбцам отсутствует. Именно поэтому, чтобы определить в нужном ли столбце находится текущая ячейка, я пользуюсь свойством ColumnIndex, сравнивая его с номером требуемого столбца. В моём случае это последний столбец таблицы. Также хочу обратить внимание, что я записал количество столбцов в отдельную переменную, чтобы не вычислять его каждый раз при сравнении.

  3. Установка табуляции в ячейке таблицы имеет свои особенности. Например, в моём случае табуляция проставилась так:

    Как видно, позиция табуляции находится чуть дальше 16,5 см, если верить линейке, но, если вызывать окно табуляции для данного абзаца, то там будет стоять цифра 1,62 см. Это вызвано тем, что позиция табуляции отсчитывается не от начала страницы, как можно подумать, глядя на линейку, а от начала текста, который этой табуляцией выравнивается. Поэтому в коде позицию табуляции я вычисляю по ширине ячейки (строка 19).

Из полезного, хочу отметить оператор DoEvents, который временно передаёт управление приложению, чтобы оно выполнило накопившиеся задачи и дало знать операционной системе, что всё в порядке. Этот оператор очень хорошо показывает себя именно в таких циклах, где перебор осуществляется внутри больших коллекций элементов.
Макрос сэкономил мне массу времени, но его можно усовершенствовать: добавить диалог ввода номера(ов) столбца(ов), в ячейки которого(ых) нужно обработать. Также, если передавать его неискушённому пользователю, следует предусмотреть проверку, что курсор находится в таблице.
И напоследок. Как быть, если нужно установить позицию табуляции не посередине ячейки, а в другом её месте? Как вычислить это место внутри ячейки? Прежде всего, нужно это место записать в переменную типа Range, а затем, используя метод Range.Information(wdHorizontalPositionRelativeToTextBoundary), получить положение этого диапазона относительно текста ячейки. Пример такого макроса можно посмотреть на форуме wordexpert.ru

вторник, 12 января 2010 г.

Измерение производительности макросов.

Когда поставленную задачу удаётся решить несколькими способами, то становится интересно, какой способ быстрее. «На глазок» это не всегда получается определить. Для этой цели лично для себя я написал простенький класс, измеряющий время работы того или иного процесса.
1 Option Explicit
2
3 Private Declare Function GetTickCount Lib "kernel32" () As Long
4
5 Private myTimer As Long
6 Private StartTime As Date
7 Private EndTime As Date
8
9 Public ProcName As String
10 Private Sub Class_Initialize()
11 myTimer = GetTickCount 'Инициализация счётчика времени работы
12 StartTime = Now 'Время начала измерений
13 End Sub
14
15 Private Sub Class_Terminate()
16 EndTime = Now 'Время конца измерений
17 MsgBox "Результаты замера производительности для """ & ProcName & """." & vbCr & "Начало измерений: " & FormatDateTime(StartTime, vbShortDate) & " " & FormatDateTime(StartTime, vbLongTime) & vbCr & _
18 "Конец измерений: " & FormatDateTime(EndTime, vbShortDate) & " " & FormatDateTime(EndTime, vbLongTime) & vbCr & _
19 "Время работы: " & (GetTickCount - myTimer) / 1000 & " сек.", vbOKOnly + vbInformation, "Результаты измерения производительности"
20 End Sub

Использовать этот класс очень просто. В модуле нужно объявить экземпляр класса, перед началом измеряемого процесса класс нужно инициализировать и задать имя процедуры или процесса. По окончании — уничтожить экземпляр класса.
1 Sub test()
2 Dim i As Long
3 Dim pm As PerformanceMeter
4
5 Set pm = New PerformanceMeter
6 pm.ProcName = "test"
7 For i = 0 To 10000000
8 Next i
9 Set pm = Nothing
10 End Sub