Хранение данных vba excel

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

Время на прочтение
8 мин

Количество просмотров 4.5K

Изображение отсюда: https://www.freepik.com/free-vector/settings-concept-illustration_9793179.htm#query=settings&position=2&from_view=search

Изображение отсюда: https://www.freepik.com/free-vector/settings-concept-illustration_9793179.htm#query=settings&position=2&from_view=search

Я часто пользуюсь конфигурацией при написании VBA макросов в Excel. Иногда она нужна в формах для сохранения настроек, иногда для сохранения каких-то получаемых в процессе выполнения макроса данных.

Долгое время я искал информацию о том как работать с конфигом в VBA.
Есть множество различных вариантов, от хранения конфигурации в коде, до выноса ее в отдельный файл.

Сегодня я хотел бы пошагово рассказать о том как я храню и обрабатываю конфигурацию макроса в книге Excel.

Создаем лист ConfigSheet

У меня под рукой был Excel 2010, но в данном случае версия не имеет значения.

Для начала создаем отдельный лист. Я назвал его config, но это не принципиально. Что же действительно принципиально, так это CodeName листа:

CodeName листа

CodeName листа

Если вы вдруг не знали, листы документа Excel в VBA – это, ни что иное, как объект класса Worksheet. Обращаемся к справке и видим у объекта Worksheet необходимое свойство (перезаписать его программно, несмотря на Read-only, можно, но об этом в другой раз):

Чтобы было проще обращаться к нашему Config листу, меняем ему значение поля (Name) в свойствах (если у вас их нет, нажмите F4 или View -> Properties Window, а если у вас нет структуры с проектом, нажмите Ctrl+R или View -> Project Explorer).

Таким образом, мы дополнительно защищаемся от ошибки в случае изменения обычного имени листа, а так же получаем автокомплит.

Вызываем автокомплит с помощью Ctrl+Space

Вызываем автокомплит с помощью Ctrl+Space

Кстати, так как лист – это объект, мы можем обращаться так же к его методам, полям и свойствам через точку, как обычно:

Вызываем методы и свойства

Вызываем методы и свойства

Этим мы и воспользуемся, но об этом чуть позже.

Создаем ListObject

Итак, как вы уже догадались, всю информацию мы будем сохранять в таблицу, а именно в объект ListObject.
Для этого на нашем листе создаем пустую таблицу с двумя столбцами Key и Value:

создаем таблицу

создаем таблицу

Теперь осталось в Конструкторе задать нашей таблице имя, и основа для хранения готова:

Получаем объект таблицы

Переходим к самому интересному. Писать код будем в модуле листа ConfigSheet.
Для начала создадим необходимые гетеры:

Public Property Get Table() As ListObject
    ' Свойство Read-Only для объекта таблицы.
    Set Table = Me.ListObjects("configTable")
End Property

Public Property Get Keys() As Range
    ' Свойство Read-Only для столбца ключей.
    Set Keys = Me.Table.ListColumns(KeyColumn).DataBodyRange
End Property

Public Property Get Values() As Range
    ' Свойство Read-Only для столбца значений.
    Set Values = Me.Table.ListColumns(ValueColumn).DataBodyRange
End Property

В свойство Table помещаем нашу таблицу, в Keys – столбец ключей, в Values – столбец значений.

Для удобства обращения к столбцам (и чтобы не хардкодить), прописываем Enum на уровне модуля:

Private Enum EConfigColumns
    KeyColumn = 1
    ValueColumn
End Enum

Сказал «не хардкодить» и захардкодил название таблицы 😀. Исправляюсь:

Option Explicit

Private Enum EConfigColumns
    KeyColumn = 1
    ValueColumn
End Enum

Private Const ConfigTable As String = "configTable"

Прописываем свойство Get Config

Осталось создать свойство Config, через которое будем записывать, получать и обновлять значения в таблице.

Для начала прописываем получение значения по ключу:

Public Property Get Config(ByVal Key As Variant) As Variant
    Dim i As Long
    For i = 1 To Me.Keys.Rows.Count
        If Key <> Me.Keys(i).Value Then GoTo NextKey
        Config = Me.Values(i).Value: Exit Property
NextKey:
    Next
End Property

Здесь все довольно просто – пробегаем циклом по ключам и сравниваем их с параметром Key, передаваемым пользователем. Как только находим нужный ключ, возвращаем соответствующее значение.

Так как мы работаем напрямую с объектом Range, это уже несколько замедляет работу макроса. Плюс ко всему, во время цикла макрос будет постоянно вызывать свойства Keys и Values чтобы получить их значения, что еще сильнее замедлит скорость выполнения.

Чтобы немного ускорить процесс и избежать постоянное обращение, можно создать переменные и передавать в них свойства:

Public Property Get Config(ByVal Key As Variant) As Variant
' Переменные, для хранения свойств.
    Dim Keys   As Range: Set Keys = Me.Keys
    Dim Values As Range: Set Values = Me.Values

    Dim i As Long
    For i = 1 To Me.Keys.Rows.Count
        If Key <> Me.Keys(i).Value Then GoTo NextKey
        Config = Me.Values(i).Value: Exit Property
NextKey:
    Next
End Property

Но это несколько загромождает код (а ведь у нас еще будет свойство Let), поэтому воспользуемся UDT (user defined type) и процедурой, которая будет его инициализировать.

Создаем тип TConfig в который помещаем все наши ранее созданные свойства (кроме, собственно, Config), а так же создаем приватную переменную This на уровне модуля:

Option Explicit

Private Enum EConfigColumns
    KeyColumn = 1
    ValueColumn
End Enum

Private Const ConfigTable As String = "configTable"

Private Type TConfig
    Table  As ListObject
    Keys   As Range
    Values As Range
End Type

Private This As TConfig

Очень важно чтобы и Type TConfig и переменная This были приватными, иначе на этапе компиляции возникнет ошибка.

Далее, прописываем небольшую процедуру InitThis, для присваивания значений нашему типу:

Public Sub InitThis()
    Set This.Table = Me.Table
    Set This.Keys = Me.Keys
    Set This.Values = Me.Values
End Sub

Теперь поправим свойство Config:

Public Property Get Config(ByVal Key As Variant) As Variant
    Me.InitThis
  
    Dim i As Long
    For i = 1 To This.Keys.Rows.Count
        If Key <> This.Keys(i).Value Then GoTo NextKey
        Config = This.Values(i).Value: Exit Property
