Перейти к содержанию

Коды FastReport

Гиперссылка на карточку задачи

Чтобы в отчете по клику, например, на номер задачи открывалась карточка задачи, поле с номером задачи надо настроить как гиперссылку.

Способ 1

Гиперссылка формируется в настройках элемента.

Настройка гиперссылки на карточку задачи, 1 способ

Для ссылки на задачу можно использовать такое выражение:

[Settings.ApplicationPath+"/MainTaskForm.aspx?TaskID="+ToString(\Table.TaskID)]

где \Table.TaskID\ надо заменить на поле, содержащее ID задачи.

Для ссылки на профиль пользователя можно использовать такое выражение:

[Settings.ApplicationPath+"/UserInfo.aspx?UserID="+ToString(\Table.UserID)]

где \Table.UserID\ надо заменить на на поле, содержащее ID пользователя.

ℹ️ Для работы таких гиперссылок в источники данных должна быть добавлена служебная таблица Settings из вашей базы данных

Добавление таблицы Settings как источника данных

Способ 2

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

Настройка гиперссылки на карточку задачи, 2 способ

Внешний вид ссылки

Чтобы ссылка отображалась как гиперссылка (синим цветом и подчеркнутым шрифтом), надо отметить флажок "Изменить внешний вид объекта, чтобы он выглядел как ссылка". Если флажок не отмечен, то ссылка будет изменять вид лишь при наведении на нее мыши:

Отображение гиперссылки в режиме пользователя

Очистка от служебных символов и тегов

Если в настройках категории включен флажок "Разрешить HTML в тексте задачи", то при выводе в отчет теги могут приводить к некорректному отображению текста задачи (не все теги корректно распознаются FastReport-ом; при обрезании первых N символов текста может потеряться закрывающий тег, и т.п.).

Чтобы избежать этого, можно использовать функцию fnStripTags, которая удаляет из текста все теги (все, что находится между символами \< и >).

Примеры использования:

SELECT dbo.fnStripTags(T.TaskText) as TaskText FROM TasksInSubcat11Denormalized T

Замена лишних символов в полном названии категории

Часто удобно отображать в отчете не название категории (поле Description таблицы Subcategories), а именно полный путь, поскольку в разных разделах могут присутствовать категории с одинаковыми названиями.

В таблице Subcategories есть поле FullPath, которое хранит полный путь к категории, с названиями всех разделов и подразделов. В качестве символа-разделителя между названиями разделов используется служебный символ \'\u2044\' (это сделано для того, чтобы отличать символ \'\\' в названии категории от символа-разделителя).

Чтобы убрать служебные символы из названия и преобразовать его к легко читаемому виду, удобно в SQL-запросе использовать такое преобразование:

REPLACE(Subcategories.FullPath, \'\u2044\', \'\\') as SubcatName

Исключение задач из служебных категорий

В таблице Tasks хранятся задачи из всех категорий, в том числе из служебных категорий "Общение" (чаты), "Календарь", "Личные задачи". В запросах иногда необходимо исключить эти задачи из отбора. Для этого можно использовать конкретные ID категорий в вашей базе данных, но в этом случае отчет будет не тиражируемым. Чтобы обеспечить возможность переноса отчета в другие базы данных, можно использовать служебную таблицу Settings, в которой хранятся ID этих служебных категорий:

  • поле CalendarSubcatID хранит ID категории "Календарь"

  • поле ChatSubcatID хранит ID категории "Общение"

  • поле PrivateSubcatID хранит ID категории "Личные задачи"

В запрос к таблице Tasks можно добавить строку:

WHERE (\@SubcatExcludeID not like \'%,\'+CAST(Tasks.SubcatID as varchar(max))+\',%\')

где значение параметра \@SubcatExcludeID задается, например, таким выражением:

","+ToString([Settings.ChatSubcatID)+","+ToString(\Settings.CalendarSubcatID)+","]

Список значений, преобразованный в строку

ℹ️ Таблица Settings должна быть явно добавлена в отчет как источник данных (т.е. должна присутствовать в объекте Connection)

Добавление таблицы Settings в источники данных

Множественный выбор по ID в фильтрах

Если параметр выбирается в фильтре как значение выпадающего списка с включенным признаком множественного выбора или как значение типа "Пользователь", "Орг.единица", "Группа" или "Категория", то такой параметр передается в отчет в виде строки со списком ID выбранных значений, разделенных запятыми:

123,456,789

ℹ️ Если ни одно значение не выбрано в выпадающем списке, то в отчет передается null, а если в значении типа "Пользователь", "Орг.единица", "Группа" или "Категория", то пустая строка.

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

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

Результатом будет преобразование строки со списком значений к виду:

,123,456,789,

Если же в фильтре не было выбрано ни одно значение, т.е. изначально была передана пустая строка или null, то в параметре будет строка вида

,,

2. Теперь каждое значение в строке выделено запятыми с обеих сторон. Для проверки, входит ли ID в список выбранных в фильтре и переданных в параметр значений, достаточно дополнить значение самого проверяемого ID запятыми слева и справа и проверить, входит ли такая подстрока в строку со списком значений. Например, входит ли в строку \',123,456,789,\' подстрока \',456,\' где 456 — это проверяемый ID.

В SQL-запросе для проверки такого параметра можно использовать выражение вида:

WHERE ((\@subcat = \',,\') OR (CHARINDEX(\',\'+CAST(T.SubcatID as varchar(max))+\',\', \@subcat) > 0))

или

WHERE ((\@subcat = \',,\') OR (\@subcat like \'%,\'+CAST(T.SubcatID as varchar(max))+\',%\'))

или функцию GetMultiValues:

WHERE ((\@subcat = \',,\') OR (CAST(T.SubcatID as varchar(max)) IN (SELECT \Value\ FROM \dbo.GetMultiValues))

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

В шапке отчета, как правило, выводится список значений, выбранных в фильтре:

Список значений, преобразованный в строку

Для формирования такого списка можно использовать дополнительную таблицу, создаваемую запросом (выводится полное название категории):

SELECT \  (select REPLACE(S.FullPath, \'\u2044\',\'\\') +\';  \' \      FROM Subcategories S \      WHERE CHARINDEX(\',\'+CAST(S.SubcatID as varchar(max))+\',\', \@subcat) > 0 \      FOR XML PATH(\'\')\   ) as SubcatNames \ FROM Subcategories

В примере таблица называется SubcatSelected. В шапке отчета текст может формироваться, например, следующим образом:

для пользователей в группах: [[FilterSubcat==",,"?" — ":SubcatSelected.SubcatNames]]

Примеры аналогичных таблиц и выражений для фильтра по пользователям и по категориям:

По пользователям:

по исполнителям: [[FilterPerformer==",,"?" — ":PerformerSelected.UserNames]]   Таблица PerformerSelected: SELECT \   (select U.SurnameName+\';  \' \   FROM UserNames U \   WHERE CHARINDEX(\',\'+CAST(U.UserID as varchar(max))+\',\', \@UserID) > 0 \   FOR XML PATH(\'\')\   ) as UserNames \ FROM UserNames По группам:

в группах: [[FilterGroup==",,"?" — ":GroupSelected.GroupNames]] Таблица GroupSelected: SELECT \   (select G.Descr+\';  \' \   FROM Groups G\   WHERE CHARINDEX(\',\'+CAST(G.GroupID as varchar(max))+\',\', \@GroupID) > 0 \   FOR XML PATH(\'\')\   ) as GroupNames \ FROM Groups Если проверку вхождения значения в список выбранных в фильтре нужно добавить в код, можно использовать выражение вида:

// если в списке отображаемых показателей отмечен показатель "план"\ if (Report.GetParameterValue("FilterColumns").ToString().Contains("план"))

ℹ️ Чтобы каждый элемент списка выводился с новой строки, для более удобного восприятия, используйте тег переноса строки

Перенос строки

Для переноса строки можно использовать html тег \<br/>.

Использование тега \<br/> в текстовом поле

Однако если тег используется не напрямую в поле, а в запросе, символы \'\<\' и \'>\' подменяются на \'<\' и \'>\', и перенос строки не выполняется. Чтобы исправить это, в запрос можно добавить обратную замену символов.

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

SELECT REPLACE(  REPLACE( \   (select G.Descr+\', \<br/>\' \   FROM Groups G \   WHERE CHARINDEX(\',\'+CAST(G.GroupID as varchar(max))+\',\', \@GroupID) > 0 \   FOR XML PATH(\'\')\   ), \'<\', \'\<\' ), \'>\', \'>\' ) as GroupNames \ FROM Groups

В этом случае шапка отчета будет выглядеть так:

Отображение результатов после обратной замены для тега \<br/> в запросе

ℹ️ Для поля должны быть включены html-теги:

Включение html-тегов для поля

Рабочие минуты как строка

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

Передаваемый в нее параметр — число рабочих минут. Оно может рассчитываться в SQL-запросе с помощью хранимой функции  dbo.tc_DiffWorkingMinutes(StartTime, EndTime).

В данной функции учитывается системная настройка "Число рабочих минут в дне". Поэтому для работы в отчет необходимо добавить таблицу Settings вашей базы данных.

Добавление таблицы Settings в источники данных

Код функции для преобразования рабочих минут в строку:

private string diff_str(int WMinutes)\    {\      int WorkDayDuration, WDays, WHours;\      string res_str;\      DataSourceBase tsettings = Report.GetDataSource("Settings");\      tsettings.Init();\      WorkDayDuration = (int)tsettings["WorkMinutesInDay"\;]\      WDays = WMinutes/WorkDayDuration;\      WMinutes = WMinutes — WDays*WorkDayDuration;\      WHours = WMinutes/60;\      WMinutes = WMinutes — WHours*60;\      res_str = "";\      if (WDays > 0) \        res_str = ToString(WDays) + " дн. ";\      if (WHours > 0)\        res_str += ToString(WHours) + " ч. ";\      if (WMinutes > 0)   \        res_str += ToString(WMinutes) + " мин.";\      return res_str;\    } Эта функция может использоваться в текстовом поле или в ячейке таблицы:

Использование функции в ячейке отчета.

Результат выглядит так:

Отображение результата в ячейке отчета

Объем времени как строка

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

\TimeTotal\ мин.     \ ([Format("{0:00} дн. {1:00} ч. {2:00} мин.", TimeSpan.FromMinutes(\TimeTotal).Days, TimeSpan.FromMinutes(\TimeTotal).Hours,

TimeSpan.FromMinutes([TimeTotal).Minutes)])]

Результат имеет вид:

853 мин.

(00 дн. 14 ч. 13 мин.)

Выгрузка в Excel с формулами

При экспорте из отчета в Excel ячейки могут содержать не только значения, но и формулы. На этой странице описано, как можно упростить или автоматизировать работу с формулами.

Имя колонки по номеру

Для автоматизации формирования формулы удобно использовать функцию, которая возвращает имя (букву) колонки по ее номеру (наприме, для 1 — A, для 2 — B и т.д.).

Имя колонки в Excel по номеру:

// Имя колонки в Excel по номеру \ private string ColumnNumberToName(int col_num)\ {\ // See if it\'s out of bounds\ if (col_num \< 1) return "A";\ // Calculate the letters\ string result = "";\ while (col_num > 0)\ {\   // Get the least significant digit\   col_num -= 1;\   int digit = col_num % 26;\   // Convert the digit into a letter\   result = (char)((int)\'A\' + digit) + result;\   col_num = (int)(col_num / 26);\ }\ return result;\ }

Последовательность расчета формул

Если пользователи в дальнейшем будут работать с выгруженным файлом Excel, в том числе будут менять значения ячеек, то данные должны пересчитываться по формулам. В этом случае может быть важна последовательность расчета формул. Чтобы учесть эту последовательность, можно использовать следующее решение: В начало каждой формулы помещается цифра, определяющая очередность выполнения. Например, все формулы, начинающиеся с цифры 1, будут выполняться в первую очередь, все формулы, начинающиеся с 2 — во вторую очередь и т.д. Порядок выполнения формул в рамках одной очереди неважен.\ Пример процедур в отчете:

// Формирование формулы Excel 1-ой очереди\ private void Matrix2ResultTable_AfterData(object sender, EventArgs e)\ {\ int[] rowIndices = Matrix1.Data.Rows.GetTerminalIndices();\ int[] columnIndices = Matrix1.Data.Columns.GetTerminalIndices();\ TableResult table = sender as TableResult;\ for (int i = 1; i \< table.Rows.Count-1; i++)\ {\   string rownum = (i + 17).ToString();\   string str1 = ColumnNumberToName(11 + 1)+rownum;\   string str2 = ColumnNumberToName(11 + columnIndices.Length)+rownum;\   string resultstr = "1=СУММ(" + str1 + ":" + str2 + ")";\   table[10, i.Text] = resultstr;\ }\ }\ // Формирование формулы Excel 2-ей очереди\ private void Matrix2_AfterTotals(object sender, EventArgs e)\ {\ int[] rowIndices = Matrix2.Data.Rows.GetTerminalIndices();\ int[] columnIndices = Matrix2.Data.Columns.GetTerminalIndices();\ foreach (int columnIndex in columnIndices)\ {\   string colnum = ColumnNumberToName(10);\   string value = "2=СУММ("+colnum+"18:"+colnum+(18+rowIndices.Length-2).ToString()+")";\   Matrix2.Data.SetValue(columnIndex, rowIndices.Length-1, value);\ }\ } Для запуска очередности выполнения формул можно использовать макрос Excel. Этот макрос может быть сохранен в отдельном файле .xlam и загружаться/запускаться как AddIn.

Пример макроса

Sub CalcFunc()\   Dim Sh As Worksheet\   Dim Cl As Range\   Dim tmpVal As Variant\   Dim Cell(1) As Variant\   Dim Ar() As Variant\   Dim i As Integer\   Dim j As Integer\   Dim isSorted As Boolean\   Dim cnt As Integer\    i = 0\   For Each Sh In Worksheets\       For Each Cl In Sh.UsedRange.Cells\           If Right(Left(Cl.Value, 2), 1) = "=" Or Left(Cl.Value, 1) = "=" Then\               ReDim Preserve Ar(i)\                Cell(0) = Cl.Value\                Cell(1) = Cl.Address\                Ar(i) = Cell\                i = i + 1\           End If\       Next Cl\   Next Sh\    i = 0\    cnt = 0\    isSorted = False\   Do While Not isSorted\        cnt = cnt + 1\        isSorted = True\        tmpVal = Ar(0)\       For j = 1 To UBound(Ar)\           If Left(tmpVal(0), 1) = "=" Then\               If Left(Ar(i)(0), 1) \<> Left(Ar(j)(0), 1) Then\                    isSorted = False\               End If\                Ar(i) = Ar(j)\                Ar(j) = tmpVal\           ElseIf Left(Ar(j)(0), 1) = "=" Then\                tmpVal = Ar(j)\           Else\               If CInt(Left(tmpVal(0), 1)) > CInt(Left(Ar(j)(0), 1)) Then\                    Ar(i) = Ar(j)\                    Ar(j) = tmpVal\                    isSorted = False\               Else\                    tmpVal = Ar(j)\               End If\           End If\            i = j\       Next\       If cnt >= 10000 Then\           MsgBox "Error While Loop"\            isSorted = True\       End If\   Loop\ For Each tmpVal In Ar\   If Left(tmpVal(0), 1) = "=" Then\        Range(tmpVal(1)).FormulaLocal = tmpVal(0)\   Else\        Range(tmpVal(1)).FormulaLocal = Right(tmpVal(0), Len(tmpVal(0)) — 1)\   End If\ Next tmpVal\    Worksheets(1).Visible = True\    Worksheets(1).Cells(1, 1).Select\ End Sub