Коды 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