Dynamic Reportların sütun genişlikleri ve Windows Emoji
This commit is contained in:
parent
9129bc17db
commit
0e1e83fc00
7 changed files with 413 additions and 161 deletions
|
|
@ -307,10 +307,17 @@ public class DynamicFormReport : XtraReport
|
|||
if (TryGetFormattedValue(field, row, out var text))
|
||||
{
|
||||
label.Text = text;
|
||||
label.Font = new DXFont(
|
||||
DynamicReportFontHelper.GetFontFamilyForText(text),
|
||||
DynamicReportFontHelper.DataFontSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
label.ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", $"[{field.BindingName}]"));
|
||||
label.ExpressionBindings.Add(new ExpressionBinding(
|
||||
"BeforePrint",
|
||||
"Font.Name",
|
||||
DynamicReportImageHelper.CreateFontFamilyExpression($"[{field.BindingName}]")));
|
||||
}
|
||||
|
||||
return label;
|
||||
|
|
|
|||
|
|
@ -115,6 +115,9 @@ public class DynamicGridReport : XtraReport
|
|||
var visibleFields = DynamicReportGridStateHelper.GetVisibleFields(
|
||||
availableFields,
|
||||
gridState?.CustomizationData);
|
||||
var stateWidths = DynamicReportGridStateHelper.GetColumnWidths(
|
||||
gridState?.CustomizationData,
|
||||
GetParameter(parameters, "columnWidths"));
|
||||
|
||||
if (visibleFields.Count == 0)
|
||||
{
|
||||
|
|
@ -174,12 +177,12 @@ public class DynamicGridReport : XtraReport
|
|||
localizer);
|
||||
reportColumns = await ApplyImageCountsAsync(reportColumns, dynamicDataRepository, selectQuery, connectionString, imageBaseUrl);
|
||||
|
||||
ConfigurePage(reportColumns, isSubReport);
|
||||
ConfigurePage(reportColumns, localizer, stateWidths, isSubReport);
|
||||
ConfigureDataSource(
|
||||
selectQuery,
|
||||
connectionString,
|
||||
dataSourceType);
|
||||
BuildLayout(listForm, reportColumns, groupColumns, localizer, imageBaseUrl, currentUser.UserName, criteriaText, isSubReport);
|
||||
BuildLayout(listForm, reportColumns, groupColumns, localizer, imageBaseUrl, currentUser.UserName, criteriaText, isSubReport, stateWidths);
|
||||
RequestParameters = false;
|
||||
}
|
||||
|
||||
|
|
@ -207,12 +210,18 @@ public class DynamicGridReport : XtraReport
|
|||
}
|
||||
|
||||
#pragma warning disable CA1416
|
||||
private void ConfigurePage(IReadOnlyCollection<ReportColumn> visibleFields, bool isSubReport)
|
||||
private void ConfigurePage(
|
||||
IReadOnlyCollection<ReportColumn> visibleFields,
|
||||
IStringLocalizer<PlatformResource> localizer,
|
||||
IReadOnlyDictionary<string, float> stateWidths,
|
||||
bool isSubReport)
|
||||
{
|
||||
Margins = CreateReportMargins(HorizontalMargin, HorizontalMargin, 35, 35);
|
||||
PaperKind = DevExpress.Drawing.Printing.DXPaperKind.A4;
|
||||
var portraitContentWidth = A4PortraitWidth - Margins.Left - Margins.Right;
|
||||
var requestedContentWidth = visibleFields.Sum(x => x.IsImage ? GetImageColumnWidth(x) : GetPreferredColumnWidth(x.Field));
|
||||
var requestedContentWidth = visibleFields.Sum(x => x.IsImage
|
||||
? GetImageColumnWidth(x)
|
||||
: DynamicReportGridStateHelper.GetPreferredColumnWidth(x.Field, localizer, stateWidths));
|
||||
Landscape = !isSubReport &&
|
||||
(visibleFields.Count >= LandscapeColumnThreshold || requestedContentWidth > portraitContentWidth);
|
||||
Font = DynamicReportFontHelper.CreateDataFont();
|
||||
|
|
@ -294,7 +303,8 @@ public class DynamicGridReport : XtraReport
|
|||
string imageBaseUrl,
|
||||
string currentUserName,
|
||||
string criteriaText,
|
||||
bool isSubReport)
|
||||
bool isSubReport,
|
||||
IReadOnlyDictionary<string, float> stateWidths)
|
||||
{
|
||||
Bands.Clear();
|
||||
|
||||
|
|
@ -352,8 +362,7 @@ public class DynamicGridReport : XtraReport
|
|||
|
||||
var detailIndent = GetGroupDetailIndent(groupFields.Count, pageWidth);
|
||||
var tableWidth = pageWidth - detailIndent;
|
||||
var detailWidths = CalculateWidths(fields, tableWidth);
|
||||
var headerWidths = CreateIndentedHeaderWidths(detailWidths, detailIndent);
|
||||
var detailWidths = DynamicReportGridStateHelper.CalculateWidths(fields, tableWidth, localizer, stateWidths);
|
||||
var headerTable = CreateTable(pageWidth, 28);
|
||||
var headerRow = new XRTableRow { HeightF = 28 };
|
||||
var detailTable = CreateTable(tableWidth, detailHeight, detailIndent);
|
||||
|
|
@ -362,10 +371,15 @@ public class DynamicGridReport : XtraReport
|
|||
var footerTable = CreateTable(tableWidth, 24, detailIndent);
|
||||
var footerRow = new XRTableRow { HeightF = 24 };
|
||||
|
||||
if (detailIndent > 0)
|
||||
{
|
||||
headerRow.Cells.Add(CreateIndentHeaderCell(detailIndent));
|
||||
}
|
||||
|
||||
for (var i = 0; i < fields.Count; i++)
|
||||
{
|
||||
var field = fields[i];
|
||||
headerRow.Cells.Add(CreateHeaderCell(field, headerWidths[i], localizer));
|
||||
headerRow.Cells.Add(CreateHeaderCell(field, detailWidths[i], localizer));
|
||||
detailRow.Cells.Add(CreateDetailCell(field, detailWidths[i], imageBaseUrl, detailHeight));
|
||||
footerRow.Cells.Add(CreateFooterCell(field, detailWidths[i]));
|
||||
}
|
||||
|
|
@ -457,19 +471,6 @@ public class DynamicGridReport : XtraReport
|
|||
return Math.Min(groupCount * GroupIndentWidth, pageWidth * 0.25F);
|
||||
}
|
||||
|
||||
private static float[] CreateIndentedHeaderWidths(float[] detailWidths, float detailIndent)
|
||||
{
|
||||
if (detailWidths.Length == 0 || detailIndent <= 0)
|
||||
{
|
||||
return detailWidths;
|
||||
}
|
||||
|
||||
var headerWidths = detailWidths.ToArray();
|
||||
headerWidths[0] += detailIndent;
|
||||
|
||||
return headerWidths;
|
||||
}
|
||||
|
||||
private static XRTable CreateTable(float width, float height, float left = 0)
|
||||
{
|
||||
return new XRTable
|
||||
|
|
@ -481,6 +482,17 @@ public class DynamicGridReport : XtraReport
|
|||
};
|
||||
}
|
||||
|
||||
private static XRTableCell CreateIndentHeaderCell(float width)
|
||||
{
|
||||
return new XRTableCell
|
||||
{
|
||||
WidthF = width,
|
||||
Weight = width,
|
||||
BackColor = System.Drawing.Color.FromArgb(238, 242, 247),
|
||||
Padding = new PaddingInfo(0, 0, 0, 0)
|
||||
};
|
||||
}
|
||||
|
||||
private static XRTableCell CreateHeaderCell(
|
||||
ReportColumn column,
|
||||
float width,
|
||||
|
|
@ -532,10 +544,15 @@ public class DynamicGridReport : XtraReport
|
|||
Multiline = true,
|
||||
CanGrow = true
|
||||
};
|
||||
var valueExpression = DynamicReportImageHelper.CreateFormatValueExpression($"[{column.BindingName}]", field, column.EditorType);
|
||||
cell.ExpressionBindings.Add(new ExpressionBinding(
|
||||
"BeforePrint",
|
||||
"Text",
|
||||
DynamicReportImageHelper.CreateFormatValueExpression($"[{column.BindingName}]", field, column.EditorType)));
|
||||
valueExpression));
|
||||
cell.ExpressionBindings.Add(new ExpressionBinding(
|
||||
"BeforePrint",
|
||||
"Font.Name",
|
||||
DynamicReportImageHelper.CreateFontFamilyExpression(valueExpression)));
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
|
@ -614,66 +631,6 @@ public class DynamicGridReport : XtraReport
|
|||
};
|
||||
}
|
||||
|
||||
private static float[] CalculateWidths(IReadOnlyList<ReportColumn> fields, float pageWidth)
|
||||
{
|
||||
var raw = fields.Select(x => x.IsImage ? GetImageColumnWidth(x) : GetPreferredColumnWidth(x.Field)).ToArray();
|
||||
var total = raw.Sum();
|
||||
if (total <= 0)
|
||||
{
|
||||
return fields.Select(_ => pageWidth / fields.Count).ToArray();
|
||||
}
|
||||
|
||||
return raw.Select(x => pageWidth * x / total).ToArray();
|
||||
}
|
||||
|
||||
private static float GetPreferredColumnWidth(ListFormField field)
|
||||
{
|
||||
if (field.Width > 0)
|
||||
{
|
||||
return Math.Max(field.Width.Value, 60);
|
||||
}
|
||||
|
||||
var caption = string.IsNullOrWhiteSpace(field.CaptionName) ? field.FieldName : field.CaptionName;
|
||||
var name = field.FieldName ?? string.Empty;
|
||||
var text = $"{caption} {name}".ToLowerInvariant();
|
||||
|
||||
if (field.SourceDbType == DbType.Boolean)
|
||||
{
|
||||
return 75;
|
||||
}
|
||||
|
||||
if (DynamicReportImageHelper.IsNumeric(field.SourceDbType))
|
||||
{
|
||||
return 70;
|
||||
}
|
||||
|
||||
if (field.SourceDbType is DbType.Date or DbType.DateTime or DbType.DateTime2 or DbType.DateTimeOffset)
|
||||
{
|
||||
return 115;
|
||||
}
|
||||
|
||||
if (text.Contains("description") ||
|
||||
text.Contains("explanation") ||
|
||||
text.Contains("note") ||
|
||||
text.Contains("comment") ||
|
||||
text.Contains("address") ||
|
||||
text.Contains("content") ||
|
||||
text.Contains("html") ||
|
||||
text.Contains("text"))
|
||||
{
|
||||
return 220;
|
||||
}
|
||||
|
||||
if (text.Contains("name") ||
|
||||
text.Contains("title") ||
|
||||
text.Contains("subject"))
|
||||
{
|
||||
return 160;
|
||||
}
|
||||
|
||||
return Math.Clamp((caption?.Length ?? 0) * 9F + 36F, 85F, 180F);
|
||||
}
|
||||
|
||||
private static bool HasAnySummary(IEnumerable<ReportColumn> fields)
|
||||
{
|
||||
return fields.Any(x => GetSummaryType(x.Field.TotalSummaryJson) != null);
|
||||
|
|
@ -737,7 +694,10 @@ public class DynamicGridReport : XtraReport
|
|||
bool IsImage,
|
||||
bool IsHtml,
|
||||
string EditorType,
|
||||
int ImageCount = 1) : DynamicReportSqlBuilder.IReportLookupColumn;
|
||||
int ImageCount = 1) : DynamicReportSqlBuilder.IReportLookupColumn, DynamicReportGridStateHelper.IReportColumnWidth
|
||||
{
|
||||
public float ImageWidth => GetImageColumnWidth(this);
|
||||
}
|
||||
|
||||
private static SummaryFunc? GetSummaryType(string totalSummaryJson)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ internal static class DynamicReportFontHelper
|
|||
public const float DataFontSize = 8.5F;
|
||||
|
||||
private const string WindowsFontFamily = "Arial";
|
||||
private const string WindowsEmojiFontFamily = "Segoe UI Emoji";
|
||||
private const string LinuxFontFamily = "Liberation Sans";
|
||||
|
||||
public static DXFont CreateHeaderFont(DXFontStyle style = DXFontStyle.Bold) =>
|
||||
|
|
@ -19,10 +20,48 @@ internal static class DynamicReportFontHelper
|
|||
|
||||
public static DXFont CreateValueFont(float size, DXFontStyle style = DXFontStyle.Regular)
|
||||
{
|
||||
var fontFamily = OperatingSystem.IsLinux()
|
||||
return new DXFont(GetDefaultFontFamily(), size, style);
|
||||
}
|
||||
|
||||
public static string GetFontFamilyForText(string text)
|
||||
{
|
||||
return OperatingSystem.IsWindows() && ContainsEmojiOrSymbol(text)
|
||||
? WindowsEmojiFontFamily
|
||||
: GetDefaultFontFamily();
|
||||
}
|
||||
|
||||
private static bool ContainsEmojiOrSymbol(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < text.Length; i++)
|
||||
{
|
||||
int codePoint;
|
||||
if (char.IsHighSurrogate(text[i]) && i + 1 < text.Length && char.IsLowSurrogate(text[i + 1]))
|
||||
{
|
||||
codePoint = char.ConvertToUtf32(text, i);
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
codePoint = text[i];
|
||||
}
|
||||
|
||||
if (codePoint is >= 0x1F000 and <= 0x1FAFF ||
|
||||
codePoint is >= 0x2600 and <= 0x27BF)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string GetDefaultFontFamily() =>
|
||||
OperatingSystem.IsLinux()
|
||||
? LinuxFontFamily
|
||||
: WindowsFontFamily;
|
||||
|
||||
return new DXFont(fontFamily, size, style);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Sozsoft.Platform;
|
||||
using Sozsoft.Platform.Entities;
|
||||
using Sozsoft.Platform.ListForms;
|
||||
using Sozsoft.Platform.Localization;
|
||||
|
||||
namespace Sozsoft.Reports.PredefinedReports;
|
||||
|
||||
internal static class DynamicReportGridStateHelper
|
||||
{
|
||||
public interface IReportColumnWidth
|
||||
{
|
||||
ListFormField Field { get; }
|
||||
bool IsImage { get; }
|
||||
float ImageWidth { get; }
|
||||
}
|
||||
|
||||
public static string GetFilterName(ListForm listForm, string prefix) =>
|
||||
$"{prefix}-{GetStateStorageKey(listForm)}";
|
||||
|
||||
|
|
@ -79,6 +89,74 @@ internal static class DynamicReportGridStateHelper
|
|||
}
|
||||
}
|
||||
|
||||
public static Dictionary<string, float> GetColumnWidths(string gridStateJson, string columnWidthsJson = null)
|
||||
{
|
||||
var widths = TryGetColumnStates(gridStateJson, out var statesByField)
|
||||
? statesByField
|
||||
.Where(x => x.Value.Width.HasValue && x.Value.Width.Value > 0)
|
||||
.ToDictionary(x => x.Key, x => x.Value.Width.Value, StringComparer.OrdinalIgnoreCase)
|
||||
: new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var item in ParseColumnWidths(columnWidthsJson))
|
||||
{
|
||||
widths[item.Key] = item.Value;
|
||||
}
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
public static float[] CalculateWidths<TColumn>(
|
||||
IReadOnlyList<TColumn> columns,
|
||||
float pageWidth,
|
||||
IStringLocalizer<PlatformResource> localizer,
|
||||
IReadOnlyDictionary<string, float> stateWidths = null)
|
||||
where TColumn : IReportColumnWidth
|
||||
{
|
||||
if (columns.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var ideal = columns
|
||||
.Select(column => column.IsImage
|
||||
? column.ImageWidth
|
||||
: GetPreferredColumnWidth(column.Field, localizer, stateWidths))
|
||||
.ToArray();
|
||||
var total = ideal.Sum();
|
||||
if (total <= 0)
|
||||
{
|
||||
return columns.Select(_ => pageWidth / columns.Count).ToArray();
|
||||
}
|
||||
|
||||
return ideal.Select(width => pageWidth * width / total).ToArray();
|
||||
}
|
||||
|
||||
public static float GetPreferredColumnWidth(
|
||||
ListFormField field,
|
||||
IStringLocalizer<PlatformResource> localizer = null,
|
||||
IReadOnlyDictionary<string, float> stateWidths = null)
|
||||
{
|
||||
var caption = localizer == null
|
||||
? (string.IsNullOrWhiteSpace(field.CaptionName) ? field.FieldName : field.CaptionName)
|
||||
: DynamicReportImageHelper.GetLocalizedFieldCaption(field, localizer);
|
||||
var name = field.FieldName ?? string.Empty;
|
||||
var calculatedWidth = CalculateTextColumnWidth(field, caption, name);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(field.FieldName) &&
|
||||
stateWidths?.TryGetValue(field.FieldName, out var stateWidth) == true &&
|
||||
stateWidth > 0)
|
||||
{
|
||||
return Math.Max(stateWidth, 40F);
|
||||
}
|
||||
|
||||
if (field.Width > 0)
|
||||
{
|
||||
return Math.Max(field.Width.Value, 40F);
|
||||
}
|
||||
|
||||
return calculatedWidth;
|
||||
}
|
||||
|
||||
private static string GetStateStorageKey(ListForm listForm)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(listForm.StateStoringJson))
|
||||
|
|
@ -151,10 +229,14 @@ internal static class DynamicReportGridStateHelper
|
|||
groupIndexProperty.TryGetInt32(out var groupValue)
|
||||
? groupValue
|
||||
: (int?)null;
|
||||
var width = TryGetProperty(column, "width", out var widthProperty) &&
|
||||
TryGetFloat(widthProperty, out var widthValue)
|
||||
? widthValue
|
||||
: (float?)null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(fieldName))
|
||||
{
|
||||
states.Add(new GridColumnState(fieldName, visible, visibleIndex, groupIndex));
|
||||
states.Add(new GridColumnState(fieldName, visible, visibleIndex, groupIndex, width));
|
||||
}
|
||||
|
||||
if (TryGetProperty(column, "columns", out var childColumns) &&
|
||||
|
|
@ -182,6 +264,114 @@ internal static class DynamicReportGridStateHelper
|
|||
return null;
|
||||
}
|
||||
|
||||
private static bool TryGetFloat(JsonElement element, out float value)
|
||||
{
|
||||
if (element.ValueKind == JsonValueKind.Number && element.TryGetSingle(out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (element.ValueKind == JsonValueKind.String &&
|
||||
float.TryParse(element.GetString(), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static float CalculateTextColumnWidth(ListFormField field, string caption, string name)
|
||||
{
|
||||
if (field.SourceDbType == DbType.Boolean)
|
||||
{
|
||||
return Math.Max(75F, MeasureTextWidth(caption, 7.2F, 30F, 68F, 100F));
|
||||
}
|
||||
|
||||
if (DynamicReportImageHelper.IsNumeric(field.SourceDbType))
|
||||
{
|
||||
return Math.Max(70F, MeasureTextWidth(caption, 7.2F, 30F, 68F, 120F));
|
||||
}
|
||||
|
||||
if (field.SourceDbType is DbType.Date or DbType.DateTime or DbType.DateTime2 or DbType.DateTimeOffset)
|
||||
{
|
||||
return Math.Max(115F, MeasureTextWidth(caption, 7.2F, 30F, 90F, 145F));
|
||||
}
|
||||
|
||||
return Math.Max(
|
||||
MeasureTextWidth(caption, 7.2F, 30F, 85F, 220F),
|
||||
MeasureTextWidth(name, 5.6F, 28F, 85F, 180F));
|
||||
}
|
||||
|
||||
private static float MeasureTextWidth(
|
||||
string value,
|
||||
float characterWidth,
|
||||
float padding,
|
||||
float minimum,
|
||||
float maximum)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.Clamp(value.Trim().Length * characterWidth + padding, minimum, maximum);
|
||||
}
|
||||
|
||||
private static Dictionary<string, float> ParseColumnWidths(string columnWidthsJson)
|
||||
{
|
||||
var widths = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);
|
||||
if (string.IsNullOrWhiteSpace(columnWidthsJson))
|
||||
{
|
||||
return widths;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var document = JsonDocument.Parse(columnWidthsJson);
|
||||
if (document.RootElement.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
foreach (var property in document.RootElement.EnumerateObject())
|
||||
{
|
||||
if (TryGetFloat(property.Value, out var width) && width > 0)
|
||||
{
|
||||
widths[property.Name] = width;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (document.RootElement.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
AddColumnWidths(document.RootElement, widths);
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
}
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
private static void AddColumnWidths(JsonElement columns, IDictionary<string, float> widths)
|
||||
{
|
||||
foreach (var column in columns.EnumerateArray())
|
||||
{
|
||||
var fieldName = GetColumnFieldName(column);
|
||||
if (!string.IsNullOrWhiteSpace(fieldName) &&
|
||||
TryGetProperty(column, "width", out var widthProperty) &&
|
||||
TryGetFloat(widthProperty, out var width) &&
|
||||
width > 0)
|
||||
{
|
||||
widths[fieldName] = width;
|
||||
}
|
||||
|
||||
if (TryGetProperty(column, "columns", out var childColumns) &&
|
||||
childColumns.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
AddColumnWidths(childColumns, widths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ListFormField> GetGroupFieldsFromParameter(
|
||||
IReadOnlyList<ListFormField> availableFields,
|
||||
string groupParameter)
|
||||
|
|
@ -249,5 +439,5 @@ internal static class DynamicReportGridStateHelper
|
|||
return false;
|
||||
}
|
||||
|
||||
private sealed record GridColumnState(string FieldName, bool Visible, int VisibleIndex, int? GroupIndex);
|
||||
private sealed record GridColumnState(string FieldName, bool Visible, int VisibleIndex, int? GroupIndex, float? Width);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ internal static class DynamicReportImageHelper
|
|||
private const string ImageUrlFunctionName = "SozReportImageUrl";
|
||||
private const string ImageSourceFunctionName = "SozReportImageSource";
|
||||
private const string FormatValueFunctionName = "SozReportFormatValue";
|
||||
private const string FontFamilyFunctionName = "SozReportFontFamily";
|
||||
private static readonly Regex ReportPagingRegex = new(
|
||||
@"\s+OFFSET\s+\d+\s+ROWS(?:\s+FETCH\s+NEXT\s+\d+\s+ROWS\s+ONLY)?\s*$",
|
||||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
||||
|
|
@ -55,6 +56,7 @@ internal static class DynamicReportImageHelper
|
|||
RegisterImageFunction(new ReportImageUrlFunction());
|
||||
RegisterImageFunction(new ReportImageSourceFunction());
|
||||
RegisterImageFunction(new ReportFormatValueFunction());
|
||||
RegisterImageFunction(new ReportFontFamilyFunction());
|
||||
}
|
||||
|
||||
public static Dictionary<string, SelectField> CreateSelectFieldMap(IEnumerable<SelectField> selectFields)
|
||||
|
|
@ -487,13 +489,13 @@ internal static class DynamicReportImageHelper
|
|||
{
|
||||
WidthF = width,
|
||||
Weight = width,
|
||||
Padding = new PaddingInfo(3, 3, 3, 3),
|
||||
Padding = new PaddingInfo(0, 0, 0, 0),
|
||||
CanGrow = false
|
||||
};
|
||||
|
||||
for (var i = 0; i < safeImageCount; i++)
|
||||
{
|
||||
var pictureBox = CreatePictureBox(i, width, DefaultGridImageHeight - 6, safeImageCount);
|
||||
var pictureBox = CreatePictureBox(i, width, DefaultGridImageHeight, safeImageCount);
|
||||
pictureBox.Visible = true;
|
||||
pictureBox.ExpressionBindings.Add(new ExpressionBinding(
|
||||
"BeforePrint",
|
||||
|
|
@ -602,6 +604,11 @@ internal static class DynamicReportImageHelper
|
|||
return $"{FormatValueFunctionName}({valueExpression}, '{field.SourceDbType}', '{EscapeExpressionString(GetEffectiveFormat(field))}', '{EscapeExpressionString(editorType)}')";
|
||||
}
|
||||
|
||||
public static string CreateFontFamilyExpression(string valueExpression)
|
||||
{
|
||||
return $"{FontFamilyFunctionName}({valueExpression})";
|
||||
}
|
||||
|
||||
public static string FormatReportDate(DateTime value)
|
||||
{
|
||||
var culture = CultureInfo.CurrentCulture;
|
||||
|
|
@ -2006,4 +2013,34 @@ internal static class DynamicReportImageHelper
|
|||
: DbType.String;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ReportFontFamilyFunction : ICustomFunctionOperatorBrowsable
|
||||
{
|
||||
public string Name => FontFamilyFunctionName;
|
||||
public int MinOperandCount => 1;
|
||||
public int MaxOperandCount => 1;
|
||||
public FunctionCategory Category => FunctionCategory.Text;
|
||||
public string Description => "Returns a report font family with emoji fallback.";
|
||||
|
||||
public object Evaluate(params object[] operands)
|
||||
{
|
||||
var text = Convert.ToString(operands.ElementAtOrDefault(0), CultureInfo.CurrentCulture);
|
||||
return DynamicReportFontHelper.GetFontFamilyForText(text);
|
||||
}
|
||||
|
||||
public Type ResultType(params Type[] operands)
|
||||
{
|
||||
return typeof(string);
|
||||
}
|
||||
|
||||
public bool IsValidOperandCount(int count)
|
||||
{
|
||||
return count == 1;
|
||||
}
|
||||
|
||||
public bool IsValidOperandType(int operandIndex, int operandCount, Type type)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,6 +124,9 @@ public class DynamicTreeReport : XtraReport
|
|||
var visibleFields = DynamicReportGridStateHelper.GetVisibleFields(
|
||||
availableFields,
|
||||
gridState?.CustomizationData);
|
||||
var stateWidths = DynamicReportGridStateHelper.GetColumnWidths(
|
||||
gridState?.CustomizationData,
|
||||
GetParameter(parameters, "columnWidths"));
|
||||
|
||||
if (visibleFields.Count == 0)
|
||||
{
|
||||
|
|
@ -169,19 +172,24 @@ public class DynamicTreeReport : XtraReport
|
|||
var orderedRows = OrderTreeRows(sourceRows, treeOptions).ToList();
|
||||
reportColumns = ApplyImageCounts(reportColumns, orderedRows, imageBaseUrl);
|
||||
|
||||
ConfigurePage(reportColumns);
|
||||
ConfigurePage(reportColumns, localizer, stateWidths);
|
||||
ConfigureDataSource(orderedRows, reportColumns);
|
||||
BuildLayout(listForm, reportColumns, localizer, imageBaseUrl, currentUser.UserName, criteriaText, isSubReport);
|
||||
BuildLayout(listForm, reportColumns, localizer, imageBaseUrl, currentUser.UserName, criteriaText, isSubReport, stateWidths);
|
||||
RequestParameters = false;
|
||||
}
|
||||
|
||||
#pragma warning disable CA1416
|
||||
private void ConfigurePage(IReadOnlyCollection<ReportColumn> visibleFields)
|
||||
private void ConfigurePage(
|
||||
IReadOnlyCollection<ReportColumn> visibleFields,
|
||||
IStringLocalizer<PlatformResource> localizer,
|
||||
IReadOnlyDictionary<string, float> stateWidths)
|
||||
{
|
||||
Margins = CreateReportMargins(HorizontalMargin, HorizontalMargin, 35, 35);
|
||||
PaperKind = DevExpress.Drawing.Printing.DXPaperKind.A4;
|
||||
var portraitContentWidth = A4PortraitWidth - Margins.Left - Margins.Right;
|
||||
var requestedContentWidth = visibleFields.Sum(x => x.IsImage ? GetImageColumnWidth(x) : GetPreferredColumnWidth(x.Field));
|
||||
var requestedContentWidth = visibleFields.Sum(x => x.IsImage
|
||||
? GetImageColumnWidth(x)
|
||||
: DynamicReportGridStateHelper.GetPreferredColumnWidth(x.Field, localizer, stateWidths));
|
||||
Landscape = visibleFields.Count >= LandscapeColumnThreshold || requestedContentWidth > portraitContentWidth;
|
||||
Font = DynamicReportFontHelper.CreateDataFont();
|
||||
}
|
||||
|
|
@ -423,7 +431,8 @@ public class DynamicTreeReport : XtraReport
|
|||
string imageBaseUrl,
|
||||
string currentUserName,
|
||||
string criteriaText,
|
||||
bool isSubReport)
|
||||
bool isSubReport,
|
||||
IReadOnlyDictionary<string, float> stateWidths)
|
||||
{
|
||||
Bands.Clear();
|
||||
|
||||
|
|
@ -466,7 +475,7 @@ public class DynamicTreeReport : XtraReport
|
|||
TextAlignment = TextAlignment.MiddleRight
|
||||
});
|
||||
|
||||
var widths = CalculateWidths(fields, pageWidth);
|
||||
var widths = DynamicReportGridStateHelper.CalculateWidths(fields, pageWidth, localizer, stateWidths);
|
||||
var headerTable = CreateTable(pageWidth, 28);
|
||||
var headerRow = new XRTableRow { HeightF = 28 };
|
||||
var detailTable = CreateTable(pageWidth, detailHeight);
|
||||
|
|
@ -560,12 +569,17 @@ public class DynamicTreeReport : XtraReport
|
|||
};
|
||||
|
||||
var valueExpression = DynamicReportImageHelper.CreateFormatValueExpression($"[{column.BindingName}]", column.Field, column.EditorType);
|
||||
var textExpression = isTreeColumn
|
||||
? $"Concat([{TreePrefixColumn}], {valueExpression})"
|
||||
: valueExpression;
|
||||
cell.ExpressionBindings.Add(new ExpressionBinding(
|
||||
"BeforePrint",
|
||||
"Text",
|
||||
isTreeColumn
|
||||
? $"Concat([{TreePrefixColumn}], {valueExpression})"
|
||||
: valueExpression));
|
||||
textExpression));
|
||||
cell.ExpressionBindings.Add(new ExpressionBinding(
|
||||
"BeforePrint",
|
||||
"Font.Name",
|
||||
DynamicReportImageHelper.CreateFontFamilyExpression(textExpression)));
|
||||
|
||||
if (isTreeColumn)
|
||||
{
|
||||
|
|
@ -635,66 +649,6 @@ public class DynamicTreeReport : XtraReport
|
|||
return cell;
|
||||
}
|
||||
|
||||
private static float[] CalculateWidths(IReadOnlyList<ReportColumn> fields, float pageWidth)
|
||||
{
|
||||
var raw = fields.Select(x => x.IsImage ? GetImageColumnWidth(x) : GetPreferredColumnWidth(x.Field)).ToArray();
|
||||
var total = raw.Sum();
|
||||
if (total <= 0)
|
||||
{
|
||||
return fields.Select(_ => pageWidth / fields.Count).ToArray();
|
||||
}
|
||||
|
||||
return raw.Select(x => pageWidth * x / total).ToArray();
|
||||
}
|
||||
|
||||
private static float GetPreferredColumnWidth(ListFormField field)
|
||||
{
|
||||
if (field.Width > 0)
|
||||
{
|
||||
return Math.Max(field.Width.Value, 60);
|
||||
}
|
||||
|
||||
var caption = string.IsNullOrWhiteSpace(field.CaptionName) ? field.FieldName : field.CaptionName;
|
||||
var name = field.FieldName ?? string.Empty;
|
||||
var text = $"{caption} {name}".ToLowerInvariant();
|
||||
|
||||
if (field.SourceDbType == DbType.Boolean)
|
||||
{
|
||||
return 75;
|
||||
}
|
||||
|
||||
if (DynamicReportImageHelper.IsNumeric(field.SourceDbType))
|
||||
{
|
||||
return 70;
|
||||
}
|
||||
|
||||
if (field.SourceDbType is DbType.Date or DbType.DateTime or DbType.DateTime2 or DbType.DateTimeOffset)
|
||||
{
|
||||
return 115;
|
||||
}
|
||||
|
||||
if (text.Contains("description") ||
|
||||
text.Contains("explanation") ||
|
||||
text.Contains("note") ||
|
||||
text.Contains("comment") ||
|
||||
text.Contains("address") ||
|
||||
text.Contains("content") ||
|
||||
text.Contains("html") ||
|
||||
text.Contains("text"))
|
||||
{
|
||||
return 220;
|
||||
}
|
||||
|
||||
if (text.Contains("name") ||
|
||||
text.Contains("title") ||
|
||||
text.Contains("subject"))
|
||||
{
|
||||
return 160;
|
||||
}
|
||||
|
||||
return Math.Clamp((caption?.Length ?? 0) * 9F + 36F, 85F, 180F);
|
||||
}
|
||||
|
||||
private static bool HasAnySummary(IEnumerable<ReportColumn> fields)
|
||||
{
|
||||
return fields.Any(x => GetSummaryType(x.Field.TotalSummaryJson) != null);
|
||||
|
|
@ -778,7 +732,10 @@ public class DynamicTreeReport : XtraReport
|
|||
bool IsImage,
|
||||
bool IsHtml,
|
||||
string EditorType,
|
||||
int ImageCount = 1) : DynamicReportSqlBuilder.IReportLookupColumn;
|
||||
int ImageCount = 1) : DynamicReportSqlBuilder.IReportLookupColumn, DynamicReportGridStateHelper.IReportColumnWidth
|
||||
{
|
||||
public float ImageWidth => GetImageColumnWidth(this);
|
||||
}
|
||||
|
||||
private static SummaryFunc? GetSummaryType(string totalSummaryJson)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -209,6 +209,63 @@ const getReportSort = (grid: GridInstance) => {
|
|||
.join(MULTIVALUE_DELIMITER)
|
||||
}
|
||||
|
||||
const getReportColumnWidths = (grid: GridInstance) => {
|
||||
const getVisibleColumns = (grid as any).getVisibleColumns
|
||||
if (typeof getVisibleColumns !== 'function') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const stateColumnWidths: Record<string, number> = {}
|
||||
const addStateColumnWidths = (columns: any[] | undefined) => {
|
||||
columns?.forEach((column) => {
|
||||
const fieldName = column?.dataField || column?.name
|
||||
const width = Number(column?.width)
|
||||
if (fieldName && Number.isFinite(width) && width > 0) {
|
||||
stateColumnWidths[fieldName] = width
|
||||
}
|
||||
|
||||
if (Array.isArray(column?.columns)) {
|
||||
addStateColumnWidths(column.columns)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (supportsState(grid)) {
|
||||
const state = grid.state()
|
||||
if (Array.isArray(state?.columns)) {
|
||||
addStateColumnWidths(state.columns)
|
||||
}
|
||||
}
|
||||
|
||||
const visibleColumns = getVisibleColumns.call(grid) as any[]
|
||||
const widths = visibleColumns.reduce((result: Record<string, number>, column: any) => {
|
||||
const fieldName = column?.dataField || column?.name
|
||||
if (!fieldName || column?.type === 'buttons') {
|
||||
return result
|
||||
}
|
||||
|
||||
const stateWidth = stateColumnWidths[fieldName]
|
||||
const columnWidth = Number(column?.width)
|
||||
const visibleWidth = Number(column?.visibleWidth)
|
||||
const width =
|
||||
Number.isFinite(stateWidth) && stateWidth > 0
|
||||
? stateWidth
|
||||
: Number.isFinite(columnWidth) && columnWidth > 0
|
||||
? columnWidth
|
||||
: Number.isFinite(visibleWidth) && visibleWidth > 0
|
||||
? visibleWidth
|
||||
: undefined
|
||||
|
||||
if (width) {
|
||||
result[fieldName] = Math.round(width)
|
||||
}
|
||||
|
||||
return result
|
||||
}, {})
|
||||
|
||||
return Object.keys(widths).length ? widths : undefined
|
||||
}
|
||||
|
||||
const createDynamicListReportUrl = (
|
||||
grid: GridInstance | undefined,
|
||||
listFormCode: string,
|
||||
|
|
@ -222,6 +279,7 @@ const createDynamicListReportUrl = (
|
|||
const searchParams = new URLSearchParams()
|
||||
const filter = getReportFilter(grid)
|
||||
const sort = getReportSort(grid)
|
||||
const columnWidths = getReportColumnWidths(grid)
|
||||
|
||||
if (filter) {
|
||||
searchParams.set('filter', JSON.stringify(filter))
|
||||
|
|
@ -231,6 +289,10 @@ const createDynamicListReportUrl = (
|
|||
searchParams.set('sort', sort)
|
||||
}
|
||||
|
||||
if (columnWidths) {
|
||||
searchParams.set('columnWidths', JSON.stringify(columnWidths))
|
||||
}
|
||||
|
||||
const queryString = createReportQueryString(searchParams)
|
||||
return queryString ? `${reportUrl}?${queryString}` : reportUrl
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue