I’m designing a dynamic buffer for outgoing messages. The data structure takes the form of a queue of nodes that have a Byte Array buffer as a member. Unfortunately in VBA, Arrays cannot be public members of a class.
For example, this is a no-no and will not compile:
'clsTest
Public Buffer() As Byte
You will get the following error: «Constants, fixed-length strings, arrays, user-defined types and Declare statements not allowed as Public members of object modules»
Well, that’s fine, I’ll just make it a private member with public Property accessors…
'clsTest
Private m_Buffer() As Byte
Public Property Let Buffer(buf() As Byte)
m_Buffer = buf
End Property
Public Property Get Buffer() As Byte()
Buffer = m_Buffer
End Property
…and then a few tests in a module to make sure it works:
'mdlMain
Public Sub Main()
Dim buf() As Byte
ReDim buf(0 To 4)
buf(0) = 1
buf(1) = 2
buf(2) = 3
buf(3) = 4
Dim oBuffer As clsTest
Set oBuffer = New clsTest
'Test #1, the assignment
oBuffer.Buffer = buf 'Success!
'Test #2, get the value of an index in the array
' Debug.Print oBuffer.Buffer(2) 'Fail
Debug.Print oBuffer.Buffer()(2) 'Success! This is from GSerg's comment
'Test #3, change the value of an index in the array and verify that it is actually modified
oBuffer.Buffer()(2) = 27
Debug.Print oBuffer.Buffer()(2) 'Fail, diplays "3" in the immediate window
End Sub
Test #1 works fine, but Test #2 breaks, Buffer
is highlighted, and the error message is «Wrong number of arguments or invalid property assignment»
Test #2 now works! GSerg points out that in order to call the Property Get Buffer()
correctly and also refer to a specific index in the buffer, TWO sets of parenthesis are necessary: oBuffer.Buffer()(2)
Test #3 fails — the original value of 3 is printed to the Immediate window. GSerg pointed out in his comment that the Public Property Get Buffer()
only returns a copy and not the actual class member array, so modifications are lost.
How can this third issue be resolved make the class member array work as expected?
(I should clarify that the general question is «VBA doesn’t allow arrays to be public members of classes. How can I get around this to have an array member of a class that behaves as if it was for all practical purposes including: #1 assigning the array, #2 getting values from the array, #3 assigning values in the array and #4 using the array directly in a call to CopyMemory
(#3 and #4 are nearly equivalent)?)»
AndreA SN 1014 / 118 / 2 Регистрация: 26.08.2011 Сообщений: 1,113 Записей в блоге: 2 |
||||||||
1 |
||||||||
Передать массив из класса в головную программу02.04.2012, 14:55. Показов 4224. Ответов 3 Метки нет (Все метки)
В программе используется класс. Как внутри класса создать массив данных — доступный в головной программе? Здесь в классе создается массив mass1 и наполняется данными
Здесь вызывается процедура класса и делается попытка перезаписать массив, созданный в классе, в другой массив
Каким способом можно перезаписать содержимое mass1 в mass2?
0 |
mc-black 2784 / 716 / 106 Регистрация: 04.02.2011 Сообщений: 1,443 |
||||||||
02.04.2012, 15:43 |
2 |
|||||||
Передать массив из класса в основную программу можно через Свойство класса. Ниже пример создания свойства класса типа массив целых чисел, доступного только для чтения и пример использования этого свойства в программе (делается копирование массива в другой массив основной программы): 1. Класс myClass
2. Стандартный модуль
2 |
аналитика здесь больше нет… 3372 / 1670 / 184 Регистрация: 03.02.2010 Сообщений: 1,219 |
||||||||
02.04.2012, 15:47 |
3 |
|||||||
Сообщение было отмечено как решение Решение
3 |
1014 / 118 / 2 Регистрация: 26.08.2011 Сообщений: 1,113 Записей в блоге: 2 |
|
02.04.2012, 16:13 [ТС] |
4 |
mc-black, вот только никого эта половина ответа не устраивает Вопрос — дверь. Ответ — ключ от двери. Пока тебе ключ не дадут — будешь . Как только дали ключ — понимаешь, что . Таков удел всех самоучек Спасибо за помощь
0 |
My problem should be really simple: I have a class, called CArticle, and I want it to have a property L, which is an array.
I pretty new to classes and so far I have used them only in Matlab, which is a lot easier. Currently my class looks like this:
Private pName As String
Public Property Get Name() As String Name = pName End Property
Public Property Let Name(Value As String) pName = Value End Property
This is the Name property for my article, so MyArticle.Name works. I am having trouble with MyArticle.L, where L is an array. How do I write arrays to my class, and how do I read arrays from my class? I tried this:
Private pL() As Double
Public Property Get L() As Double L = pL End Property
Public Property Let L(Value() As Double) pL = Value() End Property
Which gives the following compile error:
Definitions of property procedures for the same property are inconsistent, or property procedure has an optional parameter, a ParamArray, or an invalid Set final parameter
I just want arrays in my class, but I can’t find any good tutorial on how to achieve this. If someone can redirect me to an extensive tutorial on classes in VBA, that would be fantastic.
Массив в качестве свойства класса. Обращение к элементу. |
||||||||
Ответить |
||||||||
Ответить |
||||||||
Ответить |
||||||||
Ответить |
||||||||
Ответить |
Уровень сложности
Простой
Время на прочтение
5 мин
Количество просмотров 3.4K
Когда я впервые встретил этот класс, подумал «Зачем? Ведь есть простые массивы». А потом попробовал и не представляю как жил без него раньше.
Начну сразу с примера
Предположим, на активном листе в столбце 1 находится список ФИО сотрудников.
Наша задача собрать в массив только уникальные ФИО и отсортировать его по убыванию (ну такая вот немного странная задача). Сначала решим ее без использования ArrayList
, а в конце сравним результат.
Для получения уникальных значений, создаем функцию GetDistinctItems
и в нее передаем столбец с ФИО. В самой функции пробегаем циклом For Each
по всем ФИО и добавляем уникальные в объект Buffer
(Dictionary
). Далее методом Keys
извлекаем элементы в дополнительную функцию DescendingSort
(используем сортировку пузырьком) и получаем отсортированные значения в переменную Sorted
, которую и возвращаем как результат функции.
Public Sub Main()
Dim FullNameColumn As Range
Set FullNameColumn = ActiveSheet.UsedRange.Columns(1) ' Получаем первый столбец.
Dim DistinctList As Variant
DistinctList = GetDistinctItems(FullNameColumn) ' Передаем диапазон в функцию.
Debug.Print Join(DistinctList, vbCrLf) ' Выводим результат.
End Sub
Public Function GetDistinctItems(ByRef Range As Range) As Variant
Dim Data As Variant: Data = Range.Value ' Преобразуем диапазон в массив.
Dim Buffer As Object: Set Buffer = CreateObject("Scripting.Dictionary") ' Создаем объект Dictionary.
Dim Item
For Each Item In Data
If Not Buffer.Exists(Item) Then Buffer.Add Item, Empty ' Проверяем наличие элемента и добавляем если отсутствует.
Next
Dim Sorted As Variant
Sorted = DescendingSort(Buffer.Keys()) ' Сортируем функцией DescendingSort.
GetDistinctItems = Sorted ' Возвращаем результат.
End Function
Public Function DescendingSort(ByRef Data As Variant) As Variant
Dim i As Long
For i = LBound(Data) To UBound(Data) - 1
Dim j As Long
For j = i + 1 To UBound(Data)
If Data(i) < Data(j) Then
Dim Temp As Variant
Temp = Data(j)
Data(j) = Data(i)
Data(i) = Temp
End If
Next
Next
DescendingSort = Data
End Function
Тривиально? Вполне. Компактно? Ну в целом да, но в конце мы напишем еще более компактней, а заодно решим проблему написания новой функции, если вдруг результат нужно будет сортировать по возрастанию.
Что есть такое
Начнем с того, что ArrayList
это класс из пространства имен System.Collections
библиотеки mscorlib
, который реализует интерфейс IList
. Естественно, в VBA он несколько порезан в плане методов, иначе и быть не могло (например нет методов AddRange
или BinarySearch
). Но и тем не менее с ним можно (и нужно) работать.
По сути, это динамический массив. Его не нужно самостоятельно переопределять, чтобы изменить размерность, достаточно добавлять элементы с помощью метода Add
. Где-то я читал, что на низком уровне (да простят меня знатоки, я не знаю правильно ли я применяю это словосочетание здесь) есть свои нюансы в плане производительности, но, откровенно говоря, за все время использования этого объекта каких-либо проблем я не замечал и время работы макроса из-за него если и растет вообще, то совсем не критично.
В чем же сила брат удобство?
Как минимум в том, что это динамический массив. Вы просто добавляете элементы через метод и не нужно заморачиваться на тему ReDim
(и уж тем более Preserve
) и вычислений размеров будущего массива.
А дальше начинаются вкусняхи
Во-первых, мы можем выгрузить все элементы одним методом ToArray
. Как следует из названия, он преобразует все элементы объекта в обычный массив типа Variant
.
Во-вторых, мы можем составлять список уникальных значений, проверяя их наличие методом Contains
.
В-третьих, можно забыть про функцию UBound
, ведь у этого класса есть свойство Count
, которое, как не сложно догадаться, возвращает количество элементов помещенных в объект.
В-четвертых, есть возможность быстро отсортировать элементы как по возрастанию (метод Sort
), так и по убыванию (сначала используем метод Sort
, а после метод Reverse
).
Ну и быстро пробегаем по оставшимся свойствам:
Item(Index)
Предоставляет доступ к элементу по его индексу.
и методам:
IndexOf(Item, StartFrom)
Возвращает индекс элемента. Обязательный аргумент StartFrom
поможет найти каждый последующий индекс одинаковых элементов.
RemoveAt(Index)
Удаляет элемент по индексу.
Remove(Item)
Удаляет переданный элемент.
RemoveRange(StartPosition, Count)
Удаляет диапазон элементов. StartPosition
указывает на индекс первого элемента, Count
на количество элементов в удаляемом диапазоне.
Clear()
Удаляет все элементы.
Insert(Position, Item)
Добавляет элемент по заданной позиции.
Clone()
Создает копию объекта (по сути создает новый объект, а не возвращает ссылку на текущий).
Как создать это чудо
Создать объект класса ArrayList
можно с помощью функции CreateObject
:
Dim List As Object
Set List = CreateObject("System.Collections.ArrayList")
или через Tools -> Reference подключить библиотеку mscorlib.dll, а дальше создавать как обычный объект:
Dim List As New ArrayList
Минус и той и другой привязки в том, что интерфейс объекта вы не получите. Причину лично я не знаю, но почему-то VBA в Excel (больше нигде не проверял) не видит свойства и методы этого класса (в поздней привязке их и так нет ни у какого объекта, так как тип переменной Object
, а вот в ранней обычно есть).
Можно, конечно, получить часть интерфейса, объявив переменную с типом IList
и уже после этого присвоить ей инстанс ArrayList
, но тем самым мы потеряем бОльшую часть функционала, например методы Sort
, ToArray
, Reverse
.
Вернемся в начало
Помните наш пример? Предлагаю решение с новыми знаниями.
Теперь мы добавляем уникальные значения в объект Buffer
(ArrayList
), перед этим проверяя методом Contains
наличие ФИО в списке элементов. По окончанию цикла применяем метод Sort
и Reverse
для получения списка по убыванию. Выгружаем результат методом ToArray
. Согласитесь на этот раз все гораздо компактней.
Public Sub Main()
Dim FullNameColumn As Range
Set FullNameColumn = ActiveSheet.UsedRange.Columns(1) ' Получаем первый столбец.
Dim DistinctList As Variant
DistinctList = GetDistinctItems(FullNameColumn) ' Передаем диапазон в функцию.
Debug.Print Join(DistinctList, vbCrLf) ' Выводим результат.
End Sub
Public Function GetDistinctItems(ByRef Range As Range) As Variant
Dim Data As Variant: Data = Range.Value ' Преобразуем диапазон в массив.
Dim Buffer As Object: Set Buffer = CreateObject("System.Collections.ArrayList") ' Создаем объект ArrayList.
Dim Item
For Each Item In Data
If Not Buffer.Contains(Item) Then Buffer.Add Item ' Проверяем наличие элемента и добавляем если отсутствует.
Next
Buffer.Sort: Buffer.Reverse ' Сортируем по возрастанию, а потом переворачиваем (по убыванию).
GetDistinctItems = Buffer.ToArray() ' Выгружаем в виде массива.
End Function
Что в итоге
В итоге мы имеем преимущество перед классом Collection
в том, что есть проверка на наличие элемента в списке (без танцев с бубном) и быстрая выгрузка в виде массива (без написания цикла).
Перед классом Dictionary
, пожалуй, преимущество в отсутствии необходимости прописывать ключи (если они изначально не нужны).
Ну и оба вышеперечисленных проигрывают в плане сортировки, добавления элементов по индексу и т.д.
В общем и целом, достаточно удобный в применении класс для работы с одномерными массивами. Конечно, получать данные из объекта Range
гораздо проще в обычный массив, но если нужно создавать новый (например в цикле), то, как по мне, ArrayList
превосходный вариант.
P.S. (проблемки, проблемушки)
Уже после написания статьи обратил внимание, что мой пример на чистом ПК не работает, появляется automation error -2146232576 при создании объекта ArrayList.
Судя по этому ответу, для работы mscorlib необходимо включить .NET Framework 3.5.
Сделать это можно через Панель управления -> Программы -> Включение или отключение компонентов Windows -> поставить галочку напротив .NET Framework 3.5 (включает .NET 2.0 и 3.0) после чего на ПК скачаются необходимые файлы для работы компонента.
Обязательно после проделанных действий перезагрузить Excel. У меня при установке выдал ошибку. Исправилось выключением Excel и повторным включением компонента.
К слову на моем рабочем ПК таких проблем не было, т.е. данный компонент уже был подключен организацией (или по умолчанию в ранних Windows, не знаю точно).
Спасибо, что прочитали до конца.
Как насчет применения этого класса? Пишите в комментариях!
А также, подписывайтесь на мой телеграмм.