NextKey:
    Next
End Property

Лаконично, не так ли?

Прописываем свойство Let Config

С установлением значений чуть иначе:

Public Property Let Config(ByVal Key As Variant, ByVal RHS As Variant)
    Me.InitThis
    If This.Keys Is Nothing Then This.Table.ListRows.Add: Me.InitThis

    Dim i As Long
    Do Until Key = This.Keys(i).Value
         i = i + 1
         If i > This.Keys.Rows.Count Then This.Table.ListRows.Add: Exit Do
    Loop

    This.Keys(i).Value = Key
    This.Values(i).Value = RHS
End Property

В параметры принимаем Key и RHS (Right Hand Side – по правую руку), для того чтобы можно было прописывать такую конструкцию:

ConfigSheet.Config("Key") = "Value"

В самом начале проверяем This.Keys на Nothing, т.к. если в таблице еще совсем нет значений, при попытке пробежаться циклом по столбцам выскочит ошибка.
Чтобы этого избежать, после проверки добавляем в таблицу пустую строку и заново инициализировать This. Только после этого можно будет свободно проходить по столбцам циклом.
Подобную проверку добавляем и в Get, но вместо добавления строки просто возвращаем сообщение «Нет данных в таблице конфигурации»:

Public Property Get Config(ByVal Key As Variant) As Variant
    Me.InitThis
    If This.Keys Is Nothing Then Config = "Нет данных в таблице конфигурации": Exit Property

    Dim i As Long
    For i = 1 To This.Keys.Rows.Count
        If Key <> This.Keys(i).Value Then GoTo NextKey
        Config = This.Values(i).Value: Exit Property
NextKey:
    Next
End Property

Далее, так же как и в Get части, циклом, только теперь Do Until, пробегаем по ключам конфига. При достижении максимального индекса – добавляем в конце новую строку и выходим из цикла. В конце присваиваем ключ и значение в соответствующие ячейки.

Удаляем пустые строки

При первом обращении к таблице у нас появится пустая строка. Чтобы ее убрать (и вообще, на всякий случай, каждый раз проверять, нет ли пустых строк в таблице), пропишем небольшую процедуру:

Public Sub DeleteEmptyRows()
    Me.InitThis
     
    Dim i As Long
    For i = This.Keys.Count To 1 Step -1
         If (IsEmpty(This.Keys(i).Value) And IsEmpty(This.Values(i).Value)) _
         Or (This.Keys(i).Value = vbNullString And This.Values(i).Value = vbNullString) Then This.Table.ListRows(i).Delete
    Next
End Sub

и добавим ее в уже написанную Let часть:

Public Property Let Config(ByVal Key As Variant, ByVal RHS As Variant)
    Me.InitThis
    If This.Keys Is Nothing Then This.Table.ListRows.Add: Me.InitThis
     
    Dim i As Long
    Do Until Key = This.Keys(i).Value
         i = i + 1
         If i > This.Keys.Rows.Count Then This.Table.ListRows.Add: Exit Do
    Loop
     
    This.Keys(i).Value = Key
    This.Values(i).Value = RHS
    Me.DeleteEmptyRows ' Проверяем на пустые строки.
End Property

Итоговый код

Исправил запись в Property Get Config, спасибо за наводку @qyix7z.

Option Explicit

Private Enum EConfigColumns
    KeyColumn = 1
    ValueColumn
End Enum

Private Const ConfigTable As String = "configTable"

Private Type TConfig
    Table  As ListObject
    Keys   As Range
    Values As Range
End Type

Private This As TConfig

Public Sub InitThis()
    Set This.Table = Me.Table
    Set This.Keys = Me.Keys
    Set This.Values = Me.Values
End Sub

Public Property Get Table() As ListObject
    ' Свойство Read-Only для объекта таблицы.
    Set Table = Me.ListObjects(ConfigTable)
End Property

Public Property Get Keys() As Range
    ' Свойство Read-Only для столбца ключей.
    Set Keys = Me.Table.ListColumns(KeyColumn).DataBodyRange
End Property

Public Property Get Values() As Range
    ' Свойство Read-Only для столбца значений.
    Set Values = Me.Table.ListColumns(ValueColumn).DataBodyRange
End Property

Public Property Get Config(ByVal Key As Variant) As Variant
    Me.InitThis
    If This.Keys Is Nothing Then Config = "Нет данных в таблице конфигурации": Exit Property

    Dim i As Long
    For i = 1 To This.Keys.Rows.Count
        If Key = This.Keys(i).Value Then Config = This.Values(i).Value: Exit Property
    Next
End Property

Public Property Let Config(ByVal Key As Variant, ByVal RHS As Variant)
    Me.InitThis
    If This.Keys Is Nothing Then This.Table.ListRows.Add: Me.InitThis

    Dim i As Long
    Do Until Key = This.Keys(i).Value
         i = i + 1
         If i > This.Keys.Rows.Count Then This.Table.ListRows.Add: Exit Do
    Loop

    This.Keys(i).Value = Key
    This.Values(i).Value = RHS
    Me.DeleteEmptyRows ' Проверяем на пустые строки.
End Property

Public Sub DeleteEmptyRows()
    Me.InitThis

    Dim i As Long
    For i = This.Keys.Count To 1 Step -1
         If (IsEmpty(This.Keys(i).Value) And IsEmpty(This.Values(i).Value)) _
         Or (This.Keys(i).Value = vbNullString And This.Values(i).Value = vbNullString) Then This.Table.ListRows(i).Delete
    Next
End Sub

Проверяем результат

Ну и наконец проверяем получившийся результат.

Записываем значение в конфиг:

Sub Test()
    ' Значение "Дневник VBAшника" записано в таблицу с ключом "ChanelName"
    ConfigSheet.Config("ChanelName") = "Дневник VBAшника"
End Sub

Считываем значение:

Sub Test()
    ' Распечатает: "Дневник VBAшника"
    Debug.Print ConfigSheet.Config("ChanelName")
End Sub

Меняем и считываем еще раз:

Sub Test()
    ConfigSheet.Config("ChanelName") = "https://t.me/VBAn_Diary"
    ' Распечатает: "https://t.me/VBAn_Diary"
    Debug.Print ConfigSheet.Config("ChanelName")
End Sub

Ну а дальше этот лист можно спрятать от глаз пользователей, защитить книгу и структуру, и спокойно работать с конфигом.

Уверен, что можно изменить подход и написать код иначе. Возможно у вас есть предложения или дополнения по статье. Буду рад любому отзыву. 🙂

Глава 7. Хранение и обработка информации.

В этой главе …

~ Использование переменных как именованных контейнеров для хранения самых различных видов данных

~ Объявление переменных в программном коде

~ Использование именованных констант

~ Представление значений с помощью выражений

~ Организация вычислений с помощью операторов VBA

~ Практическое использование различных типов данных для переменных и констант

Чтобы максимально раскрыть потенциал VBA, программам требуются переменные для хранения информации. Переменные позволяют создавать программы, изменяющие свое поведение в зависимости от условий. В этой главе достаточно подробно описаны приемы работы с переменными в VВА — программах. Особое внимание уделяется типам данных, которые могут храниться в переменных. Подобные вопросы рассматриваются также для констант (представляющих постоянные значения) и операторов (состоящих из знаков операций и ключевых слов и предназначенных для вычисления новых значений на основании уже известных).

Достаточно обширный материал этой главы все же не исчерпывает предмет обсуждения полностью. В главе 12 показано, как использовать переменные для работы с объектами, а в главе 13 дополнительно обсуждаются переменные в связи с массивами и пользовательскими типами данных.

Для всех примеров, представленных в этой главе, на сервере издательства Диалектика имеются вполне работоспособные тексты соответствующих процедур. Они доступны через Internet по адресу www.dialektika.com.

Работа с переменными

В сущности, переменная — это идентификационный ярлык для некоторого хранящегося в программе фрагмента информации.

В некотором смысле переменная похожа на жетон из театрального гардероба. Вы приходите в театр и сдаете свою шляпу в гардероб. Гардеробщик куда-то уносит вашу шляпу — вас не интересует куда, поскольку вы уверены, что она никуда не денется. Вместо шляпы вы получаете жетончик с номером.

После спектакля вы отдаете гардеробщику жетон и получаете свою шляпу. На следующий день тот же жетон выдадут в обмен на чью-то другую шляпу.

Вы говорите, что у вас нет шляпы? Так в ответ можно сказать, что до сих пор у вас не было и переменных.

С помощью переменных вы получаете возможность доступа по имени сразу к целому куску информации, вместо того чтобы работать с конкретными числами или символами. Более того, при этом не требуется знать, где и в какой форме хранятся нужные вам данные, — вам нужно знать только имя переменной. Наконец, информацию, которая хранится в переменной, можно в любое время заменить.

Именно последнее дает возможность создавать программы, автоматически реагирующие на изменение условий, поскольку программа может при необходимости проверить информацию, содержащуюся в переменной, и выяснить, удовлетворяет ли эта информация заданным вами критериям. Если критерии удовлетворяются, программа выполняет одни операторы, а если нет. то другие.

Строго говоря, переменная (т.е. контейнер для информации) и хранящаяся в этой переменной информация — это не одно и то же. Однако, наверное, найдется не слишком много людей, которые часто употребляют фразы типа информация, представляемая переменной х. Вполне привычно ссылаться на информацию просто по имени переменной, имея при этом в виду, что эта информация может меняться.

Читайте также

ГЛАВА 4 Обработка исключений

ГЛАВА 4
Обработка исключений
Основное внимание в данной главе сфокусировано на структурной обработке исключений (Structured Exception Handling, SEH), но наряду с этим обсуждены также обработчики управляющих сигналов консоли и векторная обработка исключений (Vectored Exception Handling, VEH).SEH

Глава 12 Обработка сигналов

Глава 12
Обработка сигналов
Сигналы — это простейшая форма межпроцессного взаимодействия в мире POSIX. Они позволят одному процессу быть прерванным асинхронным образом по инициативе другого процесса (или ядра) для того, чтобы обработать какое-то событие. Обработав сигнал,

Глава 33 Резервное копирование и хранение данных

Глава 33 Резервное копирование и хранение данных
Резервное копирование выполняется с целью получения копий данных, сохраняемых на случай их потери или разрушения. Подобные копии должны создаваться периодически, в соответствии с заранее установленным графиком. Схемы

Глава 18. Многопоточная обработка

Глава 18. Многопоточная обработка

Обычные приложения с графическим интерфейсом имеют один поток (thread) выполнения и производят в каждый момент времени одну операцию. Если пользователь через интерфейс пользователя вызывает продолжительную операцию, интерфейс, как

1. Информатика. Информация. Представление и обработка информации

1. Информатика. Информация. Представление и обработка информации
Информатика занимается формализованным представлением объектов и структур их взаимосвязей в различных областях науки, техники, производства. Для моделирования объектов и явлений используются различные

Обработка информации, представленной пользовательским типом данных

Обработка информации, представленной пользовательским типом данных
После объявления переменной пользовательского типа ее можно «начинять» информацией. При этом необходимо присвоить значение каждому из элементов, составляющих тип.А для этого нужно идентифицировать

Глава 10. Ввод/вывод и хранение данных

Глава 10. Ввод/вывод и хранение данных

На чистом диске можно искать бесконечно.
Томас Б. Стил младший
Вычислительные машины хороши для вычислений. В этой тавтологии больше смысла, чем кажется на первый взгляд. Если бы программа только потребляла процессорное время да

Глава 12 Многопоточная обработка

Глава 12
Многопоточная обработка

12.0. Введение
В данной главе даются рецепты написания многопоточных программ на C++ с использованием библиотеки Boost Threads, автором которой является Вильям Кемпф (William Kempf). Boost — это набор переносимых, высокопроизводительных библиотек с

Глава 18. Обработка

Глава 18.
Обработка
Затем OCR-система анализирует (определяет блоки распознавания, выделяет в тексте строки и отдельные символы) изображение и начинает распознавать каждый его символ.Целостное целенаправленное адаптивное распознаваниеРаспознавание печатного материала

Глава 10 Обработка

Глава 10 Обработка
Сделан последний кадр, съемка окончена, под рукой имеется подходящий для работы компьютер со всеми установленными на него необходимыми приложениями, и вы уже готовы к покорению пикселов. Подробному процессу компьютерной обработки фотографий и прочих

Глава 1 Обработка аудиозаписей

Глава 1 Обработка аудиозаписей
Программы, предназначенные для обработки аудиозаписей, как правило, имеют сходный пользовательский интерфейс. На экране мы видим волновую форму сигнала в графическом представлении: по вертикали – амплитуда, по горизонтали – время. Изгибы

Глава 9 Хранение файлов и резервное копирование

Глава 9 Хранение файлов и резервное копирование
Для создания копий, которые необходимы как для сохранения информации, так и для обмена ею между пользователями, используются различные устройства:• стримеры;• цифровые магнитофоны формата DAT (digital audio tape), ADAT (Alesis DAT), DCC (digital

Хранение информации в архиве

Хранение информации в архиве
Сервисы хранения информации в архиве предназначены для долговременного хранения и управления электронными документами и другой

Рейтинг: 2.60. Голосов: 5.

Запись от bedvit размещена 31.10.2017 в 10:32

Обновил(-а) bedvit 30.05.2022 в 23:27
(v.6)


В продолжении темы и темы.
Получилось просто и вполне удобно, решил сохранить в блоге. В дальнейшем возможно развитие, новые алгоритмы и новые версии.
Кратко: Хранение файлов и простенький файловый менеджер в файле(листе) Excel, Бинарное хранение данных в CustomProperty листа (Open FileName As Binary и Get, Put через байт-массив). Запуск с параметрами хранимых файлов.

Максимальная простота и переносимость — нет форм, модулей уровня проекта, классов и т.д., все в модуле листа «ByteSheet(Storage)». Переносим лист (переносятся и загруженные данные) в другой файл, готово! Или копируем код (загруженные данные НЕ переносятся) с листа «ByteSheet(Storage)» на свой лист — и ваш лист становится файловым менеджером!

Работа с файлами:
—> Как через МЕНЮ — работает стандартно через менеджер макросов (пользовательское стартовое меню «ByteSheet.START_MENU», в котором можно выбрать нужное действие и открыть нужное меню)

—> Так и используя ФУНКЦИИ напрямую в вашем проекте/надстройке, обычном файле Excel, поддерживающим макросы. Функции возвращают количество загруженных, выгруженных, удаленных файлов в случае успеха.

1.»DownloadF» — Загрузка любых файлов (в т.ч. архивов, которые можно распаковать автоматом при выгрузке — ZIP), поддерживается пакетная загрузка.

2.»UnloadF» — Выгрузка(в т.ч. пакетная)/Запуск файлов с параметрами. Поддерживается следующий функционал/параметры:
2.1 Выгрузка в выбранную папку, «по умолчанию» выгружаются в пользовательский «TEMP».
2.2 Выбор файла(ов) для выгрузки/запуска, «по умолчанию» выгружаются/запускаются все файлы.
2.3 Обновление файл(ов) в папке при распаковки, если он(и) там уже есть (выгружен(ы) ранее или есть с совпадающим
именем), по умолчанию: не обновляем
2.4 Распаковка из архива ZIP, по умолчанию: выгружаем архив, без распаковки
2.5 Открыть файл в Excel после распаковки, по умолчанию: не открываем
2.6 Запуск файл после распаковки, приложением «по умолчанию» (для архива — один файл в один архив, в обратном
случае, загрузится первый попавшийся из распакованного архива), по умолчанию: не запускаем
2.7 Запуск файла с нужными параметрами. Используется вместе с параметром 2.6 (аналогично командной строке с
параметрами, без указания самого файла — полный путь к файлу автоматом подставляется), по умолчанию: без
параметров.
2.8 Запуск файла, даже если он открыт в каком-либо процессе (запускать еще раз), по умолчанию: не запускаем.

Все эти параметры Optional, если их нет — функция выполняется с параметрами по умолчанию.

3.»DeleteF» — Удаление файлов, в т.ч. пакетное.

Ниже исходники (лист ByteSheet(Storage)) и пример использования — модуль «Test» (расчет числа Пи).
В загруженных файлах секретных данных и вирусов нет.

Результаты стресс-тестирования:
Загрузить (файлов — 432 шт), Выгрузить (файлов — 432 шт), Удалить (файлов — 432 шт)
Общий размер файлов — 1 334 МБ (1,33 ГБ)
Время выполнения всех трех операций в сумме — 25 сек.
Размер книги Excel с файлами — 1,2 ГБ в формате .xlsb

P.S.
Отдельная благодарность! Андрей VG, за любезно предоставленные алгоритмы, которые были несколько доработаны и дополнены для этих целей.

21/11/2017 Обновление версии: Loader_02
1. Процедуры переписаны в функции, которые возвращают при успешном выполнении, количество загруженных, удаленных, выгруженных файлов.
2. Добавлена пакетная загрузка файлов (выгрузка и удаление всех файлов уже было в первой версии) с корректным счетчиком (скриншот прилагаю).
3. Добавлено описание к коду и аргументам функций.
4. Добавлен запуск файлов как «приложением по умолчанию» так и через Excel.
5. Добавлен обработчик ошибок, с выводом сообщений об ошибке.
6. Добавлен запрос/вопрос пользователю на перезапись выгружаемых и загружаемых файлов, если они уже есть.

22/03/2018 Обновление версии: Loader_03
1. Добавил стартовое меню (скриншот прилагаю).
2. Добавил поддержку командной строки (на примере расчета числа Пи — программа упакована моя, вирусов нет.).
3. Изменил порядок параметров для функций на более логичный.

22/04/2018 Обновление версии: Loader_04
1. Добавил команду «ОТКРЫТЬ» в стартовом меню с поддержкой командной строки (скриншот прилагаю). Zip-файлы, при этом, сначала распаковываются, потом запускается первый файл в распакованной папке (поэтому рекомендуется хранить по одному файлу в архивах).
2. Изменил переход по листам с символов «<«, «>» на «-«,»+» — удобно использовать цифровой блок клавиатуры.
3. Добавил возврат в меню при неверно введенных данных.

07/06/2018 Обновление версии: Loader_05
1. Добавил проверку при запуске файла, открыт ли он в другом процессе с возможностью открыть повторно или не открывать (по умолчанию)
2. Исправил ошибку, возникающих в случаях когда имя выгружаемого файла совпадало с папкой и папку нужно было удалять.
3. Исправил ошибку, возникающих в случаях когда распаковывается один и тот же архив с обновлением данных (удалением старых) в целевой папке.
4. Исправлена ошибка возникающая при загрузке файлов размером в нечётное количество байтов.
5. Добавлена возможность указать при загрузке папку (добавил новый параметр в функцию загрузки), без указания файлов, тогда загружаются все файлы из папки.

30/05/2022 Обновление версии: Loader_06
1.Исправлениа ошибка выбора папки в функции DownloadF (ранее выбиралась как папка по умолчанию, в исправленной версии выбирается указанная папка явно)
2.Закомментировал в примере распаковку из .zip-файла и запуск .exe (иногда вызывает рефлексию антивируса, хотя вредоносного функционала там нет)

1 2018-12-18 00:55:04 (изменено: omegastripes, 2019-01-18 03:08:56)

  • omegastripes
  • Разработчик
  • Неактивен
  • Рейтинг : [8|0]

Тема: VBA Excel Сохранение пользовательских данных в CustomXMLParts

Всем привет! Хочу поделиться кодом, позволяющим хранить произвольные текстовые данные в коллекции CustomXMLParts книги Excel версии 2007 и выше. Данные сохраняются непосредственно в файле книги. С одной стороны, пользователь не имеет прямого доступа ко всем элементам этой коллекции, и как следствие, сохраняемые таким образом данные не подвержены случайной утере или порче, а также не болтаются на виду и не привлекают особого внимания. С другой стороны, их легко контролировать, достаточно просто открыть файл .xlsx как архив, и зайти в одноименную папку. Речь, конечно, не идет о хранении конфиденциальной информации, но, например, для сохранения определенных настроек (путей к папкам или сетевым ресурсам, значений контролов пользовательской формы перед закрытием, в конце концов, JS кода для выполнения в htmlfile контейнере) вполне сгодится. Максимальный объем не тестировал, предположительно, речь порядка о сотне мегабайт. Бинарные данные следует предварительно конвертировать в base64 или т. п. Данные относительно неплохо сжимаются zip’ом.

Взаимодействие с сохраняемыми данными реализовано подобно обычному словарю Scripting.Dictionary:
AddItem — добавление записи ключ — значение, если такой ключ уже есть — запись удаляется и создается заново;
AddItems — добавление множества записей ключ — значение, передаваемых в словаре;
GetItem — получения значения по заданному ключу;
GetItems — получение всех записей в виде словаря;
ItemExists — проверка наличия записи с заданным ключом;
RemoveItem — удаление записи с заданным ключом;
RemoveCXStorage — полное удаление из коллекции CustomXMLParts элемента CustomXMLPart, используемого для хранения данных.

Приведенный ниже код следует сохранить в отдельный модуль CXStorage:

Option Explicit

Private Function GetItemsNode()
    
    With ThisWorkbook.CustomXMLParts.SelectByNamespace("CXStorage")
        If .Count = 0 Then ThisWorkbook.CustomXMLParts.Add "<cxs:root xmlns:cxs='CXStorage'><items/></cxs:root>"
        Set GetItemsNode = .Item(1).DocumentElement.FirstChild
    End With
    
End Function

Sub AddItem(sName, sValue)
    
    With GetItemsNode()
        With .SelectNodes("//item[@name='" & sName & "']")
            If .Count > 0 Then .Item(1).Delete
        End With
        .AppendChildNode "item", , msoCustomXMLNodeElement
        With .LastChild
            .AppendChildNode "name", , msoCustomXMLNodeAttribute, sName
            .AppendChildNode , , msoCustomXMLNodeText, sValue
        End With
    End With
    
End Sub

Sub AddItems(cItems)
    
    Dim sName
    
    With GetItemsNode()
        For Each sName In cItems
            With .SelectNodes("//item[@name='" & sName & "']")
                If .Count > 0 Then .Item(1).Delete
            End With
            .AppendChildNode "item", , msoCustomXMLNodeElement
            With .LastChild
                .AppendChildNode "name", , msoCustomXMLNodeAttribute, sName
                .AppendChildNode , , msoCustomXMLNodeText, cItems(sName)
            End With
        Next
    End With
    
End Sub

Function ItemExists(sName)
    
    ItemExists = Not (GetItemsNode().SelectSingleNode("//item[@name='" & sName & "']") Is Nothing)
    
End Function

Function GetItem(sName)
    
    With GetItemsNode().SelectNodes("//item[@name='" & sName & "']")
        If .Count > 0 Then
            If Not (.Item(1).FirstChild Is Nothing) Then
                GetItem = .Item(1).FirstChild.NodeValue
            End If
        End If
    End With
    
End Function


Function GetItems()
    
    Dim oNode
    
    Set GetItems = CreateObject("Scripting.Dictionary")
    For Each oNode In GetItemsNode().SelectNodes("//item")
        GetItems.Item(oNode.Attributes.Item(1).NodeValue) = oNode.FirstChild.NodeValue
    Next
    
End Function

Sub RemoveItem(sName)
    
    With GetItemsNode().SelectNodes("//item[@name='" & sName & "']")
        If .Count > 0 Then .Item(1).Delete
    End With
    
End Sub

Sub RemoveCXStorage()
    
    Dim oPart
    
    For Each oPart In ThisWorkbook.CustomXMLParts.SelectByNamespace("CXStorage")
        oPart.Delete
    Next
    
End Sub

И простейший код для тестирования в другом модуле:

Option Explicit

Sub Test1()
    
    Dim sKey
    
    RemoveCXStorage
    
    CXStorage.AddItem "MyItem", "MyValue"
    CXStorage.AddItem "MyXml", "<node>text</node>"
    CXStorage.AddItem "MyText", "Line #1" & vbCrLf & "Line #2" & vbCrLf & "Line #3"
    CXStorage.AddItem Empty, "Empty"
    
    With CXStorage.GetItems()
        For Each sKey In .Keys()
            MsgBox sKey & vbCrLf & .Item(sKey)
        Next
    End With
    
    
End Sub

Sub Test2()
    
    With CreateObject("Scripting.Dictionary")
        Do
            .Item(.Count) = Mid(CreateObject("Scriptlet.TypeLib").GUID, 1, 38)
        Loop Until .Count = 100000
        CXStorage.AddItem "CapacityTest", Join(.Items())
    End With
    MsgBox Len(CXStorage.GetItem("CapacityTest"))
    
End Sub

Если кому лень копипастить код — прикрепляю готовую книгу.

Post’s attachments

CXStorage_Module_customxmlparts_storage.xlsm 19.75 kb, 5 downloads since 2019-01-18 

You don’t have the permssions to download the attachments of this post.

Щт Уккщк Куыгьу Туче
’ҐЄгй п Є®¤®ў п бва Ёж : 1251

2 Ответ от alexii 2018-12-18 01:09:50

  • alexii
  • Разработчик
  • Неактивен

Re: VBA Excel Сохранение пользовательских данных в CustomXMLParts

omegastripes пишет:

кодом, позволяющим хранить произвольные текстовые данные в коллекции CustomXMLParts книги Excel версии 2007 и выше. Данные сохраняются непосредственно в файле книги. С одной стороны, пользователь не имеет прямого доступа ко всем элементам этой коллекции, и как следствие, сохраняемые таким образом данные не подвержены случайной утере или порче, а также не болтаются на виду и не привлекают особого внимания. С другой стороны, их легко контролировать, достаточно просто открыть файл .xlsx как архив, и зайти в одноименную папку.

Есть вариант попроще, например:

ActiveWorkbook.Worksheets.Item("Лист2").Visible = xlVeryHidden

Работает и в версиях до 2007.

3 Ответ от omegastripes 2019-02-10 17:48:54

  • omegastripes
  • Разработчик
  • Неактивен
  • Рейтинг : [8|0]

Re: VBA Excel Сохранение пользовательских данных в CustomXMLParts

alexii пишет:

Есть вариант попроще, например:

ActiveWorkbook.Worksheets.Item("Лист2").Visible = xlVeryHidden

Работает и в версиях до 2007.

Метод с xlVeryHidden листом имеет пару недостатков по сравнению с CustomXMLParts.
1. Размер данных, которые можно поместить в одну ячейку, ограничен 32 КБ, для больших объемов придется изощраться с разбивкой и склейкой. В моем случае, ограничение около 100 МБ.
2. Нередко при генерации каких-либо отчетов в Excel, макрос начинается с создания листа и удаления всех остальных в цикле. Лист с данными может быть случайно удален вместе с остальными «до кучи». Иными словами, риск потерять данные выше.

Щт Уккщк Куыгьу Туче
’ҐЄгй п Є®¤®ў п бва Ёж : 1251

4 Ответ от alexii 2019-02-10 19:30:53

  • alexii
  • Разработчик
  • Неактивен

Re: VBA Excel Сохранение пользовательских данных в CustomXMLParts

Да и вообще можно просто Shift-Del на файле нажать .

5 Ответ от mikegti@yandex.ru 2021-10-15 18:37:02 (изменено: mikegti@yandex.ru, 2021-10-20 17:52:26)

  • mikegti@yandex.ru
  • Участник
  • Неактивен
  • Рейтинг : [0|0]

Re: VBA Excel Сохранение пользовательских данных в CustomXMLParts

class code — insert in Class Module

Option Explicit
'Wrapper class for ThisWorkbook.CustomXMLParts
Private customXMLstorage As Object

Private nameSpace1 As String

Private Const nSpPrefixXML = "cxs"

'****
Private Sub Class_Initialize()
    Set customXMLstorage = ThisWorkbook.CustomXMLParts
        nameSpace1 = "CXStorage"
End Sub

Private Sub Class_Terminate()
    Set customXMLstorage = Nothing
        nameSpace1 = Empty
End Sub

'****
Private Sub InitStorageNameSpace(ByVal value1 As String)
    'if need more then one storage -> Public this
    nameSpace1 = value1
End Sub

'**
Public Function RemoveCustomXMLstorage() As Boolean
On Error GoTo Err
    If MsgBox("You will remove all stored data!", vbOKCancel) = vbCancel Then Exit Function
    '**
    Dim oPart
    For Each oPart In ThisWorkbook.CustomXMLParts.SelectByNamespace(nameSpace1)
        oPart.Delete
    Next
    Set oPart = Nothing
    RemoveCustomXMLstorage = True
    Exit Function
Err:
    RemoveCustomXMLstorage = False
End Function

Public Sub AddItem(ByVal itemName1 As String, ByVal value1 As String)
    '**
    With GetItemsNode()
        With .SelectNodes("//item[@name='" & itemName1 & "']")
            If .Count > 0 Then .item(1).Delete
        End With
        '**
        .AppendChildNode "item", , msoCustomXMLNodeElement
        '**
        With .LastChild
            .AppendChildNode "name", , msoCustomXMLNodeAttribute, itemName1
            .AppendChildNode , , msoCustomXMLNodeText, value1
        End With
        '**
    End With
End Sub

Public Function AddItems(ByRef cItems As Scripting.Dictionary) As Boolean
On Error GoTo Err
    Dim itemName1
    For Each itemName1 In cItems
        AddItem itemName1, cItems(itemName1)
    Next itemName1
    AddItems = True
    Exit Function
Err:
    AddItems = False
End Function

Public Function ItemExists(ByVal itemName1 As String) As Boolean
    ItemExists = Not (GetItemsNode().SelectSingleNode("//item[@name='" & itemName1 & "']") Is Nothing)
End Function

Public Function GetItem(ByVal itemName1 As String) As Variant
    With GetItemsNode().SelectNodes("//item[@name='" & itemName1 & "']")
        If .Count > 0 Then
            If Not (.item(1).FirstChild Is Nothing) Then
                GetItem = .item(1).FirstChild.NodeValue
            End If
        End If
    End With
End Function

Public Function GetItems() As Scripting.Dictionary
    Dim oNode
    Set GetItems = CreateObject("Scripting.Dictionary")
    '**
    For Each oNode In GetItemsNode().SelectNodes("//item")
        GetItems.item(oNode.Attributes.item(1).NodeValue) = oNode.FirstChild.NodeValue
    Next oNode
    '**
    Set oNode = Nothing
End Function

Public Function RemoveItem(ByVal itemName1 As String) As Boolean
    If ItemExists(itemName1) Then
        With GetItemsNode().SelectNodes("//item[@name='" & itemName1 & "']")
            If .Count > 0 Then .item(1).Delete
            RemoveItem = True
        End With
    End If
End Function

Private Function GetItemsNode() As Object
    With ThisWorkbook.CustomXMLParts.SelectByNamespace(nameSpace1)
        If .Count = 0 Then ThisWorkbook.CustomXMLParts.Add "<" & nSpPrefixXML & ":root xmlns:" & nSpPrefixXML & "='" & nameSpace1 & "'><items/></" & nSpPrefixXML & ":root>"
        Set GetItemsNode = .item(1).DocumentElement.FirstChild
    End With
End Function

test code — insert in Module


Option Explicit

Private Sub Test_CustomXMLstorageCls()
    '**
    Dim sv1 As CustomXMLstorageCls
    Set sv1 = New CustomXMLstorageCls
    '**
        sv1.RemoveCustomXMLstorage
    '**
        sv1.AddItem "MyItem", "MyValue"
        sv1.AddItem "MyXml", "<node>text</node>"
        sv1.AddItem "MyText", "Line #1" & vbCrLf & "Line #2" & vbCrLf & "Line #3"
        sv1.AddItem Empty, "Empty"
    '**
    DebugPrintDictColl sv1.GetItems
    '**
        sv1.RemoveCustomXMLstorage
    '**
    Set sv1 = Nothing
    '**
End Sub

Private Sub Test_CustomXMLstorageAdd_CapacityTest()
    '**
    Dim sv1 As CustomXMLstorageCls
    Set sv1 = New CustomXMLstorageCls
    '**
    With CreateObject("Scripting.Dictionary")
        Do
            .item(.Count) = GetGUID 'CreateGUID 'Mid(typeLib1.GUID, 1, 38)
        Loop Until .Count = 100000
        '**
        sv1.AddItem "CapacityTest", Join(.items(), "; ")
    End With
    '**
    Logg "Stored string length: " & Len(sv1.GetItem("CapacityTest")), True, False, True
    '**
        sv1.RemoveCustomXMLstorage
    Set sv1 = Nothing
    '**
End Sub

'****
Private Function GetGUID() As String
  GetGUID = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
  GetGUID = Replace(GetGUID, "y", Hex(Rnd() And &H3 Or &H8))
  Dim i As Long
  For i = 1 To 30
    GetGUID = Replace(GetGUID, "x", Hex$(CLng(Rnd() * 15.9999)), 1, 1)
  Next i
End Function

Private Function DebugPrintDictColl(ByRef obj1 As Object) As Boolean
    If obj1 Is Nothing Then Debug.Print "The object Is Nothing": Exit Function
    If obj1.Count = 0 Then Debug.Print "The object does not have items": Exit Function
    '**
    Dim i As Long, key1
    '**
    If TypeOf obj1 Is Scripting.Dictionary Then
        For Each key1 In obj1.keys
            i = i + 1
            If TypeOf obj1.item(key1) Is Object  Then
                Debug.Print i & " key: " & key1 & vbTab & " -> item TypeName: " & TypeName(obj1.item(key1))
            Else
                Debug.Print i & " key: " & key1 & vbTab & " -> item: " & obj1.item(key1)
            End If
        Next key1
    Else
        'collection
        Dim itm1
        For Each itm1 In obj1
            i = i + 1
            If Not TypeName(itm1) = "String" And Not TypeName(itm1) = "Long" Then
                Debug.Print i & " key: " & itm1.key & " -> item TypeName: " & TypeName(itm1)
            Else
                Debug.Print i & " key number: " & i & " -> item: " & itm1
            End If
        Next itm1
        Set itm1 = Nothing
    End If
    DebugPrintDictColl = True
End Function

Private Sub Logg(ByVal str1 As String, Optional ByVal debugPrint1 As Boolean = True, Optional ByVal statusBar1 As Boolean, Optional ByVal msgBox1 As Boolean)
    If Len(str1) = 0 Then Exit Sub
    If debugPrint1 Then Debug.Print str1
    If statusBar1 Then Application.StatusBar = str1
    If msgBox1 Then MsgBox str1, vbOKOnly
End Sub

Модератор:Naeel Maqsudov

Avsha

Сообщения:664
Зарегистрирован:08 сен 2005, 13:47
Откуда:KZ

Требуется хранить где-нибудь в проекте VBA текстовые массивы (список ПЭВМ, перечень текстовых фильтров и т.д.)

Всегда хранил такие массивы непосредственно в коде программ. Вот захотелось усовершенствовать это дело.
Думаю использовать для таких исходных данных отдельный модуль (Mod_Info), чтобы при изменении этих данных уже прямо «идти» в него и только в нем править.

Возможно, имеет смысл сделать некоторый конфигуратор таких массивов, например, с помощью дополнительной формы?
Какие могут быть ещё варианты хранения массивов в проекте VBA?

Аватара пользователя

Aent

Сообщения:1108
Зарегистрирован:01 окт 2006, 14:52
Откуда:Saratov,Russia
Контактная информация:

23 июл 2007, 22:51

Avsha, если есть программный доступ к коду проекта в некоторых случаях
удобнее хранить большие текстовые массивы как строки комментариев в одноимённых с массивом модулях.
Вариант — внедрённый в офисный документ ini (xml) файл.
Но это всё актуально когда массивы действительно большие и их удобно править текстовым редактором или макросом через VBE.
Кстати, полезно помнить, что VBE поддерживает до 1024 символов в строке…
В последнее время храню все настройки для VB/VBA проектов в базе SQLITE.
Правда, это влечёт за собой одну (нерегистрируемую) DLL в каталоге макроса и файл
с базой в нём же. Зато нормальная база, отвязанная от всего. Опять же, SELECT сделать можно ;)
Дальнейшие спекуляции на тему зависят от сути Ваших задач (проблем) ;)

Avsha

Сообщения:664
Зарегистрирован:08 сен 2005, 13:47
Откуда:KZ

24 июл 2007, 10:20

Aent, спасибо за советы

большие текстовые массивы как строки комментариев в одноимённых с массивом модулях

Массивы небольшие по 10-20 элементов, всего штук 5-6 массивов.
Они меняются нечасто, но при изменении желательно идти в одно место, если в коде VBA.
Наверно мне незачем их писать в комментарии, просто явным присвоением наверно введу.

Вариант — внедрённый в офисный документ ini (xml) файл.

Про него я думал, но я умею только отдельный, лежащий неподалеку на диске.
Ведь оперативность изменений — мгновенная и гибкая.

Отсюда вопросы:
1. Есть ли какой стандартый (классический) формат у таких ini/xml-файлов, чтобы хранить текстовые массивы?
2. Внедрять в офисный документ, это как?

Аватара пользователя

Aent

Сообщения:1108
Зарегистрирован:01 окт 2006, 14:52
Откуда:Saratov,Russia
Контактная информация:

24 июл 2007, 13:29

&quot писал(а):
1. Есть ли какой стандартый (классический) формат у таких ini/xml-файлов, чтобы хранить текстовые массивы?
2. Внедрять в офисный документ, это как?

1. Нет
2. Я имел в виду ведрение через OleObjects. Но для небольших массивов это не имеет смысла.
А почему бы вам не хранить данные в самом офисном документе ?
В листе Excel или в переменных (Variables) Word.
Можно так же использовать пользовательские Property документа.

