fastreport массивы в скрипте

[ELMA3] Скрипты в FastReport

Общая информация

В отличие от других генераторов отчетов, скрипт в FastReport содержит только то, что написано вами. В скрипте вы можете:

При запуске отчета происходит следующее:

Обработчики событий

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

fastreport массивы в скрипте. 2. fastreport массивы в скрипте фото. fastreport массивы в скрипте-2. картинка fastreport массивы в скрипте. картинка 2. Общая информация

Выберите интересующее вас событие и сделайте на нем двойной щелчок мышью. FastReport добавит в код отчета пустой обработчик события:

Объект Отчет также имеет события. Выбрать этот объект можно следующими способами:

Чтобы удалить обработчик события, выберите событие в окне Свойства, выделите текст имени события и нажмите клавишу Delete:

fastreport массивы в скрипте. 4(38). fastreport массивы в скрипте фото. fastreport массивы в скрипте-4(38). картинка fastreport массивы в скрипте. картинка 4(38). Общая информация

События

Чтобы максимально гибко управлять отчетом, каждый объект отчета имеет несколько событий, которым можно назначить обработчик – метод из скрипта. Например, в обработчике, привязанном к бэнду Данные, можно выполнять фильтрацию записей, т.е. скрывать или показывать бэнд в зависимости от каких-либо условий.

Рассмотрим процесс формирования отчета и события, которые при этом генерируются. В качестве примера возьмем простой отчет, содержащий одну страницу, один бэнд – Данные – и два объекта Текст на бэнде:

fastreport массивы в скрипте. 4. fastreport массивы в скрипте фото. fastreport массивы в скрипте-4. картинка fastreport массивы в скрипте. картинка 4. Общая информация

В начале отчета вызывается событие StartReport объекта Отчет. Перед формированием страницы вызывается событие страницы StartPage. Это событие вызывается один раз для каждой страницы шаблона отчета (не путать со страницами готового отчета!). В нашем случае, сколько бы ни было страниц в готовом отчете, событие вызовется один раз, т.к. шаблон отчета состоит из одной страницы.

Далее начинается печать строк бэнда Данные. Происходит это следующим образом:

Печать строк бэнда происходит до тех пор, пока есть данные в источнике. После этого формирование отчета в нашем случае завершается, и вызываются события FinishPage страницы отчета и наконец – событие FinishReport объекта Отчет.

Таким образом, используя события разных объектов, можно контролировать практически каждый момент формирования отчета. Ключ к правильному использованию событий – полное понимание процесса печати бэндов, изложенного выше в одиннадцати пунктах. Так, большинство действий можно выполнить, используя только событие бэнда BeforePrint: любые изменения, внесенные в объект, будут тут же отображены. Но в этом событии невозможно анализировать, на какой странице будет напечатан бэнд, если он растягиваемый – ведь подсчет высоты бэнда будет выполнен в пункте 6. Это можно сделать с помощью событий AfterLayout в пункте 7 или AfterPrint в пункте 10, но в последнем случае бэнд уже будет напечатан и действия над объектами ничего не дадут. Одним словом, вы должны четко представлять, в какой момент времени вызывается каждое из событий и использовать те, которые соответствуют поставленной задаче.

Кроме того, вы можете использовать любые объекты, определенные в сборках FastReport. Если вам нужно получить доступ к другой сборке, добавьте ее название в список сборок отчета. Это можно сделать в меню Отчет|Свойства…, выбрав в окне элемент Скрипт.

fastreport массивы в скрипте. 5. fastreport массивы в скрипте фото. fastreport массивы в скрипте-5. картинка fastreport массивы в скрипте. картинка 5. Общая информация

Обратиться к ней в скрипте можно так:

Если добавить в скрипт директиву using Demo, это позволит укоротить форму обращения к функции:

Обращение к объектам отчета

Для обращения к объектам отчета (например, объекту Текст) используйте имя объекта. Следующий пример вернет высоту объекта Text1:

Учтите, что «родными» единицами измерения отчета являются экранные пикселы. Вы должны это помнить при обращении к таким свойствам объектов, как Left, Top, Width, Height. Для перевода пикселов в сантиметры и обратно используйте константы, определенные в классе Units:

Обращение к источникам данных

В отличие от выражений FastReport (они рассмотрены в главе «Выражения»), в скрипте нельзя использовать квадратные скобки для обращения к данным отчета. Вместо этого используется метод GetColumnValue объекта Report, возвращающий значение поля:

