Vba excel массив в классе

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 и наполняется данными

Visual Basic
1
2
3
4
5
6
7
8
9
10
myClass
 
Dim mass1() as Integer
 
Public Sub ClassSub()
Redim mass1(0)
For i=0 to 10
   Redim preserve mass1(ubound(mass1)+1)
   mass1(ubound(mass1)) = i
End sub

Здесь вызывается процедура класса и делается попытка перезаписать массив, созданный в классе, в другой массив

Visual Basic
1
2
3
4
5
6
7
8
Dim mass2() as Integer
 
Dim mc as New myClass
 
Sub Вызов()
Call mc.ClassSub
mass2=mass1 ' это не проходит
End Sub

Каким способом можно перезаписать содержимое mass1 в mass2?



0



mc-black

2784 / 716 / 106

Регистрация: 04.02.2011

Сообщений: 1,443

02.04.2012, 15:43

2

Передать массив из класса в основную программу можно через Свойство класса. Ниже пример создания свойства класса типа массив целых чисел, доступного только для чтения и пример использования этого свойства в программе (делается копирование массива в другой массив основной программы):

1. Класс myClass

Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Option Explicit
 
Dim mass1() As Integer
 
Private Sub Class_Initialize()
    Dim i As Long
    ReDim mass1(0 To 10)
    For i = 0 To 10
        mass1(i) = i
    Next
End Sub
 
Property Get myMass1() As Variant
    myMass1 = mass1
End Property

2. Стандартный модуль

Visual Basic
1
2
3
4
5
6
7
8
9
10
Option Explicit
 
Sub Вызов()
    Dim mass2
    Dim mc As New myClass
    
    mass2 = mc.myMass1
    
    Debug.Print UBound(mass2)
End Sub



2



аналитика

здесь больше нет…

3372 / 1670 / 184

Регистрация: 03.02.2010

Сообщений: 1,219

02.04.2012, 15:47

3

Лучший ответ Сообщение было отмечено как решение

Решение

Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
' myClass
 
Option Explicit
Private mass1() As Integer
 
Public Sub ClassSub()
    ReDim mass1(0)
    
    Dim i As Integer
    For i = 0 To 10
        ReDim Preserve mass1(UBound(mass1) + 1)
        mass1(UBound(mass1)) = i
    Next i
End Sub
 
Public Function getMass()
    getMass = mass1
End Function
Visual Basic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
' модуль
 
Option Explicit
 
Sub Вызов()
    Dim mc As New myClass
    mc.ClassSub
    
    Dim mass2() As Integer
    mass2 = mc.getMass
    Stop
 
    '    mass2 = mass1    ' это не проходит
End Sub



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.

Массив в качестве свойства класса. Обращение к элементу.

Roman777

Дата: Понедельник, 03.12.2018, 16:56 |
Сообщение № 1

Группа: Проверенные

Ранг: Ветеран

Сообщений: 980


Репутация:

127

±

Замечаний:
0% ±


Excel 2007, Excel 2013

Добрый всем день!
Столкнулся с небольшой проблемой.
Писал небольшой макрос, который парсит файл .frm (текстовый файл формы). Для удобства создал классы (в ВБА, наверное, 2й раз использую классы).
Собственно, проблема.
Есть класса CTR:
[vba]

Код

    Public Name As String
    Public Typ As String
    Public Attributes As Variant
    Public Props As Variant
    Public ExternData As Variant ‘äëÿ ProPipe — _Points
    Public Height As Long
    Public Width As Long
    Public Left As Long
    Public Top As Long
    Public Cntrls As Variant
    Public Parent As Variant
    Public Index As Long
    Public Group As Long

[/vba]
Где свойство Attributes — это массив экземпляров класса ATRBT:
[vba]

Код

    Public Name As String
    Public Value As Variant
    Public IsExternal As Boolean ‘ Внутренний атрибут — false, Внешний — true (хранится в бинарнике frx)
    Public Index As Long ‘ ссылка на строку для быстрого поиска

[/vba]

Ну а проблема возникла в такого рода коде:
[vba]

Код

msgbox (oCTRL.Attributes(1).Index)

[/vba] -вызывает ошибку «Property let procedure not defined and property get procedure did not return an object»
По ошибке нашёл на зарубежном сайте такое решение:
[vba]

Код

msgbox (oCTRL.Attributes()(1).Index)

[/vba]
Но в итоге всё-равно не понял, почему именно так. Подскажите, пожалуйста.


Много чего не знаю!!!!

Сообщение отредактировал Roman777Понедельник, 03.12.2018, 16:58

 

Ответить

StoTisteg

Дата: Понедельник, 03.12.2018, 22:11 |
Сообщение № 2

Группа: Авторы

Ранг: Старожил

Сообщений: 1161


Репутация:

103

±

Замечаний:
0% ±


Excel 2010

Roman777, я не юзал классы… Но Вы уверены, что [vba]

Код

    Public Name As String
    Public Attributes As Variant
    Public Height As Long
    Public Width As Long
    Public Left As Long
    Public Top As Long
    Public Parent As Variant
    Public Index As Long
    Public Group As Long

[/vba]это хорошая идея? Вы переопределили зарезервированные имена как паблики и теперь удивляетесь, что они как-то странно работают…


Интуитивно понятный код — это когда интуитивно понятно, что это код.

 

Ответить

anvg

Дата: Понедельник, 03.12.2018, 22:41 |
Сообщение № 3

Группа: Друзья

Ранг: Ветеран

Сообщений: 581


Репутация:

271

±

Замечаний:
0% ±


2016, 365

Доброе время суток

Вы переопределили зарезервированные имена как паблики

. Чем это зарезервированы? Обыкновенный в ООП полиморфизм. Вот отсутствие файла примера с кодом как работают с тем самым Attributes это да, плохая идея.

 

Ответить

Roman777

Дата: Вторник, 04.12.2018, 09:44 |
Сообщение № 4

Группа: Проверенные

Ранг: Ветеран

Сообщений: 980


Репутация:

127

±

Замечаний:
0% ±


Excel 2007, Excel 2013

StoTisteg,

Вы переопределили зарезервированные имена как паблики

это же не переопределение, а определение для моего конкретного класса. (или в VBA классы по умолчанию наследники чего-то?)
anvg, Сложно довольно выделить отдельно работу с Attributes. Мне проще скинуть весь файл. В нём ничего секретного. Но «небольшой», это был сарказм …) Посчитал, что разбираться долго нужно будет… а так мб сразу в глаза что-то бросится.
Я грешу на определение в классе Attributes As Variant, поэтому когда я создаю контрол:


Много чего не знаю!!!!

Сообщение отредактировал Roman777Вторник, 04.12.2018, 09:52

 

Ответить

Roman777

Дата: Вторник, 04.12.2018, 09:45 |
Сообщение № 5

Группа: Проверенные

Ранг: Ветеран

Сообщений: 980


Репутация:

127

±

Замечаний:
0% ±


Excel 2007, Excel 2013

где непосредственно Атрибуты создаются функцией, постепенно расширяющей этот массив:

, Attributes является Variant-ом. А его структура в VBA специфическая… всегда представляется двумерным массивом, вроде бы… (поправьте, плз).
Но способа определить в классе поле-массив, я не знаю как иначе, чем Variant.

[p.s.]почему весь текст сразу не влез? Писало, что ещё 5000+ символом осталось, но при попытке всё в 1 сообщение пихнуть, говорит что лимит превышаю…[/p.s.]

К сообщению приложен файл:

TST_.frx
(0.5 Kb)


Много чего не знаю!!!!

Сообщение отредактировал Roman777Вторник, 04.12.2018, 09:47

 

Ответить

Уровень сложности
Простой

Время на прочтение
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, не знаю точно).

Спасибо, что прочитали до конца.

Как насчет применения этого класса? Пишите в комментариях!
А также, подписывайтесь на мой 
телеграмм.

Like this post? Please share to your friends:
  • Vba excel копировать диапазон ячеек
  • Vba excel копирование ячейки на другой лист
  • Vba excel копирование формул
  • Vba excel копирование форматов ячеек excel
  • Vba excel копирование формата ячейки