Avsha

Сообщения:664
Зарегистрирован:08 сен 2005, 13:47
Откуда:KZ

24 июл 2007, 15:04

Принял для себя решение хранить в отдельном ini-файле, формат его придумаю, выложу на обсуждение…
Потому что удобнее и оперативнее изменять исходные данные и не надо будет сохранять сам проект.
Можно будет скомпилировать потом форму при желании в самостоятельный EXE-шник.

А почему бы вам не хранить данные в самом офисном документе ?
В листе Excel или в переменных (Variables) Word.

Я делаю форму для другого приложения с VBA, а там подходящих объектов нет и они не удобны :rolleyes:

Avsha

Сообщения:664
Зарегистрирован:08 сен 2005, 13:47
Откуда:KZ

25 июл 2007, 11:05

хранить в отдельном ini-файле, формат его придумаю, выложу на обсуждение…

Код: Выделить всё

# это комментарий - удобно выводить из работы но не удалять значения
[Nodes]
Node_01
Node_02
Node_03
#Node_04
Node_05
Node_06
[Nodes_End]
[Filters]
расчет
задание
ввод
ошибка
тревога
график
[Filters_End]
[Users]
Gosha
Misha
Lena
Vanja
[Users_End]
....

Группа: Пользователи

Ранг: Участник

Сообщений: 57


Репутация:

4

±

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


Excel 2013

День добрый всем.

Скажите, можно ли сделать так, чтобы один раз объявив переменную и загрузив в нее какое-то значение, оно там оставалось, пока не закрыта книга..
Может есть какие-то «глобальные» переменные, которые можно объявить вне процедур?

Или может это через классы делается
*С классами еще ни разу не работал..

Подскажите, плз

==
Основная задача такая — для генерации документов word использую закладки. Все работает, документы создаются, но процедура создания самих шаблонов довольно утомительная. Полей много, (>200) и неудобно вставлять их имена в шаблон. Думал сделать макрос для word, который позволяет по горячей клавише показать выпадающий список с поиском Данные в этот список загружаются из excel файла..

Так вот, можно ли не открывать каждый раз файл и не загружать данные о полях из него?

  • Remove From My Forums
  • Question

  • Hi,

    I would like to ask a question on data format.

    C

    Excel

    {Signed, unsigned}

    ×

    {Char, short, int, long, long long}

    Float, double

    Processor

    Adder, multiplier

    Floating-point co-processor

    Basic data types in C are easily associated with computation components in the CPU core, but for Excel?

    1.   
    At the level of cell, each cell is definitely an object with a multitude of members and hierarchical structure

    2.   
    But for basic non-object data types, such as

       
    Dim a As
    Integer 

    are they just like basic types in C?

    For “number” type, what is its internal storage? Is it
    float or double? If Excel is programmed with C or C++, I guess the “number” format must be based on basic C/C++ data types. What is the actual data format?

    How do “Percentage””Fraction” and “Scientific
    differ from “Number”? Are they types (like defined with typedef, by the Excel development team), or even objects?

    And what about strings?

    The second question is with memory storage. With C, it is possible to
    access memory address directly with pointers, and memory viewing ability is also provided in Visual Studio IDE; with Excel VBA, how to view memory? How to access memory
    programmatically?

    The third question is with array. In C, array elements are stored
    consecutively
    in memory; what about for VBA arrays? Arrays are stored in
    row-major
    order in C, what about in Excel VBA?

    Bob

Answers

  • Excel cells are polymorphic. They can contain any of the following 4 Excel fundamental datatypes:

    • Double precision floating point (all numbers including integers, currency, dates times etc)
    • String
    • Boolean
    • Error

    to retrieve data from Excel into C you use the Oper data structure (note there are 2  versions of this, for Excel 2007/10 and previous versions, see the Excel SDK).

    In VBA the Variant data type can handle all the fundamental Excel data types (and some additional VBA datatypes). To retrieve data from Excel into VBA you use Range.Value or Range.Value2. VBA also has several other data types (string, integer, long, double,
    currency, date etc.)

    Excel also has a rendering layer that contains for each cell the currently formatted (number of decimal places, %, currency, date time etc) version of the underlying value. To retrieve the currently formatted value from the rendering layer into VBA you use
    Range.Text.

    And of course there is also the Formula layer for each cell.

    AFAIK VBA stoes arrays in column-major order.

    VBA does not really have the concept of directly addressing memory or pointers


    Charles Excel MVP The Excel Calculation Site http://www.decisionmodels.com/

    • Marked as answer by

      Monday, September 26, 2011 1:14 PM

  • Excel does not have a currency value datatype, currency values are stored as doubles.

    The rendering converts the cell value to a formatted value using the cell format  properties, the column widths/row height and conditional formatting rules etc

    If you pass VBA arrays to C functions then you need to know what the VBA arrays look like in memory.

    I have observed some performance gains by looping large 2-d VBA arrays by row within column …


    Charles Excel MVP The Excel Calculation Site http://www.decisionmodels.com/

    • Marked as answer by
      Bob Sun
      Monday, September 26, 2011 1:57 PM

Понравилась статья? Поделить с друзьями:
  • Хранение времени в excel
  • Хочу работать на word
  • Хочу работать на excel
  • Хочу избежать службы армии word
  • Характеристики ms word 2003