Как видно, надо указать имя источника данных и его поля через точку. Имя источника может быть составным в случае, если мы обращаемся к источнику данных, используя связь (relation). Подробнее о связях рассказано в главе «Данные». Например, так можно обратиться к полю связанного источника данных:

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

Для обращения к самому источнику данных используйте метод GetDataSource объекта Report:

Справку по свойствам и методам класса DataSourceBase можно получить в справочной системе FastReport.Net Class Reference. Как правило, этот объект используется в скрипте следующим образом:

Обращение к системным переменным

Для обращения к системной переменной используйте метод GetVariableValue объекта Report:

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

Обращение к итоговым значениям

Для обращения к итоговому значению используйте метод GetTotalValue объекта Report:

Список итогов можно увидеть в окне Данные. Из него можно перетаскивать итоги в скрипт, при этом FastReport автоматически создает код для обращения к итогу.

Итоговое значение имеет тип FastReport.Variant. Оно может быть напрямую использовано в любых выражениях, потому что тип FastReport.Variant автоматически приводится к любому типу. Например:

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

Обращение к параметрам отчета

Для обращения к параметру отчета используйте метод GetParameterValue объекта Report:

Параметры могут быть вложенными. В этом случае укажите имя родительского параметра и через точку имя дочернего параметра:

Параметры имеют определенный тип данных. Он задается в свойстве DataType параметра. Вы должны учитывать это при обращении к параметру. Список параметров можно увидеть в окне Данные. Из него можно перетаскивать параметры в скрипт, при этом FastReport автоматически создает код для обращения к параметру.

Для изменения значения параметра используйте метод SetParameterValue объекта Report:

Пример 1. Изменение внешнего вида объекта

В этом примере мы покажем, как изменить цвет текста у объекта в зависимости от значения, которое печатается в объекте. Мы будем использовать:

Создайте простой отчет следующего вида:

fastreport массивы в скрипте. 7. fastreport массивы в скрипте фото. fastreport массивы в скрипте-7. картинка fastreport массивы в скрипте. картинка 7. Общая информация

У объекта, который печатает стоимость продукта, создайте обработчик события BeforePrint:

Чтобы вставить в скрипт значение поля Products.UnitPrice, перетащите его из окна Данные. При этом в скрипт будет вставлена строка:

Если запустить отчет, мы увидим, что все продукты, имеющие стоимость > 20, выделены красным цветом:

fastreport массивы в скрипте. 8. fastreport массивы в скрипте фото. fastreport массивы в скрипте-8. картинка fastreport массивы в скрипте. картинка 8. Общая информация

Пример 2. Вычисление итогов

В этом примере мы покажем, как программным способом вычислить суммарное значение. Мы будем использовать:

Создайте отчет следующего вида:

fastreport массивы в скрипте. 9. fastreport массивы в скрипте фото. fastreport массивы в скрипте-9. картинка fastreport массивы в скрипте. картинка 9. Общая информация

В скрипте объявите переменную summa и создайте у бэнда обработчик события BeforePrint:

Источник

Fastreport массивы в скрипте

Управление кросс-таблицей из скрипта

Если визуальных средств настройки таблицы недостаточно, можно использовать скрипт для тонкой настройки внешнего вида таблицы. Объект «Кросс-таблица» имеет следующие события:

Событие вызывается после печати таблицы.

Событие вызывается перед печатью таблицы.

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

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

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

Событие вызывается перед отображением заголовка колонок таблицы. Обработчик события может изменить оформление или содержимое ячейки заголовка.

Событие вызывается перед отображением заголовка строк таблицы. Обработчик события может изменить оформление или содержимое ячейки заголовка.

В событиях удобно использовать следующие методы объекта «Кросс-таблица»:

function ColCount: Integer

Возвращает количество колонок в таблице.

function RowCount: Integer

Возвращает количество строк в таблице.

(Index: Integer): Boolean

Возвращает True, если колонка с указанным номером является итоговой.

(Index: Integer): Boolean

Возвращает True, если строка с указанным номером является итоговой.

(Index: Integer): Boolean

Возвращает True, если колонка с указанным номером является колонкой промежуточных итогов.

(Index: Integer): Boolean

Возвращает True, если строка с указанным номером является строкой промежуточных итогов.

procedure AddValue( const Rows, Columns, Cells:

Добавляет значение в таблицу.

Рассмотрим на примере, каким образом можно выделить третью колонку цветом фона (в нашем примере – это данные за ноябрь 1999 года). Для этого выделим кросс-таблицу и создадим обработчик события OnPrintCell:

procedure Cross1OnPrintCell(Memo: TfrxMemoView;

RowIndex, ColumnIndex, CellIndex: Integer;

RowValues, ColumnValues, Value: Variant);

if ColumnIndex = 2 then

Мы увидим следующий результат:

fastreport массивы в скрипте. img216. fastreport массивы в скрипте фото. fastreport массивы в скрипте-img216. картинка fastreport массивы в скрипте. картинка img216. Общая информация

Чтобы выделить цветом заголовок колонки, создадим обработчик события OnPrintColumnHeader:

procedure Cross1OnPrintColumnHeader(Memo: TfrxMemoView;

HeaderIndexes, HeaderValues, Value: Variant);

if (VarToStr(HeaderValues[ 0 ]) = ‘1999’ ) and

(VarToStr(HeaderValues[ 1 ]) = ’11’ ) then

if ((VarToStr(HeaderValues[ 0 ]) == «1999» ) &&

(VarToStr(HeaderValues[ 1 ]) == «11» ))

fastreport массивы в скрипте. img217. fastreport массивы в скрипте фото. fastreport массивы в скрипте-img217. картинка fastreport массивы в скрипте. картинка img217. Общая информация

Поясним работу скриптов. Обработчик события OnPrintCell вызывается перед печатью ячейки, которая содержится в теле таблицы (при печати ячеек из заголовка таблицы вызывается обработчик OnPrintColumnHeader или OnPrintRowHeader). При этом в обработчик OnPrintCell передается ссылка на объект «Текст», который представляет собой ячейку таблицы (параметр Memo), и «адрес» ячейки в двух вариантах: номер строки, колонки и ячейки (последнее актуально, если в вашей таблице многоуровневые ячейки) в параметрах RowIndex, ColumnIndex, CellIndex соответственно. Второй вариант «адреса» – это параметры RowValues и ColumnValues. Параметр Value – это содержимое ячейки.

Для определения «адреса» вы можете использовать как первый вариант (RowIndex, ColumnIndex), так и второй (RowValues, ColumnValues) – что удобнее в конкретном случае. В нашем случае нужно было выделить третью колонку – поэтому удобнее анализировать первый вариант. Т.к. нумерация колонок и строк начинается с 0, проверка ColumnIndex = 2 позволила нам определить 3-ю колонку. Можно было поступить иначе, анализируя нужную колонку по ее данным (нам нужен 11 месяц 1999 года):

procedure Cross1OnPrintCell(Memo: TfrxMemoView;

RowIndex, ColumnIndex, CellIndex: Integer;

RowValues, ColumnValues, Value: Variant);

if (VarToStr(ColumnValues[ 0 ]) = ‘1999’ ) and

(VarToStr(ColumnValues[ 1 ]) = ’11’ ) then

if ((VarToStr(ColumnValues[ 0 ]) == «1999» ) &&

(VarToStr(ColumnValues[ 1 ]) == «11» ))

Значения, передаваемые в параметрах RowValues и ColumnValues – это массивы типа Variant с нулевой базой. Нулевой элемент – это значение верхнего уровня заголовка таблицы, первый – значение следующего уровня и т.д. В нашем случае ColumnValues[0] – это года, ColumnValues[1] – месяцы.

Зачем нужно преобразование VarToStr? Это гарантирует отсутствие ошибок приведения типов. FastReport при операциях с типом Variant пытается автоматически приводить строки в числовой формат, что в нашем случае вызовет ошибку при попытке приведения значения столбцов ‘Total’ и ‘Grand Total’.

Обработчик события OnPrintColumnHeader вызывается при печати ячеек заголовка столбца. Набор параметров похож на параметры обработчика OnPrintCell, но здесь «адрес» ячейки (параметры HeaderIndexes, HeaderValues) передается иначе. Параметр HeaderValues возвращает те же значения, что и параметры ColumnValues, RowValues в обработчике OnPrintCell. Параметр HeaderIndexes также является массивом значений типа Variant и содержит адрес ячейки заголовка в другой форме: нулевой элемент – это порядковый номер верхнего уровня заголовка таблицы, первый – номер следующего уровня и т.д. Принцип нумерации ячеек заголовка станет понятен, если взглянуть на рисунок:

fastreport массивы в скрипте. img218.zoom94. fastreport массивы в скрипте фото. fastreport массивы в скрипте-img218.zoom94. картинка fastreport массивы в скрипте. картинка img218.zoom94. Общая информация

В нашем случае удобно анализировать значение HeaderValues, но можно написать и такой обработчик:

procedure Cross1OnPrintColumnHeader(Memo: TfrxMemoView;

HeaderIndexes, HeaderValues, Value: Variant);

if (HeaderIndexes[ 0 ] = 0 ) and (HeaderIndexes[ 1 ] = 2 ) then

Источник

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

Чтобы сделать все это нужно отключить стандартные итоги и создать собственный столбец, в котором будет рассчитываться ваш итог. Но, если вы все-таки используете стандартные итоги, то, чтобы их значения тоже попали в пользовательский итог, нужно использовать событие матрицы AfterTotals. Оно произойдет после построения матрицы со всеми стандартными итогами, так что все данные будут в нашем распоряжении. Это событие не позволяет вам добавлять и изменять данные в матрицы, то есть использовать методы AddValue и SetValue.

Итак, рассмотрим пример.

Создаем матрицу с простейшим шаблоном:

fastreport массивы в скрипте. . fastreport массивы в скрипте фото. fastreport массивы в скрипте-. картинка fastreport массивы в скрипте. картинка . Общая информация

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

Создаем обработчик события AfterData для матрицы. В нем мы будем добавлять данные:

Здесь, мы добавляем значения для каждой ячейки матрицы. В нашем случае – это две строки данных. Обратите внимание, что мы добавили только один итог, для февраля. Пока он имеет нулевое значение, но вскоре мы рассчитаем его в событии AfterTotals.

Далее нам понадобится метод получения значения ячейки:

Все просто – передаем индекс колонки, а получаем – значение.

Также нам потребуется переменная класса – rowIndex:

private int rowIndex;

А теперь добавим метод присвоения значения ячейке:

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

Здесь, мы получаем массив индексов строк матрицы. Затем, проходим цикл по этим индексам строк. Для каждой строки получаем массив индексов столбцов. В цикле по индексам столбцов производим нарастающую сумму. В конце присваиваем полученную сумму ячейке «February;Total».

А теперь запустим отчет:

fastreport массивы в скрипте. . fastreport массивы в скрипте фото. fastreport массивы в скрипте-. картинка fastreport массивы в скрипте. картинка . Общая информация

Итог, как и предполагалось, выводится для группы February. Причем суммы от «штатного» итога тоже обработаны.

Таким образом мы можем создавать собственные итоги для нужных столбцов и строк.

Источник

Как использовать новый объект Таблица в FastReport VCL 6

На мой взгляд очень полезное нововведение в FastReport VCL 6 – объект Таблица. Раньше нам была доступна только кросс-таблица, которая по сути представляла собой матрицу. А матрица нам нужна в специфичных отчетах. Не хватало обычной таблицы, которая удобна при масштабировании и перемещении полей. Все данные в одном контейнере и нет необходимости каждый раз при изменении отчета подгонять поля, чтобы избежать перекрытия. Это большой плюс, если вы собираетесь экспортировать отчет в табличные форматы.

Давайте рассмотрим особенности этого объекта на примере.

Добавляем объект «Таблица» на бэнд «Данные». Конечно, вы можете поместить таблицу и на другие бэнды, но тогда она не будет заполняться данными. Вернее, она будет содержать данные только из первой строки таблицы.

Размерность таблицы

По умолчанию таблица создается с пятью столбцами и пятью строками.

Управлять размерностью таблицы очень просто:

fastreport массивы в скрипте. VCLTable1. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable1. картинка fastreport массивы в скрипте. картинка VCLTable1. Общая информация

Потянув мышью за этот значок можно увеличить размерность таблицы в высоту или в ширину:

fastreport массивы в скрипте. VCLTable2. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable2. картинка fastreport массивы в скрипте. картинка VCLTable2. Общая информация

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

fastreport массивы в скрипте. VCLTable3. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable3. картинка fastreport массивы в скрипте. картинка VCLTable3. Общая информация

В нашем случае количество колонок равно 2, а строк – 6.

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

fastreport массивы в скрипте. VCLTable4. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable4. картинка fastreport массивы в скрипте. картинка VCLTable4. Общая информация

Из этого же меню можно удалить выделенную колонку с помощью соответствующего пункта.

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

fastreport массивы в скрипте. VCLTable5. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable5. картинка fastreport массивы в скрипте. картинка VCLTable5. Общая информация

Объединение/разделение ячеек

Для объединения ячеек, нужно выбрать мышью нужные. Правым кликом вызываем контекстное меню и выбираем пункт «Объединить ячейки» (join cell).

Объединенные ячейки можно разделять также, из контекстного меню, пункт «Разделить ячейки» (split cell).

Добавление данных

Добавлять поля в ячейки таблицы можно несколькими способами:

2) Из выпадающего списка в ячейке. При этом для бэнда «Данные» обязательно должен быть выбран источник данных;

fastreport массивы в скрипте. VCLTable6. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable6. картинка fastreport массивы в скрипте. картинка VCLTable6. Общая информация

3) Двойной клик по нужной ячейке. В редакторе текста нажать на иконку «Вставить выражение». В редакторе выражения выбрать поле данных.

fastreport массивы в скрипте. VCLTable7. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable7. картинка fastreport массивы в скрипте. картинка VCLTable7. Общая информация

4) Групповое добавление:

fastreport массивы в скрипте. VCLTable8. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable8. картинка fastreport массивы в скрипте. картинка VCLTable8. Общая информация

Отпускаем кнопку мыши:

fastreport массивы в скрипте. VCLTable9. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable9. картинка fastreport массивы в скрипте. картинка VCLTable9. Общая информация

Перемещение столбцов и строк

Но возможности таблицы не исчерпываются лишь добавлением и удалением. Можно изменять порядок столбцов и строк. Для этого выделяем столбец, зажимаем клавишу Ctrl, нажимаем левую кнопку мыши и перетягиваем столбец (строку) в нужное место.

fastreport массивы в скрипте. VCLTable10. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable10. картинка fastreport массивы в скрипте. картинка VCLTable10. Общая информация

fastreport массивы в скрипте. VCLTable11. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable11. картинка fastreport массивы в скрипте. картинка VCLTable11. Общая информация

Объекты внутри таблицы

Таблица позволяет размещать в ячейках помимо объекта Текст и другие. Ограничение накладывается лишь на таблицы, кросс-таблицы, ДБ кросс-таблицы и вложенные отчеты. Все остальные объекты можно размещать в ячейках, причем хоть все в одной ячейке одновременно. Например, картинка:

fastreport массивы в скрипте. VCLTable12. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable12. картинка fastreport массивы в скрипте. картинка VCLTable12. Общая информация

Объекты внутри ячеек могут расти и смещаться, так же, как и на бэнде.

Свойства объекта Таблица

У объекта «Таблица» есть два свойства, о которых стоит написать:

Чтобы его использовать придется настроить бэнд «Данные». В его свойствах находим Stretched и устанавливаем в true. А для StretchMode выбираем ActualHeight. Теперь бэнд может растягиваться по высоте. Теперь нужно выбирать столбец или строку и сделать правый клик по области между стрелкой-указателем столбца/строки и границей таблицы. В контекстном меню выбираем AutoSize.

fastreport массивы в скрипте. VCLTable13. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable13. картинка fastreport массивы в скрипте. картинка VCLTable13. Общая информация

Нижняя строка с выключенным свойством AutoSize:

fastreport массивы в скрипте. VCLTable14. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable14. картинка fastreport массивы в скрипте. картинка VCLTable14. Общая информация

Нижняя строка со включенным свойством AutoSize:

fastreport массивы в скрипте. VCLTable15. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable15. картинка fastreport массивы в скрипте. картинка VCLTable15. Общая информация

Теперь рассмотрим свойство AllowSplit. Включить его очень просто. Для бэнда «Данные», в котором расположен объект «Таблица» нужно включить свойство AllowSplit. Сделать это можно правым кликом по заголовку бэнда. В контекстном меню выбираем нужное свойство. Либо в инспекторе свойств бэнда «Данные» также находим AllowSplit и задаем true. Вот как выглядит таблица, если это свойство отключено:

fastreport массивы в скрипте. VCLTable16. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable16. картинка fastreport массивы в скрипте. картинка VCLTable16. Общая информация

Как видите, следующая таблица не поместилась на странице и перенесена на следующую. А теперь включим AllowSplit.

fastreport массивы в скрипте. VCLTable17. fastreport массивы в скрипте фото. fastreport массивы в скрипте-VCLTable17. картинка fastreport массивы в скрипте. картинка VCLTable17. Общая информация

Часть таблицы выведена на первой странице, остальная – на второй. Эта опция помогает экономить бумагу при распечатке отчетов с таблицами.

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

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *