Развернуть иерархический список 1с
Дерево значений представляет из себя некую структуру с иерархией. Каждая строка имеет свойства «Родитель» и «Строки». У каждой строки может быть сколько угодно подчиненных строк. При этом такие операции как поиск, сортировка, подсчет итогов можно проводить с учетом уровня иерархии и подчиненных строк.
Программное создание дерева значений
Как уже упоминалось выше, каждая строка имеет свойство Строки , которое содержит коллекцию дочерних строк. И сам объект ДеревоЗначений имеет свойство Строки , которое содержит коллекцию строк верхнего уровня.
- Добавляет колонку в конец коллекции колонок дерева значений.
- Возвращаемое значение: КолонкаДереваЗначений .
- Добавляет строку в конец коллекции строк данного уровня дерева значений.
- Возвращаемое значение: СтрокаДереваЗначений .
Заполнить табличное поле на форме
Визуальное представление дерева значений на форме обеспечивает элемент Таблица .
Пример программного заполнения дерева значений для управляемых форм:
Результат выполнения запроса очень легко преобразовать в дерево значений, для этого нужно воспользоваться методом Выгрузить() и указать параметр ТипОбхода отличным от того, что стоит по умолчанию, т.е. ПоГруппировкам или ПоГруппировкамСИерархией .
Свернуть и развернуть строки дерева значений
Свернуть и развернуть дочерние строки элемента дерева значений можно с помощью методов Свернуть() и Развернуть() .
- Сворачивает узел в указанной строке дерева.
- ИдектификаторСтроки — идентификатор строки таблицы.
- Разворачивает узел в указанной строке дерева.
- ИдектификаторСтроки — идентификатор строки таблицы.
- СПодчиненными — определяет необходимость раскрытия подчиненных узлов.
- Получает коллекцию элементов дерева верхнего уровня.
- Возвращаемое значение: ДанныеФормыКоллекцияЭлементовДерева .
- Получает коллекцию дочерних элементов.
- Возвращаемое значение: ДанныеФормыКоллекцияЭлементовДерева .
Для представления в форме объектов конфигурации (справочники, документы и т.п.) существуют специальные типы данных:
- ДанныеФормыСтруктура — содержит набор свойств произвольного типа. Свойствами могут быть другие структуры, коллекции или структуры с коллекциями. Таким типом представляется, например, в форме СправочникОбъект .
- ДанныеФормыКоллекция — это список типизированных значений, похожий на массив. Доступ к элементу коллекции осуществляется по индексу или по идентификатору. Доступ по идентификатору может отсутствовать в некоторых случаях. Это обусловлено типом прикладного объекта, который представлен этой коллекцией. Идентификатором может быть любое целое число. Таким типом представляется, например, в форме табличная часть.
- ДанныеФормыСтруктураСКоллекцией — это объект, который представлен в виде структуры и коллекции одновременно. С ним можно обращаться как с любой из этих сущностей. Таким типом представляется, например, в форме набор записей.
- ДанныеФормыДерево — объект предназначен для хранения иерархических данных.
Прикладной объект представлен либо одним, либо несколькими элементами данных формы. Например, документ, содержащий табличную часть, будет представлен объектом типа ДанныеФормыСтруктура (собственно документ), которому подчинен объект типа ДанныеФормыКоллекция (табличная часть документа).
Удалить строку и очистить дерево значений
Поиск в дереве значений
Среди наиболее часто используемых методов стоит отметить метод Найти() коллекции строк дерева значений.
- Значение (обязательный, тип Произвольный ). Искомое значение.
- Колонки (необязательный, тип Строка ). Список имен колонок, в которых будет осуществляться поиск, разделенных запятыми. Если параметр не указан, поиск осуществляется по всем колонкам дерева. Значение по умолчанию — Пустая строка.
- ВключатьПодчиненные (необязательный, тип Булево ). Определяет, будут ли участвовать в поиске строки подчиненных коллекций (если таковые имеются). Если Истина — строки подчиненных коллекций участвуют в поиске. Значение по умолчанию — Ложь .
Метод осуществляет поиск значения в дереве в указанных колонках коллекции строк дерева значений. Возвращает строку (тип СтрокаДереваЗначений ), которая содержит искомое значение. Если значение не найдено, то возвращается значение Неопределено . Предназначен для поиска уникальных значений.
Дерево значений является одним из популярных типов данных, часто встречающихся на формах и конфигурации. Важным его преимуществом является заложенная платформой иерархичность, которой удобно воспользоваться в некоторых ситуациях, например, осуществить поиск по определенной группе элементов или получить отдельные итоговые суммы в различных категориях. Дерево значений в 1С – достаточно эффективный инструмент, достойный, чтобы разработчики тратили на его изучение свое время.
Размещение на форме и заполнение дерева значений
Чтобы на управляемой форме вывести дерево значений, необходимо добавить новый реквизит, выбрать нужный тип, добавить колонки и перетащить влево. На вопрос о добавлении колонок ответьте утвердительно, и перед вами предстанет общий вид дерева значений. Чтобы увидеть какие-либо записи, необходимо добавить строки дерева значений 1С с нужными данными.
Существует несколько способов заполнения дерева значений нужными нам свойствами с определенной иерархией. Самый простой вариант – заполнить вручную, последовательно добавляя элементы и внимательно самостоятельно соблюдая иерархию. Естественно, при большом количестве элементов данный вариант не должен рассматриваться разработчиками 1С. Данный способ вывода информации состоит из следующих операторов:
На стороне сервера:
- Получение значения реквизита;
- Добавление элементов с учетом иерархии;
- Возврат значения в элемент формы на клиенте для вывода пользователю.
На стороне клиента:
- Получение элементов дерева;
- Добавление новых.
Таким способом можно оформить на управляемой форме 1С небольшое дерево значений или простую иерархию, используя цикл. Однако часто возникает задача представить на форме сложный иерархический справочник из базы данных. И здесь на помощь придет помещение результата запроса в дерево на форме. Алгоритм достаточно прост и логичен:
- Создаем запрос и указываем нужные нам условия. Важно, чтобы псевдонимы совпадали с наименованием колонок дерева значений на форме;
- Получаем данные с нужным видом обхода. Если не указывать, то получится не иерархическая таблица значений.
При многократном использовании управляемой формы приходится очищать реквизиты. Не является исключением и дерево значений, если нам нужно получать актуальную информацию. Полностью очистить наш реквизит можно различными способами без особых усилий программиста:
Вышеперечисленные действия достаточно просты и не требуют усилий. Намного сложнее действия с конкретными строками, которые тоже необходимо использовать в работе. Ведь зачастую мы не будем иметь ни малейшего понятия о количестве строк в дереве, его иерархии, ее глубине и пр. Именно корректная работа с данными произвольной структуры требуется от качественно разработанной функциональности решения для анализа и обработки информации дерева значений.
Работа с заполненным деревом значений
В первую очередь, нужно научиться работать с данными, получая их из дерева. Для этого придется написать процедуру обхода строк дерева значений. Данные можно получить на сервере или клиенте, причем разница в алгоритмах будет небольшая. Ниже представлен алгоритм обхода дерева с краткими пояснениями операторов:
На клиентской стороне:
- Вызываем процедуру, используя в качестве параметров строки дерева;
- Внутри процедуры в цикле перебираем строки и проверяем наличие вложенных строк внутри каждой из них. Если таковые обнаружились, то снова вызываем эту же процедуру. Таким способом мы последовательно обойдем все строки, какой бы вложенностью не обладало наше дерево значений.
На серверной стороне:
- Получаем объект с формы;
- Вызываем процедуру с объектом в качестве параметра;
- В цикле по каждой строке дерева проверяем количество вложенных элементов и при их наличии снова вызываем процедуру.
Иногда в процессе обхода строк нам может потребоваться удалить что-либо. Для этого используется метод «Удалить(_параметр_)», для которого в качестве параметра может использоваться индекс или непосредственно строка. Так как в процессе обхода вы рассматриваете все строки по отдельности, не составит труда удалить некоторые из них. Будьте внимательны, так как при удалении строки удаляются все вложенные элементы.
Также достаточно распространенной задачей является преобразование дерева значений в таблицу значений. Так как нам нельзя терять иерархию, в таблице будет на 2 поля больше, чем в дереве – добавятся идентификатор и родитель. Заполнить таблицу с сохранением структуры мы сможем с помощью рекурсивной процедуры.
Целиком алгоритм состоит из:
- Вызов процедуры с указанием начального идентификатора;
- Цикл с добавлением данных в таблицу значений и проверкой на наличие вложенных элементов в дереве в каждой итерации. При их наличии снова начинается вызов рекурсивной процедуры.
Зачастую пользователям бывает мало просто добавить дерево значений в 1С на управляемую форму. Постоянно поступают запросы, чтобы была возможность посмотреть всю структуру. Для этого придется развернуть дерево значений прямо на глазах у пользователя. В 1С это можно сделать, обойдя в цикле все строки и воспользовавшись одним из их методов:
Также могут случаться ситуации, когда нужно свернуть имеющееся дерево. Для сворачивания конкретной строки можно воспользоваться методом «Свернуть(_Строка_)», аналогичным по синтаксису вышеописанному «Развернуть()». Чтобы полностью свернуть все дерево, придется воспользоваться рекурсивной функцией. Ее код достаточно прост и поддерживает общую методологию работы с деревом значений:
Созданный в платформе 1С тип «Дерево значений» отлично подходит для задач отображения иерархических списков и не только. Пользуясь им грамотно, можно существенно сэкономить время и удовлетворить требования пользователей по визуализации необходимых им данных, легко загрузив данные в дерево, обойти их и отразить развернутый список на управляемой форме.
1) Идея проста – выбираем запросом элементы, не являющиеся папками, а всю иерархию нам построит запрос. Тут сразу начинаются неожиданности. Какую конструкцию использовать: ИЕРАРХИЯ или ТОЛЬКО ИЕРАРХИЯ? Вроде логично было бы ТОЛЬКО ИЕРАРХИЯ, т.к. итоги на уровне элементов нам не нужны (будут дубли). Заглядываем в справку: "ИЕРАРХИЯ. В результате будут рассчитаны итоги по контрольным точкам и итоги по иерархии для контрольных точек . При необходимости можно рассчитать итоги только значений по иерархии, без расчета итогов в контрольных точках. Для этого перед ключевым словом ИЕРАРХИЯ нужно указать ключевое слово ТОЛЬКО."
Для однозначного понимания моих объяснений введу несколько "терминов", которыми буду пользоваться. Все листья дерева буду называть элементами. Узлы дерева, которые содержат только элементы – нижние папки, Остальные узлы, которые содержат хотя бы одну нижнюю папку – верхние папки.
Для ИЕРАРХИЯ – все логично: разбираем дерево итогов по иерархии для папок. У всех папок тип – ТипЗаписиЗапроса.ИтогПоИерархии. У элементов тип – ТипЗаписиЗапроса.ИтогПоГруппировке. Внутри группировки одна запись того же элемента но уже с типом ТипЗаписиЗапроса.ДетальнаяЗапись. Все как заявлено. Но если выгрузить в дерево, дубль пропадает!
Либо сформировать вручную
Для ТОЛЬКО ИЕРАРХИЯ все немного не так, как ожидалось. Верхние папки – ТипЗаписиЗапроса.ИтогПоИерархии, нижние – ТипЗаписиЗапроса.ИтогПоГруппировке. Внутри элементы с типом – ТипЗаписиЗапроса.ДетальнаяЗапись. НО, если верхняя папка содержит элементы, все они будут помещены в еще в одну вложенную группу с типом ТипЗаписиЗапроса.ИтогПоГруппировке. Поэтому выгрузить() приводит к дублированию! Цель такого дублирования думаю в том, чтобы все элементы обязательно содержались в папке с ТипЗаписиЗапроса.ИтогПоГруппировке, чтобы мы могли обходить выборку ОбходРезультатаЗапроса.ПоГруппировкам. Поэтому, если использовать ТОЛЬКО ИЕРАРХИЯ, лучше обойти выборку и сформировать ручками дерево, устраняя дублирование. При обходе нужно обязательно указывать второй параметр "Группировки". Привожу обход для ТОЛЬКО ИЕРАРХИЯ
Недостатки такого метода очевидны. Все папки вычисляет запрос, и мы не можем как-то их использовать. Например, при соединении с другой таблицей по номенклатуре, склеются только элементы, а на уровне папок нам доступны только вычисления в итогах. Проблема производительности здесь не рассматривается.
2) Для решения этой проблемы необходимо выбрать папки в запросе. Такой запрос не получиться выгрузить в дерево, но, если мы будем использовать в запросе УПОРЯДОЧИТЬ ПО . ИЕРАРХИЯ, а также выберем в запросе родителя, то обход дерева станет простым, мы будем обходить выборку в цикле и прицеплять следующий элемент к текущему или одному из его родителей. К кому цеплять покажет выбранное поле родитель.
Рассмотрим задачу получения только иерархии по набору элементов. Для решения задачи выберем для элементов все папки, в которых они содержатся, затем замыканием вычислим всех родителей этих папок, ну и далее выборка с обходом по 2 методу.
Не секрет, что в большинстве случаев пользователю намного удобнее использовать отображение списка иерархического справочника в виде дерева. Все бы ничего, да на полное разворачивание объемного справочника стандартными средствами 1С может уйти до нескольких минут. Моя методика позволит разворачивать и сворачивать подобные справочники в десятки раз быстрее.
плюсую конечно, а так хотелось бы текст методики видеть в самой статье а не скачивать отдельный файл..
Коллеги - поймите - я не из жадности хочу, чтобы скачивали файл. Эту $m потом девать в общем-то и некуда. Но это дает толчок к развитию как инфостарта - так и его пользователей. Например, с чего все началось у меня? Мне очень понадобилась конкретная обработка с инфостарта. В итоге пришлось кое-какие свои наработки достать из закромов и оформить публикации, чтобы их скачали, а у меня появились $m на то, чтобы скачать целевую публикацию. В итоге оформил уже 3 публикации - и думаю хорошо ими помог нескольким десяткам пользователей инфостарта.
(3)
Полное отсутствие информации о методах решения задачи дает обратный эффект. Не заинтересовывает.
Хотя бы в общих чертах и сжато но описание подхода нужно.
Иначе давайте все статьи будем назвать "Кто-то сделал что-то" и прикреплять файлы. Пусть инфостарт развивается!
(6) Даже не знаю что и ответить. Подождем для начала комментариев от тех, кто воспользовался методикой. О достигнутом эффекте.
Присоединяюсь к (6). В таком виде публикация выглядит как реклама от перхоти. Поделитесь хотя бы оценкой трудоемкости прививки данной методики к готовым конфигурациям. Ну что-то вроде "добавить три процедуры в общий модуль и внести их вызовы в 2315 мест в формах справочников". Суть самой методики, так и быть, не раскрывайте.
Ну, и еще просьба. Если кто скачал и использовал методику - то было бы очень хорошо, если бы в комментах написали какой выигрыш во времени раскрытия дерева получили (или не получили). Возможно большая выборка результатов даст повод попробовать еще больше оптимизировать процесс. Хотя на первый взгляд дальше уже некуда.
. рискну выразится несколько резко. однако даже после скачивания файла - не совсем понятен принцип действия, тем более, что чтобы опробовать методику требуется некая доработка Вашей обработки. Если уж Вы не предоставили описание методики, выложите хотя-бы готовую обработку, которую можно было бы запустить в конфигурации без её изменения (выпустите некий "окончательный продукт", не требующий "допиливания"), понимаю, что на это потребуется чуть больше времени, но все же попробуйте, и, возможно повысите свой статус, до уровня "когда $m уже не важны" ))). Неплохо бы в общих словах и описать принцип действия.
(9) Принимаю любую критику - так что не проблема
Для начала поясню - это не обработка, а процедуры и функции + пояснения по их внедрению в вашу конфигурацию. Просто для удобства и скажем так ради "боевой раскраски" они упакованы не в текстовый файл, а в общий модуль обработки.
И невозможно сделать готовую обработку (дело не во времени). В любом случае придется вносить изменения в конфигурацию. Хотя бы создать кнопки Свернуть и Развернуть.
Ну а это выдержка из текста модуля обработки:
//Создаем в любом общем модуле с установленным свойством Клиент(Управляемое приложение)
//(остальные свойства в Ложь) либо создаем свой общий модуль с описанными выше свойствами.
//В моем случае это общий модуль РазворачиваниеСворачиваниеДереваКлиент.
//В нем создаем экспортную процедуру:
Далее текст самой процедуры
//Создаем в любом общем модуле с установленным свойством Сервер
//(остальные свойства в Ложь) либо создаем свой общий модуль с описанными выше свойствами.
//В моем случае это общий модуль РазворачиваниеСворачиваниеДереваСервер.
//В нем создаем экспортную функцию:
Далее текст самой функции
//Далее в модуле управляемой формы списка целевого справочника создаем 2 процедуры и 2 функции:
Далее тексты процедур и функций
//Готово. Теперь в палитре свойств списка справочника устанавливаем Отображение = Дерево,
//НачальноеОтображениеДерева = НеРаскрывать.
//А далее по желанию либо в каком-нибудь событии формы вызывать РазвернутьВсе() или СвернутьВсе()
//либо (как это реализовано у меня) создать две соответствующие команды, разместить их
//в командной панели формы, а в их обработчиках вызывать соответствующие процедуры (РазвернутьВсе() или СвернутьВсе()).
Вот интересно - что здесь непонятно?
Ну, и заодно очевидна и "трудоемкость прививки к готовым конфигурациям" для (8).
А $m мне действительно не важны, сколько их уже есть - мне столько обработок не понадобится - факт.
Количество скачиваний файла мне пусть и субъективно но показывает количество человек кому необходима такая методика. А возможно, с некоторой скидкой, и кому в итоге помогла. Т.е. если я вижу ее не бесполезность, то тогда есть желание дальше работать в этом направлении - в том смысле, что разрабатывать нечто, что может быть полезным другим.
(11) Каким образом для пользователя подмените стандартную форму списка на свою обработку не внося правки в конфигурацию?
Но суть даже не в этом - я нигде и не писал, что это готовая обработка. Я обозначал, что это методика. И в (10) я объяснил причину ее "упаковки" в обработку. Методика готовая к использованию. Все функции и процедуры написаны, их нужно только внедрить в свою конфигурацию.
(12) если "въеду" в Вашу методику, напишу такую обработку. Методика работает только в "стандартной форме списка"? По крайней мере в пределах обычных форм на форму внешней обработки можно "положить" Форму списка любого справочника.
(13) Методика работает в любой форме с динамическим списком отображаемым в виде дерева. В пределах обычных форм (если Вы под обычными имеете в виду не управляемые) смысла в этой методике нет абсолютно.
И понятное дело, что в управляемых формах можно в форму обработки "положить" список любого справочника. Только как Вы будете подменять стандартную форму справочника на Вашу из обработки не влезая в конфигуратор (если у Вас конфигурация на полной поддержке)?
(14) в большинстве случаев интересно применение ускорения именно в нестандартных формах справочников, как-то например, подбор номенклатуры в УТ11 (Управляемые формы). В УТ11 "подбор в документ продажи" реализован обработкой. Я имел ввиду, что для иллюстрации выигрыша можно сделать внешнюю обработку, скажем по одному и тому-же справочнику, стандартным способом, и Вашим, возможно в одной форме по типу "Norton Commander"
С интересом прочитал обсуждение.
Видно, что в публикации изначально не хватало:
- указания из (15), что "смысла в этой методике нет в режиме обычного приложения"
- картинки, которая бы сразу показала вид развернутого дерева.
Теперь (наконец) стало ясно, необходима ли мне такая методика.
(39) Да бросьте - изначально в заголовке публикации было указано "управляемый интерфейс" :)
(38) Теперь я понял о каких двух вариантах шла речь. И теперь утверждать то, что реализовал оба варианта конечно же не буду. Изначально в описании публикации я говорил о конкретном варианте - одном списке в виде дерева.
Ваша функция с запросом и с последующей обработкой отрабатывает быстрее моей, пусть даже и та и другая выполняется за доли секунды - но отрицать факт неоптимальности моей реализации нет смысла.
Также возьму на заметку организацию запроса и обработки для двух списков. Спасибо.
Еще раз отмечу - что публиковалась методика, т.е. идея. А ее реализовывать можно по разному. Моя реализация - не оптимальна. И рад, что тема вызвала такое обсуждение, пусть и лишь с участием нескольких человек.
Ну и попрошу одного пояснения - у Вас все, что было Вами написано в этом обсуждении уже было реализовано до появления этой публикации или нет?
Т.е. мне интересно идея КЛЮЧЕВЫХ узлов была для Вас нова, или нет? (просто помню обсуждение подобной темы на мисте, где Вы говорили, что иерархический справочник из 5 000 строк открывался более 3 минут)
(41) - Изначально я закинул решение этой задачи для конкретно большого справочника и просто по-умолчанию поставил режим отображения в виде иерархического дерева. + до сегодняшнего моемента все как-то не доходили руки до добавления сервиса в виде сворачивания и разворачивания деревьев в иных формах. Вообщем-то отчасти из-за того, что как то отложилось что нужно передавать иденитификатор строки (Тип Произвольный в документации для параметра никак не ассоциировался с ссылкой и к тому же в обычном дереве точно давал ошибку и требовался идентификатор строки) , а для динамического списка он отсутствует, вот и забил на это дело. Второй момент, который подсознательно давил, так это ну просто ТУПАЯ реализация стандартного поведения данного объекта в 1С. 5 минут разворачивается стандартными внутренними средствами 1С справочник в моей базе при развороте. Поэтому демонстрация того что срабатывает ссылка - решило проблемы с тем чтобы пробовать. Второй момент - честно как-то не думал раньше разворачивать не первый уровень, а последний. Почему-то тоже отложилось должно быть одно и тоже, а скорее всего просто производительность не тормозила, что бы как-то оптимизировать. Разворачивать верхний уровень с подчиненными програмно намного проще. Ко всему в обычных формах это нормально работало. Хотя стоит попробовать и там разворачивать последний уровень. Возможно поведение однотипное. В результате тестов на производительность можно однозначно сказать - оптимизации работы динамического списка нет абсолютно (как минимум в режиме дерева), поэтому если и использовать режим дерева, то разворачивать следует действительно самый нижний требуемый уровень. Разворот вниз от самого верхнего узла 1С делает аналогично тупо, как и работа с деревом. И этот вариант только может привести хоть к каким-то приемлимым цифрам по быстродействию. Хоть в целом и не фонтан, ну что тут зделаешь
(42) Опять же не могу понять к чему именно относится Ваша фраза "оптимизации работы динамического списка нет абсолютно" (как минимум в режиме дерева)? Если это относится к этой публикации, то не соглашусь, т.к. она как раз и была предназначена для того, чтобы продвинуть идею разворачивания не всех, а лишь ключевых узлов, а это и есть оптимизация (и существенный выигрыш по времени, хотя как Вы выразились на выходе все равно не фонтан - это уже зависит от справочника). А если фраза относится не к публикации - тогда прошу пояснить к чему именно.
(42) ChAlex,
А меня ещё больше, чем возможность использования ссылки в качестве параметра, удивил сам результат применения метода "Раскрыть" к глубоко вложенным ветвям дерева. Никогда бы не подумал, что при этом раскроются все родительские узлы. Я считал, что в этом случае просто запомнится состояние данного узла во внутренних переменных.
И вот что в связи с этим я для себя при этом обнаружил. Если на форме размещается дерево (полученное, например, запросом), то в нём запоминается состояние "раскрыт" для дочерних узлов. Т.е. если закрыть и заново открыть родительский узел, то его дочерние узлы будут раскрыты или закрыты в зависимости от того, какими мы их оставили. А в динамическом списке в виде дерева этого нет. Хотя, если задуматься, то понятно почему.
(41) Признаю, был неправ, не дочитал конец заголовка, а чуть ниже - многочисленные "не имеет значения" в рубрикаторе - ввели в меня заблуждение.
Но картинку все ж надо было приделать.
Ну и еще - стандартного метода раскрыть все дерева после того как форма создана и выведена на экран - нет. Во всяком случае я его не знаю. Поэтому только выставлять свойство списка НачальноеОтображение = РаскрыватьВсеУровни.
Ну вот писал, писал - все куда-то ушло в корзину. Поэтому повторю. Если вдруг появится предыдущий труд - извиняйте.
Итак попробовал данный вариант. Сначала цифры: Справочник "Материалы" - количество записей 40 093 из них 379 папок. Время разворачивания списка: 1-е 50 сек., последующие 8 сек. Время сворачивания: 4 сек. Время разворачивания справочника при открытии в режиме "Разворачивать все уровни" 4 мин. 50 сек.
Теперь по методике: ничего нового или оптимизирующего в ней нет. Единственное что для себя открыл (до этого не пробовал писать не в соответствии с документаций) так это то, что в метод Развернуть(Строка,СПодчиненными) и Свернуть(Строка) можно передать в параметр "Строка" - ссылку а не идентификатор строки, как это написано в документации:
ТаблицаФормы (FormTable)
Развернуть (Expand)
Синтаксис:
Тип: Произвольный.
Идентификатор строки таблицы.
(необязательный)
Считаю это недоработкой документации.
Разжую для тех кто не сразу въезжает в тонкости данной темы:
По данной методики можно только организовать полное сворачивания и разворачивания веток дерева для динамического списка (в принципе можно модифицировать для разворачивания до определенного уровня). Все делается стандартными методами объекта "ТаблицаФормы": Развернуть() и Свернуть().
С вышесказанными замечаниями относительно параметров этих методов никаких сложностей с реализацией данных действий не возникает. Для динамических списков не возможно получить строку(и) из формы, которые нужно свернуть или развернуть, как например, для дерева( методом ДанныеФормыДерево.ПолучитьЭлементы()) - и потом уж получить по этим элементам идентификатор строки, который следовало бы передавать в качестве строки в метод Развернуть(). Оказывается вместо этого можно просто передать ссылки справочника. В этом и суть всех действий, предлагаемых автором. Все остальное стандартно. Ссылки получает запросом на сервере (кстати сам текст запроса какой-то геморный, не понял зачем так делать, но об этом позже), и потом обходя полученные ссылки разворачивается или сворачивается ветка ТаблицыФормы (списка справочника) из динамического списка методом Развернуть()/Свернуть(), где в качестве ссылки передается не идентификатор строки, а сама ссылка.
Теперь по поводу начального режима дерева: стоит устанавливать "Разворачивать только верхний уровень" - на моей базе разницы во времени с режимом "Не разворачивать" вообще никакой. Но хоть что-то отображается, вместо просто наименования справочника.
Теперь по поводу запроса: зачем там такой алгоритм - не понял. Переделал на простенький вариант:
Функция ПолучитьСписокЭлементовДерева2(ИмяСправочника, Развернуть) Экспорт
Запрос = Новый Запрос();
Запрос.Текст + ИмяСправочника + ".Ссылка
|ИЗ
| Справочник." + ИмяСправочника + " КАК " + ИмяСправочника + "
|ГДЕ
| " + ИмяСправочника + ".ЭтоГруппа"+?(Развернуть,""," И "+ИмяСправочника + ".Родитель = ЗНАЧЕНИЕ(Справочник." + ИмяСправочника + ".ПустаяСсылка)");
СписокЭлементов = Новый СписокЗначений;
СписокЭлементов.ЗагрузитьЗначения(Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка"));
Возврат СписокЭлементов;
КонецФункции
Некоторое пояснение: для сворачивания нет необходимости получения всех групп, достаточно свернуть только группы на верхнем уровне.
Ну и теперь размышления по поводу производительности разворачивания(сворачивание всегда идет более менее быстро). В общем-то можно организовать 2-мя способами: разворачивать только верхний уровень с подчиненными, что делать не стоит (на моей базе в этом случае время разворачивания составляет 3 мин 50 сек - то, есть тоже самое, что режим при открытии разворачивать все уровни)! или разворачивать все ветки, следует использовать данный вариант. Разница во времени показывает качество оптимизации (вернее ее полное отсутствие) для дерева в 1С.
Не секрет, что в большинстве случаев пользователю намного удобнее использовать отображение списка иерархического справочника в виде дерева. Все бы ничего, да на полное разворачивание объемного справочника стандартными средствами 1С может уйти до нескольких минут. Моя методика позволит разворачивать и сворачивать подобные справочники в десятки раз быстрее.
Эффективность методики зависит от структуры справочника и количества записей в нем. Понятное дело - что если справочник мал, и при установке в палитре свойств таблицы формы свойства НачальноеОтображениеДерева в РаскрыватьВсеУровни он открывается за секунду, то тут эффект не будет заметен. Однако все изменяется с ростом количества элементов. Ниже приведены результаты тестирования. Помимо полного разворачивания разработана и процедура для полного сворачивания.
Для тестирования старался выбирать разноплановые справочники (разные по типу иерархии, количеству элементов и групп / родителей)
1 колонка - наименование справочника
2 колонка - количество элементов
3 колонка - количество групп / родителей
4 колонка - время на раскрытие методом установки в палитре свойств таблицы формы свойства НачальноеОтображениеДерева в РаскрыватьВсеУровни
5 колонка - время на раскрытие с помощью моей методики
Для клиент-серверной версии:
Справочник | Элементов | Групп/родителей | Стандартно | С оптимиз. |
Контрагенты | 7809 | 6 | 27 с | 0,6 с |
Банки | 17465 | 78 | 52 с | 5 с |
Статьи движения ДС | 124 | 32 | 3 с | 2,1 с |
Подразделения организаций | 73 | 17 | 4 с | 1,5 с |
Повторные разворачивания всего дерева в течение сеанса работы происходит за доли секунды (если вы не закрывали окно справочника).
Суть оптимизации состоит в нахождении КЛЮЧЕВЫХ узлов, раскрытия которых абсолютно достаточно для полного раскрытия всего дерева. А исходя из того, что стандартным механизмом 1С раскрываются полностью все узлы, в т.ч. и подчиненные элементы, то выигрыш в количестве обращений к серверу, а соответсвенно и во времени разворачивания дерева очень существенный (особенно заметно на больших справочниках).
Специальные предложения
плюсую конечно, а так хотелось бы текст методики видеть в самой статье а не скачивать отдельный файл..
Коллеги - поймите - я не из жадности хочу, чтобы скачивали файл. Эту $m потом девать в общем-то и некуда. Но это дает толчок к развитию как инфостарта - так и его пользователей. Например, с чего все началось у меня? Мне очень понадобилась конкретная обработка с инфостарта. В итоге пришлось кое-какие свои наработки достать из закромов и оформить публикации, чтобы их скачали, а у меня появились $m на то, чтобы скачать целевую публикацию. В итоге оформил уже 3 публикации - и думаю хорошо ими помог нескольким десяткам пользователей инфостарта.
(3)
Полное отсутствие информации о методах решения задачи дает обратный эффект. Не заинтересовывает.
Хотя бы в общих чертах и сжато но описание подхода нужно.
Иначе давайте все статьи будем назвать "Кто-то сделал что-то" и прикреплять файлы. Пусть инфостарт развивается!
(6) Даже не знаю что и ответить. Подождем для начала комментариев от тех, кто воспользовался методикой. О достигнутом эффекте.
Присоединяюсь к (6). В таком виде публикация выглядит как реклама от перхоти. Поделитесь хотя бы оценкой трудоемкости прививки данной методики к готовым конфигурациям. Ну что-то вроде "добавить три процедуры в общий модуль и внести их вызовы в 2315 мест в формах справочников". Суть самой методики, так и быть, не раскрывайте.
Ну, и еще просьба. Если кто скачал и использовал методику - то было бы очень хорошо, если бы в комментах написали какой выигрыш во времени раскрытия дерева получили (или не получили). Возможно большая выборка результатов даст повод попробовать еще больше оптимизировать процесс. Хотя на первый взгляд дальше уже некуда.
. рискну выразится несколько резко. однако даже после скачивания файла - не совсем понятен принцип действия, тем более, что чтобы опробовать методику требуется некая доработка Вашей обработки. Если уж Вы не предоставили описание методики, выложите хотя-бы готовую обработку, которую можно было бы запустить в конфигурации без её изменения (выпустите некий "окончательный продукт", не требующий "допиливания"), понимаю, что на это потребуется чуть больше времени, но все же попробуйте, и, возможно повысите свой статус, до уровня "когда $m уже не важны" ))). Неплохо бы в общих словах и описать принцип действия.
(9) Принимаю любую критику - так что не проблема
Для начала поясню - это не обработка, а процедуры и функции + пояснения по их внедрению в вашу конфигурацию. Просто для удобства и скажем так ради "боевой раскраски" они упакованы не в текстовый файл, а в общий модуль обработки.
И невозможно сделать готовую обработку (дело не во времени). В любом случае придется вносить изменения в конфигурацию. Хотя бы создать кнопки Свернуть и Развернуть.
Ну а это выдержка из текста модуля обработки:
//Создаем в любом общем модуле с установленным свойством Клиент(Управляемое приложение)
//(остальные свойства в Ложь) либо создаем свой общий модуль с описанными выше свойствами.
//В моем случае это общий модуль РазворачиваниеСворачиваниеДереваКлиент.
//В нем создаем экспортную процедуру:
Далее текст самой процедуры
//Создаем в любом общем модуле с установленным свойством Сервер
//(остальные свойства в Ложь) либо создаем свой общий модуль с описанными выше свойствами.
//В моем случае это общий модуль РазворачиваниеСворачиваниеДереваСервер.
//В нем создаем экспортную функцию:
Далее текст самой функции
//Далее в модуле управляемой формы списка целевого справочника создаем 2 процедуры и 2 функции:
Далее тексты процедур и функций
//Готово. Теперь в палитре свойств списка справочника устанавливаем Отображение = Дерево,
//НачальноеОтображениеДерева = НеРаскрывать.
//А далее по желанию либо в каком-нибудь событии формы вызывать РазвернутьВсе() или СвернутьВсе()
//либо (как это реализовано у меня) создать две соответствующие команды, разместить их
//в командной панели формы, а в их обработчиках вызывать соответствующие процедуры (РазвернутьВсе() или СвернутьВсе()).
Вот интересно - что здесь непонятно?
Ну, и заодно очевидна и "трудоемкость прививки к готовым конфигурациям" для (8).
А $m мне действительно не важны, сколько их уже есть - мне столько обработок не понадобится - факт.
Количество скачиваний файла мне пусть и субъективно но показывает количество человек кому необходима такая методика. А возможно, с некоторой скидкой, и кому в итоге помогла. Т.е. если я вижу ее не бесполезность, то тогда есть желание дальше работать в этом направлении - в том смысле, что разрабатывать нечто, что может быть полезным другим.
(11) Каким образом для пользователя подмените стандартную форму списка на свою обработку не внося правки в конфигурацию?
Но суть даже не в этом - я нигде и не писал, что это готовая обработка. Я обозначал, что это методика. И в (10) я объяснил причину ее "упаковки" в обработку. Методика готовая к использованию. Все функции и процедуры написаны, их нужно только внедрить в свою конфигурацию.
(12) если "въеду" в Вашу методику, напишу такую обработку. Методика работает только в "стандартной форме списка"? По крайней мере в пределах обычных форм на форму внешней обработки можно "положить" Форму списка любого справочника.
(13) Методика работает в любой форме с динамическим списком отображаемым в виде дерева. В пределах обычных форм (если Вы под обычными имеете в виду не управляемые) смысла в этой методике нет абсолютно.
И понятное дело, что в управляемых формах можно в форму обработки "положить" список любого справочника. Только как Вы будете подменять стандартную форму справочника на Вашу из обработки не влезая в конфигуратор (если у Вас конфигурация на полной поддержке)?
(14) в большинстве случаев интересно применение ускорения именно в нестандартных формах справочников, как-то например, подбор номенклатуры в УТ11 (Управляемые формы). В УТ11 "подбор в документ продажи" реализован обработкой. Я имел ввиду, что для иллюстрации выигрыша можно сделать внешнюю обработку, скажем по одному и тому-же справочнику, стандартным способом, и Вашим, возможно в одной форме по типу "Norton Commander"
С интересом прочитал обсуждение.
Видно, что в публикации изначально не хватало:
- указания из (15), что "смысла в этой методике нет в режиме обычного приложения"
- картинки, которая бы сразу показала вид развернутого дерева.
Теперь (наконец) стало ясно, необходима ли мне такая методика.
(39) Да бросьте - изначально в заголовке публикации было указано "управляемый интерфейс" :)
(38) Теперь я понял о каких двух вариантах шла речь. И теперь утверждать то, что реализовал оба варианта конечно же не буду. Изначально в описании публикации я говорил о конкретном варианте - одном списке в виде дерева.
Ваша функция с запросом и с последующей обработкой отрабатывает быстрее моей, пусть даже и та и другая выполняется за доли секунды - но отрицать факт неоптимальности моей реализации нет смысла.
Также возьму на заметку организацию запроса и обработки для двух списков. Спасибо.
Еще раз отмечу - что публиковалась методика, т.е. идея. А ее реализовывать можно по разному. Моя реализация - не оптимальна. И рад, что тема вызвала такое обсуждение, пусть и лишь с участием нескольких человек.
Ну и попрошу одного пояснения - у Вас все, что было Вами написано в этом обсуждении уже было реализовано до появления этой публикации или нет?
Т.е. мне интересно идея КЛЮЧЕВЫХ узлов была для Вас нова, или нет? (просто помню обсуждение подобной темы на мисте, где Вы говорили, что иерархический справочник из 5 000 строк открывался более 3 минут)
(41) - Изначально я закинул решение этой задачи для конкретно большого справочника и просто по-умолчанию поставил режим отображения в виде иерархического дерева. + до сегодняшнего моемента все как-то не доходили руки до добавления сервиса в виде сворачивания и разворачивания деревьев в иных формах. Вообщем-то отчасти из-за того, что как то отложилось что нужно передавать иденитификатор строки (Тип Произвольный в документации для параметра никак не ассоциировался с ссылкой и к тому же в обычном дереве точно давал ошибку и требовался идентификатор строки) , а для динамического списка он отсутствует, вот и забил на это дело. Второй момент, который подсознательно давил, так это ну просто ТУПАЯ реализация стандартного поведения данного объекта в 1С. 5 минут разворачивается стандартными внутренними средствами 1С справочник в моей базе при развороте. Поэтому демонстрация того что срабатывает ссылка - решило проблемы с тем чтобы пробовать. Второй момент - честно как-то не думал раньше разворачивать не первый уровень, а последний. Почему-то тоже отложилось должно быть одно и тоже, а скорее всего просто производительность не тормозила, что бы как-то оптимизировать. Разворачивать верхний уровень с подчиненными програмно намного проще. Ко всему в обычных формах это нормально работало. Хотя стоит попробовать и там разворачивать последний уровень. Возможно поведение однотипное. В результате тестов на производительность можно однозначно сказать - оптимизации работы динамического списка нет абсолютно (как минимум в режиме дерева), поэтому если и использовать режим дерева, то разворачивать следует действительно самый нижний требуемый уровень. Разворот вниз от самого верхнего узла 1С делает аналогично тупо, как и работа с деревом. И этот вариант только может привести хоть к каким-то приемлимым цифрам по быстродействию. Хоть в целом и не фонтан, ну что тут зделаешь
(42) Опять же не могу понять к чему именно относится Ваша фраза "оптимизации работы динамического списка нет абсолютно" (как минимум в режиме дерева)? Если это относится к этой публикации, то не соглашусь, т.к. она как раз и была предназначена для того, чтобы продвинуть идею разворачивания не всех, а лишь ключевых узлов, а это и есть оптимизация (и существенный выигрыш по времени, хотя как Вы выразились на выходе все равно не фонтан - это уже зависит от справочника). А если фраза относится не к публикации - тогда прошу пояснить к чему именно.
(42) ChAlex,
А меня ещё больше, чем возможность использования ссылки в качестве параметра, удивил сам результат применения метода "Раскрыть" к глубоко вложенным ветвям дерева. Никогда бы не подумал, что при этом раскроются все родительские узлы. Я считал, что в этом случае просто запомнится состояние данного узла во внутренних переменных.
И вот что в связи с этим я для себя при этом обнаружил. Если на форме размещается дерево (полученное, например, запросом), то в нём запоминается состояние "раскрыт" для дочерних узлов. Т.е. если закрыть и заново открыть родительский узел, то его дочерние узлы будут раскрыты или закрыты в зависимости от того, какими мы их оставили. А в динамическом списке в виде дерева этого нет. Хотя, если задуматься, то понятно почему.
(41) Признаю, был неправ, не дочитал конец заголовка, а чуть ниже - многочисленные "не имеет значения" в рубрикаторе - ввели в меня заблуждение.
Но картинку все ж надо было приделать.
Ну и еще - стандартного метода раскрыть все дерева после того как форма создана и выведена на экран - нет. Во всяком случае я его не знаю. Поэтому только выставлять свойство списка НачальноеОтображение = РаскрыватьВсеУровни.
Ну вот писал, писал - все куда-то ушло в корзину. Поэтому повторю. Если вдруг появится предыдущий труд - извиняйте.
Итак попробовал данный вариант. Сначала цифры: Справочник "Материалы" - количество записей 40 093 из них 379 папок. Время разворачивания списка: 1-е 50 сек., последующие 8 сек. Время сворачивания: 4 сек. Время разворачивания справочника при открытии в режиме "Разворачивать все уровни" 4 мин. 50 сек.
Теперь по методике: ничего нового или оптимизирующего в ней нет. Единственное что для себя открыл (до этого не пробовал писать не в соответствии с документаций) так это то, что в метод Развернуть(Строка,СПодчиненными) и Свернуть(Строка) можно передать в параметр "Строка" - ссылку а не идентификатор строки, как это написано в документации:
ТаблицаФормы (FormTable)
Развернуть (Expand)
Синтаксис:
Тип: Произвольный.
Идентификатор строки таблицы.
(необязательный)
Считаю это недоработкой документации.
Разжую для тех кто не сразу въезжает в тонкости данной темы:
По данной методики можно только организовать полное сворачивания и разворачивания веток дерева для динамического списка (в принципе можно модифицировать для разворачивания до определенного уровня). Все делается стандартными методами объекта "ТаблицаФормы": Развернуть() и Свернуть().
С вышесказанными замечаниями относительно параметров этих методов никаких сложностей с реализацией данных действий не возникает. Для динамических списков не возможно получить строку(и) из формы, которые нужно свернуть или развернуть, как например, для дерева( методом ДанныеФормыДерево.ПолучитьЭлементы()) - и потом уж получить по этим элементам идентификатор строки, который следовало бы передавать в качестве строки в метод Развернуть(). Оказывается вместо этого можно просто передать ссылки справочника. В этом и суть всех действий, предлагаемых автором. Все остальное стандартно. Ссылки получает запросом на сервере (кстати сам текст запроса какой-то геморный, не понял зачем так делать, но об этом позже), и потом обходя полученные ссылки разворачивается или сворачивается ветка ТаблицыФормы (списка справочника) из динамического списка методом Развернуть()/Свернуть(), где в качестве ссылки передается не идентификатор строки, а сама ссылка.
Теперь по поводу начального режима дерева: стоит устанавливать "Разворачивать только верхний уровень" - на моей базе разницы во времени с режимом "Не разворачивать" вообще никакой. Но хоть что-то отображается, вместо просто наименования справочника.
Теперь по поводу запроса: зачем там такой алгоритм - не понял. Переделал на простенький вариант:
Функция ПолучитьСписокЭлементовДерева2(ИмяСправочника, Развернуть) Экспорт
Запрос = Новый Запрос();
Запрос.Текст + ИмяСправочника + ".Ссылка
|ИЗ
| Справочник." + ИмяСправочника + " КАК " + ИмяСправочника + "
|ГДЕ
| " + ИмяСправочника + ".ЭтоГруппа"+?(Развернуть,""," И "+ИмяСправочника + ".Родитель = ЗНАЧЕНИЕ(Справочник." + ИмяСправочника + ".ПустаяСсылка)");
СписокЭлементов = Новый СписокЗначений;
СписокЭлементов.ЗагрузитьЗначения(Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка"));
Возврат СписокЭлементов;
КонецФункции
Некоторое пояснение: для сворачивания нет необходимости получения всех групп, достаточно свернуть только группы на верхнем уровне.
Ну и теперь размышления по поводу производительности разворачивания(сворачивание всегда идет более менее быстро). В общем-то можно организовать 2-мя способами: разворачивать только верхний уровень с подчиненными, что делать не стоит (на моей базе в этом случае время разворачивания составляет 3 мин 50 сек - то, есть тоже самое, что режим при открытии разворачивать все уровни)! или разворачивать все ветки, следует использовать данный вариант. Разница во времени показывает качество оптимизации (вернее ее полное отсутствие) для дерева в 1С.
При разработке форм подборов и организации отборов для удобства работы может возникнуть задача по автоматическому раскрытию текущей группы.
- упростить работу пользователю, выполнять 1 клик вместо 2;
- явно дать понять ему, что есть вложенные группы.
В такой простой задаче может возникнуть стопор из-за особенностей динамического списка:
- в метод Развернуть() таблицы формы следует передать идентификатор текущей строки (численное значение, согласно описанию в синтаксис-помощнике);
- в динамическом списке, когда источник и основная таблица есть справочник, метод ТекущаяСтрока() возвращает ссылку этого справочника, к которому не применим метод ПолучитьИдентификатор().
Решение проблемы:
Первое что необходимо сделать: проверить или установить свойство «Отображение» у таблице в значение «Дерево»
Оказывается в метод Развернуть() в данном случае можно передавать эту ссылку, т.к. источник справочник, в котором нет дублей, следовательно система однозначно идентифицирует данную строку и сможет ее развернуть или свернуть. Аналогично работает и метод Развернут().
Код события при «АктивизацииСтроки»
тд = Элементы . Дерево . ТекущиеДанные ;
Если Не тд = Неопределено Тогда
Если Элементы . Дерево . Развернут ( Элементы . Дерево . ТекущаяСтрока ) = Ложь Тогда
Элементы . Дерево . Развернуть ( Элементы . Дерево . ТекущаяСтрока , Ложь ) ;
КонецЕсли ;
КонецЕсли ;
Для удобства пользователя добавим, авто раскрытие/сокрытие при выборе(двойном клике, по уже активной строке):
Код события «Выбор»
Процедура ДеревоВыбор ( Элемент , ВыбраннаяСтрока , Поле , СтандартнаяОбработка )
ТС = Элементы . Дерево . ТекущаяСтрока ;
Если Элементы . Дерево . Развернут ( ТС ) = Истина Тогда
Элементы . Дерево . Свернуть ( ТС ) ;
Иначе
Элементы . Дерево . Развернуть ( ТС , Ложь ) ;
КонецЕсли ;
Данная статья также применима к плану счетов, планам видов характеристик и расчетов (которые имеют иерархическую структуру).
Если новая модель не удалась, позаботься о рекламе изделия.
— Артур Блох
Данный запрос выбирает все записи из справочника и производит упорядочивание по иерархии. Результат будет упорядочен по наименованию, с учетом иерархии.
Для того чтобы группы справочника размещались выше элементов необходимо в данном запросе заменить предложение УПОРЯДОЧИТЬ ПО на следующее:
Код 1C v 8.х
Результат по-прежнему будет упорядочен по иерархии, однако группы будут располагаться выше элементов.
Возможна также замена предложения УПОРЯДОЧИТЬ ПО на предложение АВТОУПОРЯДОЧИВАНИЕ. В этом случае результат будет упорядочен в соответствии с настройками справочника, т.е. если в справочнике указано, что группы должны располагаться выше элементов, то они будут расположены выше.
Получить иерархическую структуру справочника также возможно и при помощи итогов.
Код 1C v 8.х
Похожие FAQ
Еще в этой же категории
Создание и запись нового элемента справочника 14
НаКлиенте Процедура ПоКнопкеНовыйКонтрагент(Команда) // Вставить содержимое обработчика. Перем НовыйКонтрагент; НовыйКонтрагент = Новый Структура(" Наименование, ПолноеНаименование, ИНН" ); НовыйКонтрагент.Наименование = " РиК ООО" ; НовыйКон Как для Выбора Элемента Справочника Открыть Форму Выбора с нужной Открытой Группой? 8
Порядок действий такой: 1. В поле ввода " Номенклатура" добавь событие ПриНачалеВыбора. 2. Установи в них СтандартнаяОбработка = Ложь; 3. Получи форму выбора нужного справочника 4. Установи отбор по родителю 5. Открой форму для выбора Теп Перебрать, выбрать элементы справочника 7
Выборка = Справочники.Сотрудники.Выбрать(); //или Выборка = Справочники.Сотрудники.ВыбратьИерархически(); Пока выборка.Следующий() = 1 Цикл . //действия с очередным элементом . Сообщить(" Сотрудник " + выборка.Наименование); КонецЦикла; / Перебрать, выбрать элементы подчиненного справочника 7
Перебор элементов справочника принадлежащих элементу другого справочника, т.е когда один справочник подчинен другому справочнику. // Получить выборку по указанному контрагенту. Выборка = Справочники.КонтактныеЛица.Выбрать( , Контрагент); Пока Вы Как программно открыть и выбрать элемент справочника, выбор элемента справочника? 7
Просто открыть: // Получить форму выбора справочника как подчиненную форме документа ФормаСписка = Справочники.Номенклатура.ПолучитьФормуСписка( , ЭтаФорма); // Открыть полученную форму ФормаСписка.Открыть(); Открыть для выбора элемента: Ф Посмотреть все в категории Справочники
Читайте также: