Время на прочтение
7 мин
Количество просмотров 312K
Приветствую всех.
В этом посте я расскажу, что такое VBA и как с ним работать в Microsoft Excel 2007/2010 (для более старых версий изменяется лишь интерфейс — код, скорее всего, будет таким же) для автоматизации различной рутины.
VBA (Visual Basic for Applications) — это упрощенная версия Visual Basic, встроенная в множество продуктов линейки Microsoft Office. Она позволяет писать программы прямо в файле конкретного документа. Вам не требуется устанавливать различные IDE — всё, включая отладчик, уже есть в Excel.
Еще при помощи Visual Studio Tools for Office можно писать макросы на C# и также встраивать их. Спасибо, FireStorm.
Сразу скажу — писать на других языках (C++/Delphi/PHP) также возможно, но требуется научится читать, изменять и писать файлы офиса — встраивать в документы не получится. А интерфейсы Microsoft работают через COM. Чтобы вы поняли весь ужас, вот Hello World с использованием COM.
Поэтому, увы, будем учить Visual Basic.
Чуть-чуть подготовки и постановка задачи
Итак, поехали. Открываем Excel.
Для начала давайте добавим в Ribbon панель «Разработчик». В ней находятся кнопки, текстовые поля и пр. элементы для конструирования форм.
Появилась вкладка.
Теперь давайте подумаем, на каком примере мы будем изучать VBA. Недавно мне потребовалось красиво оформить прайс-лист, выглядевший, как таблица. Идём в гугл, набираем «прайс-лист» и качаем любой, который оформлен примерно так (не сочтите за рекламу, пожалуйста):
То есть требуется, чтобы было как минимум две группы, по которым можно объединить товары (в нашем случае это будут Тип и Производитель — в таком порядке). Для того, чтобы предложенный мною алгоритм работал корректно, отсортируйте товары так, чтобы товары из одной группы стояли подряд (сначала по Типу, потом по Производителю).
Результат, которого хотим добиться, выглядит примерно так:
Разумеется, если смотреть прайс только на компьютере, то можно добавить фильтры и будет гораздо удобнее искать нужный товар. Однако мы хотим научится кодить и задача вполне подходящая, не так ли?
Кодим
Для начала требуется создать кнопку, при нажатии на которую будет вызываться наша програма. Кнопки находятся в панели «Разработчик» и появляются по кнопке «Вставить». Вам нужен компонент формы «Кнопка». Нажали, поставили на любое место в листе. Далее, если не появилось окно назначения макроса, надо нажать правой кнопкой и выбрать пункт «Назначить макрос». Назовём его FormatPrice. Важно, чтобы перед именем макроса ничего не было — иначе он создастся в отдельном модуле, а не в пространстве имен книги. В этому случае вам будет недоступно быстрое обращение к выделенному листу. Нажимаем кнопку «Новый».
И вот мы в среде разработки VB. Также её можно вызвать из контекстного меню командой «Исходный текст»/«View code».
Перед вами окно с заглушкой процедуры. Можете его развернуть. Код должен выглядеть примерно так:
Sub FormatPrice()End Sub
Напишем Hello World:
Sub FormatPrice()
MsgBox "Hello World!"
End Sub
И запустим либо щелкнув по кнопке (предварительно сняв с неё выделение), либо клавишей F5 прямо из редактора.
Тут, пожалуй, следует отвлечься на небольшой ликбез по поводу синтаксиса VB. Кто его знает — может смело пропустить этот раздел до конца. Основное отличие Visual Basic от Pascal/C/Java в том, что команды разделяются не ;, а переносом строки или двоеточием (:), если очень хочется написать несколько команд в одну строку. Чтобы понять основные правила синтаксиса, приведу абстрактный код.
Примеры синтаксиса
' Процедура. Ничего не возвращает
' Перегрузка в VBA отсутствует
Sub foo(a As String, b As String)
' Exit Sub ' Это значит "выйти из процедуры"
MsgBox a + ";" + b
End Sub' Функция. Вовращает Integer
Function LengthSqr(x As Integer, y As Integer) As Integer
' Exit Function
LengthSqr = x * x + y * y
End FunctionSub FormatPrice()
Dim s1 As String, s2 As String
s1 = "str1"
s2 = "str2"
If s1 <> s2 Then
foo "123", "456" ' Скобки при вызове процедур запрещены
End IfDim res As sTRING ' Регистр в VB не важен. Впрочем, редактор Вас поправит
Dim i As Integer
' Цикл всегда состоит из нескольких строк
For i = 1 To 10
res = res + CStr(i) ' Конвертация чего угодно в String
If i = 5 Then Exit For
Next iDim x As Double
x = Val("1.234") ' Парсинг чисел
x = x + 10
MsgBox xOn Error Resume Next ' Обработка ошибок - игнорировать все ошибки
x = 5 / 0
MsgBox xOn Error GoTo Err ' При ошибке перейти к метке Err
x = 5 / 0
MsgBox "OK!"
GoTo ne
Err:
MsgBox
"Err!"
ne:
On Error GoTo 0 ' Отключаем обработку ошибок
' Циклы бывает, какие захотите
Do While True
Exit DoLoop 'While True
Do 'Until False
Exit Do
Loop Until False
' А вот при вызове функций, от которых хотим получить значение, скобки нужны.
' Val также умеет возвращать Integer
Select Case LengthSqr(Len("abc"), Val("4"))
Case 24
MsgBox "0"
Case 25
MsgBox "1"
Case 26
MsgBox "2"
End Select' Двухмерный массив.
' Можно также менять размеры командой ReDim (Preserve) - см. google
Dim arr(1 to 10, 5 to 6) As Integer
arr(1, 6) = 8Dim coll As New Collection
Dim coll2 As Collection
coll.Add "item", "key"
Set coll2 = coll ' Все присваивания объектов должны производится командой Set
MsgBox coll2("key")
Set coll2 = New Collection
MsgBox coll2.Count
End Sub
Грабли-1. При копировании кода из IDE (в английском Excel) есь текст конвертируется в 1252 Latin-1. Поэтому, если хотите сохранить русские комментарии — надо сохранить крокозябры как Latin-1, а потом открыть в 1251.
Грабли-2. Т.к. VB позволяет использовать необъявленные переменные, я всегда в начале кода (перед всеми процедурами) ставлю строчку Option Explicit. Эта директива запрещает интерпретатору заводить переменные самостоятельно.
Грабли-3. Глобальные переменные можно объявлять только до первой функции/процедуры. Локальные — в любом месте процедуры/функции.
Еще немного дополнительных функций, которые могут пригодится: InPos, Mid, Trim, LBound, UBound. Также ответы на все вопросы по поводу работы функций/их параметров можно получить в MSDN.
Надеюсь, что этого Вам хватит, чтобы не пугаться кода и самостоятельно написать какое-нибудь домашнее задание по информатике. По ходу поста я буду ненавязчиво знакомить Вас с новыми конструкциями.
Кодим много и под Excel
В этой части мы уже начнём кодить нечто, что умеет работать с нашими листами в Excel. Для начала создадим отдельный лист с именем result (лист с данными назовём data). Теперь, наверное, нужно этот лист очистить от того, что на нём есть. Также мы «выделим» лист с данными, чтобы каждый раз не писать длинное обращение к массиву с листами.
Sub FormatPrice()
Sheets("result").Cells.Clear
Sheets("data").Activate
End Sub
Работа с диапазонами ячеек
Вся работа в Excel VBA производится с диапазонами ячеек. Они создаются функцией Range и возвращают объект типа Range. У него есть всё необходимое для работы с данными и/или оформлением. Кстати сказать, свойство Cells листа — это тоже Range.
Примеры работы с Range
Sheets("result").Activate
Dim r As Range
Set r = Range("A1")
r.Value = "123"
Set r = Range("A3,A5")
r.Font.Color = vbRed
r.Value = "456"
Set r = Range("A6:A7")
r.Value = "=A1+A3"
Теперь давайте поймем алгоритм работы нашего кода. Итак, у каждой строчки листа data, начиная со второй, есть некоторые данные, которые нас не интересуют (ID, название и цена) и есть две вложенные группы, к которым она принадлежит (тип и производитель). Более того, эти строки отсортированы. Пока мы забудем про пропуски перед началом новой группы — так будет проще. Я предлагаю такой алгоритм:
- Считали группы из очередной строки.
- Пробегаемся по всем группам в порядке приоритета (вначале более крупные)
- Если текущая группа не совпадает, вызываем процедуру AddGroup(i, name), где i — номер группы (от номера текущей до максимума), name — её имя. Несколько вызовов необходимы, чтобы создать не только наш заголовок, но и всё более мелкие.
- После отрисовки всех необходимых заголовков делаем еще одну строку и заполняем её данными.
Для упрощения работы рекомендую определить следующие функции-сокращения:
Function GetCol(Col As Integer) As String
GetCol = Chr(Asc("A") + Col)
End FunctionFunction GetCellS(Sheet As String, Col As Integer, Row As Integer) As Range
Set GetCellS = Sheets(Sheet).Range(GetCol(Col) + CStr(Row))
End FunctionFunction GetCell(Col As Integer, Row As Integer) As Range
Set GetCell = Range(GetCol(Col) + CStr(Row))
End Function
Далее определим глобальную переменную «текущая строчка»: Dim CurRow As Integer. В начале процедуры её следует сделать равной единице. Еще нам потребуется переменная-«текущая строка в data», массив с именами групп текущей предыдущей строк. Потом можно написать цикл «пока первая ячейка в строке непуста».
Глобальные переменные
Option Explicit ' про эту строчку я уже рассказывал
Dim CurRow As Integer
Const GroupsCount As Integer = 2
Const DataCount As Integer = 3
FormatPrice
Sub FormatPrice()
Dim I As Integer ' строка в data
CurRow = 1
Dim Groups(1 To GroupsCount) As String
Dim PrGroups(1 To GroupsCount) As String
Sheets(
"data").Activate
I = 2
Do While True
If GetCell(0, I).Value = "" Then Exit Do
' ...
I = I + 1
Loop
End Sub
Теперь надо заполнить массив Groups:
На месте многоточия
Dim I2 As Integer
For I2 = 1 To GroupsCount
Groups(I2) = GetCell(I2, I)
Next I2
' ...
For I2 = 1 To GroupsCount ' VB не умеет копировать массивы
PrGroups(I2) = Groups(I2)
Next I2
I = I + 1
И создать заголовки:
На месте многоточия в предыдущем куске
For I2 = 1 To GroupsCount
If Groups(I2) <> PrGroups(I2) Then
Dim I3 As Integer
For I3 = I2 To GroupsCount
AddHeader I3, Groups(I3)
Next I3
Exit For
End If
Next I2
Не забудем про процедуру AddHeader:
Перед FormatPrice
Sub AddHeader(Ty As Integer, Name As String)
GetCellS("result", 1, CurRow).Value = Name
CurRow = CurRow + 1
End Sub
Теперь надо перенести всякую информацию в result
For I2 = 0 To DataCount - 1
GetCellS("result", I2, CurRow).Value = GetCell(I2, I)
Next I2
Подогнать столбцы по ширине и выбрать лист result для показа результата
После цикла в конце FormatPrice
Sheets("Result").Activate
Columns.AutoFit
Всё. Можно любоваться первой версией.
Некрасиво, но похоже. Давайте разбираться с форматированием. Сначала изменим процедуру AddHeader:
Sub AddHeader(Ty As Integer, Name As String)
Sheets("result").Range("A" + CStr(CurRow) + ":C" + CStr(CurRow)).Merge
' Чтобы не заводить переменную и не писать каждый раз длинный вызов
' можно воспользоваться блоком With
With GetCellS("result", 0, CurRow)
.Value = Name
.Font.Italic = True
.Font.Name = "Cambria"
Select Case Ty
Case 1 ' Тип
.Font.Bold = True
.Font.Size = 16
Case 2 ' Производитель
.Font.Size = 12
End Select
.HorizontalAlignment = xlCenter
End With
CurRow = CurRow + 1
End Sub
Уже лучше:
Осталось только сделать границы. Тут уже нам требуется работать со всеми объединёнными ячейками, иначе бордюр будет только у одной:
Поэтому чуть-чуть меняем код с добавлением стиля границ:
Sub AddHeader(Ty As Integer, Name As String)
With Sheets("result").Range("A" + CStr(CurRow) + ":C" + CStr(CurRow))
.Merge
.Value = Name
.Font.Italic = True
.Font.Name = "Cambria"
.HorizontalAlignment = xlCenterSelect Case Ty
Case 1 ' Тип
.Font.Bold = True
.Font.Size = 16
.Borders(xlTop).Weight = xlThick
Case 2 ' Производитель
.Font.Size = 12
.Borders(xlTop).Weight = xlMedium
End Select
.Borders(xlBottom).Weight = xlMedium ' По убыванию: xlThick, xlMedium, xlThin, xlHairline
End With
CurRow = CurRow + 1
End Sub
Осталось лишь добится пропусков перед началом новой группы. Это легко:
В начале FormatPrice
Dim I As Integer ' строка в data
CurRow = 0 ' чтобы не было пропуска в самом начале
Dim Groups(1 To GroupsCount) As String
В цикле расстановки заголовков
If Groups(I2) <> PrGroups(I2) Then
CurRow = CurRow + 1
Dim I3 As Integer
В точности то, что и хотели.
Надеюсь, что эта статья помогла вам немного освоится с программированием для Excel на VBA. Домашнее задание — добавить заголовки «ID, Название, Цена» в результат. Подсказка: CurRow = 0 CurRow = 1.
Файл можно скачать тут (min.us) или тут (Dropbox). Не забудьте разрешить исполнение макросов. Если кто-нибудь подскажет человеческих файлохостинг, залью туда.
Спасибо за внимание.
Буду рад конструктивной критике в комментариях.
UPD: Перезалил пример на Dropbox и min.us.
UPD2: На самом деле, при вызове процедуры с одним параметром скобки можно поставить. Либо использовать конструкцию Call Foo(«bar», 1, 2, 3) — тут скобки нужны постоянно.
Tuesday, January 12, 2010
Peltier Technical Services, Inc., Copyright © 2023, All rights reserved.
Today I am pleased to present a guest post written by my colleague Nicholas Hebb. Nick appears online frequently, commenting on Excel-related blogs and corresponding in the various forums.
By Nicholas Hebb
With the release of Office 2007, Microsoft rewrote the drawing tools from the ground up. On the plus side, this meant new shapes, new styles, and the addition of SmartArt. On the downside, they didn’t have time to incorporate AutoShape operations in their macro recorder prior to release. Thankfully, Excel 2010 has added macro support back, but like all macros the code generated is often bloated and relies heavily on the Selection object, which tends to hide the core objects in use. For the most part, the online help is good, but there is some information that doesn’t get explained in detail. This article attempts to provide a basic overview of working with AutoShapes using VBA and touch on some areas that are not covered extensively in the help documentation.
Definitions
Two properties of the Shape object will be used in the code samples below – Shape.Type and Shape.AutoShapeType. Excel has a broad range of shape Types consisting not only of AutoShapes, but also connectors, lines, pictures, charts, comments, and many other graphical items. For AutoShapes, the AutoShapeType property lets you get/set the type of shape as shown in the gallery image below.
Knowing when to check the Shape.Type property versus the Shape.AutoShapeType is very useful. For example, if the AutoShapeType value is -2, then for all practical purposes the shape is not an AutoShape. If the value is greater than 1, then the shape is one of the types display in the Shapes gallery. The tricky part comes when the AutoShapeType is 1, which equals the AutoShape constant msoShapeRectangle. It could be a Rectangle AutoShape, but it could also be anything shaped like a rectangle, such as a text box, a comment, or even a picture. So if the AutoShapeType evaluates to 1, then you also need to check the Type property.
Callouts are another special type of shape that can cause confusion. They are discussed more in the Miscellaneous Issues section below.
Accessing a Shape Object
Each worksheet contains a Shapes collection consisting of Shape objects. Like other collections in VBA, the Shape object is accessed either via its name or index number, as in:
ActiveSheet.Shapes("SHAPE_NAME")
or
ActiveSheet.Shapes(1)
Or, using the For…Each syntax:
Dim shp as Shape
For Each shp in ActiveSheet.Shapes
MsgBox shp.Name
Next
Adding an AutoShape
The syntax for adding a shape is:
Worksheet.Shapes.AddShape(AutoShapeType, Left, Top, Width, Height)
The AutoShapeType is a constant that ranges from 1 to 137 for Excel 2003 and earlier versions. Excel 2007 added shapes 139 through 183. AutoShapeTypes 125-136 are special AutoShapes. The online help file states that they support mouse over and click events, but that only applies when they are used in PowerPoint presentations. You can use them in Excel but they don’t have any special properties.
To see what the AutoShapeType constant is for each AutoShape, you can copy and paste the following code into the Excel Visual Basic Editor and run it (or download the sample file and run the macro). Not all the AutoShapes are available in the Shapes gallery, so this will also give you a look at some of the hidden ones.
Sub CreateAutoshapes()
Dim i As Integer
Dim t As Integer
Dim shp As Shape
t = 10
For i = 1 To 137
Set shp = ActiveSheet.Shapes.AddShape(i, 100, t, 60, 60)
shp.TextFrame.Characters.Text = i
t = t + 70
Next
' skip 138 - not supported
If CInt(Application.Version) >= 12 Then
For i = 139 To 183
Set shp = ActiveSheet.Shapes.AddShape(i, 100, t, 60, 60)
shp.TextFrame.Characters.Text = i
t = t + 70
Next
End If
End Sub
The Left, Top, Width, and Height parameters of AddShape() are specified in points. The origin is the top left corner of the worksheet, with the Left and Top values increasing to the right and down, respectively. Dealing with points on a worksheet isn’t intuitive, so if you prefer you can add a shape to a given range address by using code like this:
Function AddShapeToRange(ShapeType As MsoAutoShapeType, sAddress As String) As Shape
With ActiveSheet.Range(sAddress)
Set AddShapeToRange = ActiveSheet.Shapes.AddShape(ShapeType, _
.Left, .Top, .Width, .Height)
End With
End Function
Adding Text to an AutoShape
The Shape object has both a TextFrame and TextFrame2 members. The TextFrame2 member was added in Excel 2007 and gives better control over the formatting of the text. Because it is not backward compatible, I would recommend using the TextFrame object, as shown in the following code.
Sub AddFormattedTextToShape(oShape As Shape, sText As String)
If Len(sText) > 0 Then
With oShape.TextFrame
.Characters.Text = sText
.Characters.Font.Name = "Garamond"
.Characters.Font.Size = 12
.HorizontalAlignment = xlHAlignCenter
.VerticalAlignment = xlVAlignCenter
End With
End If
End Sub
Setting Border and Fill Styles
If you take advantage of the built-in styles for Excel 2007 and Excel 2010, setting the AutoShape formatting is ridiculously easy compared to Excel 2003 and previous versions. Excel 2007 introduced the ShapeStyle property with the 42 preset styles shown below.
The style numbers can be set using a simple line of code:
Shape.ShapeStyle = msoShapeStylePresetXX
Where Shape is the shape object and XX is the style number. The style numbers are shown in the image gallery in order from left to right, top to bottom. For example, the red button in the second row is msoShapeStylePreset10.
Adding Connectors and Lines
Connectors and lines are different objects in Excel. Connectors are special lines that “connect” to shapes, and if the shape is moved the connector stays connected and reroutes accordingly. Connectors cannot connect to other connectors, but they can connect to the end point of a line.
The syntax for adding a line is straightforward:
Worksheet.Shapes.AddLine(BeginX, BeginY, EndX, EndY)
…with all coordinates as Singles. Adding a connector is a bit more complex, since you typically want it to connect two shapes. The code below calculates the begin and end points, creates the connector, attaches the connector to the two shapes, then finally does a reroute to ensure the shortest path.
Function AddConnectorBetweenShapes(ConnectorType As MsoConnectorType, _
oBeginShape As Shape, oEndShape As Shape) As Shape
Const TOP_SIDE As Integer = 1
Const BOTTOM_SIDE As Integer = 3
Dim oConnector As Shape
Dim x1 As Single
Dim x2 As Single
Dim y1 As Single
Dim y2 As Single
With oBeginShape
x1 = .Left + .Width / 2
y1 = .Top + .Height
End With
With oEndShape
x2 = .Left + .Width / 2
y2 = .Top
End With
If CInt(Application.Version) < 12 Then
x2 = x2 - x1
y2 = y2 - y1
End If
Set oConnector = ActiveSheet.Shapes.AddConnector(ConnectorType, x1, y1, x2, y2)
oConnector.ConnectorFormat.BeginConnect oBeginShape, BOTTOM_SIDE
oConnector.ConnectorFormat.EndConnect oEndShape, TOP_SIDE
oConnector.RerouteConnections
Set AddConnectorBetweenShapes = oConnector
Set oConnector = Nothing
End Function
Several points worth mentioning are:
- The ConnectorType can be one of three constants – msoConnectorCurve, msoConnectorElbow, or msoConnectorStraight.
- The calculations for the beginning and ending points are not normally needed. You could put any values in for the AddConnector() function because once you call BeginConnect and EndConnect, the connector is attached to the shapes and the begin and end points get set automatically.
- How the end coordinates are specified is not consistent between Excel versions. Prior to Excel 2007, the end coordinates were relative to the begin coordinates. Starting in Excel 2007, the function now uses absolute coordinates.
- When you route a Connector to an AutoShape, you need to specify the side using a connection site constant. The constants are different for each AutoShape type, but generally they start with the top side = 1 and go counter-clockwise. For example, most rectangular shapes have connection site constants where top = 1, left = 2, bottom = 3, and right = 4.
- When you call the RerouteConnections() function, it sets the connection sides automatically in order to create the shortest path between the two shapes. So, unless you want a specific routing, you can get usually just guess at the connection site values then call RerouteConnections().
Formatting Connectors and Lines
Like AutoShapes, formatting Connectors and Lines is fairly straightforward in Excel 2007 and 2010. Here is a comparison of two formatting routines for older versions of Excel versus the newer versions:
Sub FormatConnector2003(oConnector As Shape)
With oConnector
If .Connector Or .Type = msoLine Then
' rough approximation of the Excel 2007 preset line style #17
.Line.EndArrowheadStyle = msoArrowheadTriangle
.Line.Weight = 2
.Line.ForeColor.RGB = RGB(192, 80, 77)
.Shadow.Type = msoShadow6
.Shadow.IncrementOffsetX -4.5
.Shadow.IncrementOffsetY -4.5
.Shadow.ForeColor.RGB = RGB(192, 192, 192)
.Shadow.Transparency = 0.5
.Visible = msoTrue
End If
End With
End Sub
Sub FormatConnector2007(oConnector As Shape)
With oConnector
If .Connector Or .Type = msoLine Then
.Line.EndArrowheadStyle = msoArrowheadTriangle
.ShapeStyle = msoLineStylePreset17
End If
End With
End Sub
The Connector property, used above, returns a Boolean indicating whether the shape is a connector. The Type=msoLine statement checks if the shape is a line. In this case the code will format both connectors and lines the same way, but at times you may want handle them separately. (NB: The Insert Shapes gallery of Excel 2007 only lets you add Connectors, not Lines. So unless you are dealing with legacy files or add Lines via code, testing Type=msoLine may never be an issue for you.)
Like the shape styles, you can format the line style by setting the ShapeStyle to one of the msoLineStylePresetXX values, where XX matches the order they appear in the style gallery (below) from left to right, top to bottom.
The Line object has several other members worth mentioning. In addition to the EndArrowheadStyle shown above, there is a corresponding BeginArrowheadStyle property, a DashStyle property, and also a Style property that lets you create double lines.
Miscellaneous Issues
Here are a few Excel 2007 issues with AutoShapes that are good to be aware of :
- If you copy a set of shapes, older versions of Office gave the option to Paste Special as editable AutoShapes in other Office applications. This option no longer exists in Office 2007. I haven’t tested this in Office 2010 beta yet.
- In Excel 2007 and the 2010 beta, changing the AutoShape type will disconnect any incoming or outgoing Connectors to a shape.
- Some print drivers (including PDF export handlers) do not handle printing thick Connectors well, e.g., elbow connectors may straighten. If this happens, either change the line thickness or try grouping all the shapes together prior to printing.
- The Arc is an AutoShape but needs to be treated as a line when setting the ShapeStyle property.
- Most of the styles are backward compatible except for styles 37-42 (the glossy button look in the bottom row of the style gallery). Styles 31-36 (the second row from bottom) do not render very well in Excel 2000.
- You can add Callouts using the AddShape() or AddCallout() methods, but the AddCallout() method will only let you add four types, two of which are actually the same. Callouts have AutoShapeType values in the range of 105-124. Even though they have AutoShapeType values, the Shape.Type property will return msoCallout – not msoAutoShape. Confused? Wait, there’s more. The callouts with AutoShapeTypes from 105-108 actually return a Shape.Type = msoAutoShape – not msoCallout.
Sample File
The sample file includes three demo sheets. The ShapeDemo sheet contains a macro to add two shapes, format them, then add a connector and format it. The Animation sheet has a simple macro showing how to move a shape around the sheet. The CreateAutoShapes sheet has a macro to create all AutoShapes available in your version of Excel.
The ShapeDemo routine has two function calls that are commented out – IlluminateShapeText2003() and IlluminateShapeText2007(). These subs add some gaudy formatting to the first letter of each text block, but they serve to highlight some of the differences between Excel 2007 and previous versions. Two parts of the code worth looking at are the HasText property and the formatting properties of TextFrame2. With the old TextFrame object, you would have to try accessing the TextFrame.Characters.Count property, which throws an error if no text exists. As for the formatting, the font colors in Excel 2003 and previous were limited to the colors in the pallet. In Excel 2007 and 2010, you can add reflections, drop shadows, and glow as well as set the color to whatever RGB value your heart desires.
Lastly, there is a module MCommon containing a sub that deletes all the AutoShapes on a sheet. In order not to delete the command buttons on the sheet (which are shapes too), it creates a ShapeRange of the AutoShapes and deletes that. The online help file shows the syntax for creating a ShapeRange when you know the names of the shapes at design time, but the syntax is a bit tricky when creating one dynamically. The DeleteAllAutoShapes() sub in the sample file shows you how to do this.
Click Here to Download the Sample File
About the Author
Nicholas Hebb is the founder of BreezeTree Software and author of FlowBreeze Flowchart Software, an add-in that automates flowcharting in Excel.
As typical small-business user, when my laptop broke I HAD to upgrade to Office 2010 (2007 isn’t on market anymore). Now I have a bunch of quick-and-dirty VBA to go with my old worksheets. None of them were programming masterpieces, but those worked and got work done with excel, and office 2007.
Now I tried to use my old stuff on Office 2010, and everything is just little bit off to be usable. For two days I searched web, trying to find any documentation for the end user, no luck. I found a lot of blogs aimed to professional programmers who are hoping to make bullet proof code to serve anything from Office 0 to Office2010, 32 bits to 64 bits (No 8 bits here?), but none of the articles talked about the main business, The End User with whole lot of homemade coding to survive a day.
How do I make my old 2007 VBA to work with 2010?
No, I don’t need it to backwards compatible with anything, it is enough that it works with my new computer, and new software (In my case it is 64 bits, if anyone wonders). I just need to know why my code doesn’t work anymore, and what to do about it.
What I really want is some pointers to real information, if there is any!
Of course I could copy-paste every piece of my code to programmers discussion forums, get laughed at, and after some weeks I might get some of them to work, but what I really need is real information written to basic VBA user. I need to learn this.
So What Is Your Problem?
What has changed?
Calendar active-X component was missing. I found solution: http://answers.microsoft.com/en-us/office/forum/officeversion_other-customize/missing-calendar-control/03ad5d05-ca3f-4081-9989-e757223ebdde now I just have to redo every calendar on all my forms… Thanks.
Textbox.Text wasn’t working, I found and run Microsoft Excel Code Compatibility Inspector (CII), and it showed that I have few thousand of those Textbox.Text elements that are “Deprecated” – “Potentially contains removed items the object model” – What?
It keeps getting better, same Microsoft software stated:
“TYPE: DEPRECATION
ITEM: [xls]SmartTagRecognizer.FullName
URL: http://go.microsoft.com/?linkid=9719761
CODE: MyFullName = ThisWorkbook.FullName””
No explanation… (Btw. only way to stop the Inspector and see the results was CRT-ALT-DEL –Stop…)
I visited the links given by the Inspector, no help there.
I run the Inspector few times, and It gave different results every time. Now that is interesting.
I hear your problem, now show me your problem
Where do I start. Here is one.
I have a form where user can make a new ‘appointment’, when saving, VBA creates piece of code and new Shape into calendar worksheet (and to Outlook, but that is not the point here). When user comes back and clicks that shape – button – Button runs a piece of code that was created. here is the code:
Private Sub myMacro2001_Click()
Dim meetingId As Integer
meetingId = 2001
Load formHours
Call formHours.selectMeeting(meetingId)
formHours.Show vbModeless
End Sub
Which is supposed, and did back in 2007, open up form formHours with information stored for that meeting 2001 (meetingId)
And the code in formHours starts as:
Sub selectMeeting (ByRef IdNo As Integer) ’Bring in the meeting ID
Meeting = IdNo
….
Not very elegant, but it worked. Doesn’t do that anymore.
If you can help me with this case, I appreciate, But it would be even better if you inform me where was I supposed to find the answer.
What has changed? Worked before, doesn’t anymore. What to do?
(PS. I know my code is crappy, but it worked… And I just would like to find out what has changed, and how do I need to change.
Ps.Ps Yes I know I should be asking Microsoft, but you know how that is…)
FOLLOWUP
After some kind advice from Barrowc and others I managed to fix some of problems. Changing Text.Text to Text.Value created bunch of new problems, some of Text-Values were used on formulas, and now I needed to change them Val(FooTextBox.Value)
What is really surprising is that 2010 seems very SLOW! I ran 2007 and 2010 side by side at office, and 2007 won hands down. One of my workers had already invoiced a customer using 2007, when 2010 was still opening up! Funny, since 2007 was running on AMD Athlon X2 Dual-Core with limited memory, and 2010 was on my new laptop with Core i7-740QM, 6 GB, both on win7-64. I surfed the net and didn’t find any complaints that VBA7 on Office 2010 is so much slower than VBA6 on Office2007. I don’t know if this is just my problem, but my employees voted for 2007 single minded…
-
11-28-2013, 08:46 AM
#1
Registered User
VBA compatibility issues: Excel 2007, 2010, 2013
Hi All,
I have been working for months on a series of projects that are due to be submitted imminently. The agreement with the client was that the VBA functionality will be compatible with Excel 2007, 2010 and 2013, both 32-bit and 64-bit versions.
To date, I have been working and testing in 2010 and 2013 (32-bit) and have had no problems but the tools have just been run on a client’s machine running 2007 and it’s started throwing up a load of errors I haven’t encountered before now. My problem is that I don’t have a copy of Excel 2007 to test it with, making identifying the cause of the conflicts very difficult. I have included error handling routines in the code so I am able to identify which subs the problems arise in and the type of error code being generated, but there are hundreds of subroutines and some of them are quite long so pinning the issues to specific lines is proving tricky.
My question is: is there any way of testing VBA code for compatibility with specific versions of Excel? Or at the very least, is it possible to use some form of emulator to replicate earlier versions of the software? I have an Office 365 subscription for what it’s worth…
Any suggestions would be very welcomed!
-
11-28-2013, 09:41 AM
#2
Re: VBA compatibility issues: Excel 2007, 2010, 2013
I have never come across such a thing but I am surprised you have so many errors between those versions. Are you using API calls in these workbooks?
-
11-28-2013, 09:56 AM
#3
Re: VBA compatibility issues: Excel 2007, 2010, 2013
Usually the best approach when writing cross version code is to start with the oldest version, but that ship has already sailed
You will need a copy of each version/bit application in order to test.
Note 32 and 64 bit can not be installed together. So you will also need a separate windows environment for that part, which you can create via a Virtualized pc.I assume you included costs for all those licences?
-
11-28-2013, 10:07 AM
#4
Registered User
Re: VBA compatibility issues: Excel 2007, 2010, 2013
I am using API calls but the errors do not derive from the module that contains them. It seems that (at least some of) the errors are stemming from the following subroutine:
Essentially (all the fiddly details aside) the routine takes an image control in a userform and loads a picture to it which is saved in a worksheet, embedded in a chart frame; the chart frame is first re-sized to match the dimensions of the control (this had to be done to accommodate different local resolution settings). The image in the worksheet is then exported to a static image file («~TempImg.xxx»), which is loaded to the control and then the temporary file is deleted. All pretty standard and straight forward and not a problem in Excel 2010 and 2013 but it 2007 it falls down.
It seems to fall down specifically when it is being called multiple times in quick succession — the same routine is used without a problem to load images at other points in the code. My suspicion is that the code is running faster than the excel 2007 system can export the image file, load it and delete it. I have tried using a sleep procedure to add a pause in the code to allow the system to «catch up», I have tried adding DoEvents (which, incidentally, I have never found to make a difference in any scenario) but to no avail.
The most common errors are 70 (permission denied) and 75 (path/file access error), which I think lends weight to my suspicion. Any thoughts?
-
11-28-2013, 10:16 AM
#5
Re: VBA compatibility issues: Excel 2007, 2010, 2013
You could try using the Temporary folder to export to in case the folder has protect priviliges.
You could try testing to make sure the file exists before loading it.
-
11-28-2013, 10:36 AM
#6
Registered User
Re: VBA compatibility issues: Excel 2007, 2010, 2013
Hi Andy,
I have 2010 and 2013 running on the same machine but that’s it. The cost of the licenses were not included (to be honest I have been shafted from beginning to end in terms of workload/money but that’s another issue and won’t be repeated!).
I guess my only solution would be to install Excel 2007 on top of the other two versions but I know that’s going to give me no end of trouble and is not really feasible now on the time scales involved. To be honest, when I started working on the project everyone was using 2007 or 2010, now a lot of the user base has upgraded to 2013, some of them 64-bit (stupid idea if you ask me but there we are) and I have spent a lot of time fighting fires in those versions — it’s all very frustrating to now be finding issues in 2007 (and inconsistent issues at that!).
I founded my company off the back of work similar to this, creating very complex interfaces in VBA to accommodate Excel statistical models (public sector work). I’m about to start transferring all of my products over to web platforms so hopefully my days of battling with these compatibility issues in Excel are drawing to a close…
Thanks, as always, for your valuable input.
AdLoki
-
11-28-2013, 10:44 AM
#7
Re: VBA compatibility issues: Excel 2007, 2010, 2013
Virtual PC will probably make testing, without messing with your pc, easier.
http://www.microsoft.com/en-gb/downl…s.aspx?id=3702
-
11-28-2013, 10:55 AM
#8
Registered User
Re: VBA compatibility issues: Excel 2007, 2010, 2013
Hi, yeah I had previously included the line
but it didn’t help. The directory in question should be ok as the user will have previously saved the parent file there.
I am now of the opinion that the procedure of exporting the image to a file, uploading to the VBA control and deleting the temporary file is causing a conflict because of system lag — the VBA is running faster than windows can process the file creation/deletion, resulting in the code effectively being bottlenecked and crashing. It could actually have more to do with the computer than the version of Excel. Interestingly, I just remoted onto a machine where the error was occurring every time and it doesn’t happen while I am connected via WebEx. I reckon the additional delay caused by the remote operation is just enough to allow the system to catch up. Does that sound feasible?
-
11-28-2013, 11:00 AM
#9
Re: VBA compatibility issues: Excel 2007, 2010, 2013
Maybe.
You could try FileExists test with a loop, you would need to pause (use the Sleep API for millisecond control) before additional checks. Also control the number of times the loop happens before your code continues with the display or error reports to the user.
-
11-28-2013, 11:06 AM
#10
Registered User
Re: VBA compatibility issues: Excel 2007, 2010, 2013
That’s a really good idea, I might just give that a go — thanks.
-
11-29-2013, 07:01 AM
#11
Registered User
Re: VBA compatibility issues: Excel 2007, 2010, 2013
Andy, I added the following code
It seems to have done the job, so thanks for the suggestion!