Compare commits

...

21 Commits

Author SHA1 Message Date
Vladimir Enchev
1a12a75bde Version updated 2026-02-03 09:56:04 +02:00
Vladimir Enchev
d1917eac0c Fixed QRCode eyes with transparent background 2026-02-03 08:44:24 +02:00
Vladimir Enchev
09830f0ea2 DataGrid possible memory leak fixed 2026-02-03 07:46:32 +02:00
Vladimir Enchev
d34e0684fb DataGrid GroupRowRenderEventArgs Expandable property added 2026-02-02 12:09:40 +02:00
Vladimir Enchev
482eca3278 tests fixed 2026-01-30 12:23:10 +02:00
Vladimir Enchev
5c8ac16c83 Version updated 2026-01-30 12:09:54 +02:00
Vladimir Enchev
29382cf0f4 DataGrid QueryOnlyVisibleColumns property added 2026-01-30 12:09:34 +02:00
Vladimir Enchev
53204cc8d6 RadzenPager GoToPage() will not update page index 2026-01-30 09:53:52 +02:00
Vladimir Enchev
6cf550c517 Version updated 2026-01-28 17:54:45 +02:00
vadimstrekha
7bf107af4c Fix sytax error in Radzen.Blazor.js (#2436) 2026-01-28 17:53:21 +02:00
Vladimir Enchev
adf2785a5a Version updated 2026-01-28 15:31:27 +02:00
Vladimir Enchev
cae44df00a Bar Charts have a zero for the min and max on the y-axis
Fix #2434
2026-01-28 15:31:05 +02:00
Vladimir Enchev
8ba1c69573 RadzenQRCode and RadzenBarcode ToSvg() methods added 2026-01-28 10:31:13 +02:00
Vladimir Enchev
56031c2fd4 QRCode and Barcode save to SVG examples added 2026-01-28 10:00:24 +02:00
Vladimir Enchev
ad44802d30 RadzenBarcodeEncoder and RadzenQREncoder made public
Fix #2433
2026-01-27 18:27:40 +02:00
Vladimir Enchev
64ca088e61 ListBox duplicates the first typed letter in WASM when inside Popup
Fix #2429
2026-01-27 14:08:46 +02:00
Theronguard
f4777565a2 Changed the method signature to virtual, to allow overrding Enum translation with component activators (#2432) 2026-01-27 13:14:28 +02:00
Vladimir Enchev
eb1423e757 Version update 2026-01-27 08:34:44 +02:00
Vladimir Enchev
596b251511 DataGrid column custom filter indicator is active even when no filter 2026-01-27 08:34:29 +02:00
Vladimir Enchev
e186315935 DataGrid grouping arrows do not show expanded state
Fix #2431
2026-01-27 08:27:26 +02:00
yordanov
cba9a5120d Reorder examples 2026-01-26 16:14:07 +02:00
23 changed files with 1715 additions and 969 deletions

View File

@@ -2,6 +2,7 @@ using Bunit;
using Bunit.JSInterop;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
namespace Radzen.Blazor.Tests
@@ -54,7 +55,7 @@ namespace Radzen.Blazor.Tests
}
[Fact]
public async void RadzenPager_Renders_Summary() {
public async Task RadzenPager_Renders_Summary() {
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.JSInterop.SetupModule("_content/Radzen.Blazor/Radzen.Blazor.js");
@@ -64,7 +65,7 @@ namespace Radzen.Blazor.Tests
parameters.Add<int>(p => p.Count, 100);
parameters.Add<bool>(p => p.ShowPagingSummary, true);
});
await component.Instance.GoToPage(2);
await component.InvokeAsync(() => component.Instance.GoToPage(2));
component.Render();
Assert.Contains(@$"rz-pager-summary", component.Markup);
@@ -111,7 +112,7 @@ namespace Radzen.Blazor.Tests
}
[Fact]
public async void RadzenPager_First_And_Prev_Buttons_Are_Disabled_When_On_The_First_Page()
public async Task RadzenPager_First_And_Prev_Buttons_Are_Disabled_When_On_The_First_Page()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
@@ -123,7 +124,7 @@ namespace Radzen.Blazor.Tests
parameters.Add<bool>(p => p.ShowPagingSummary, true);
});
await component.Instance.GoToPage(0);
await component.InvokeAsync(() => component.Instance.GoToPage(0));
component.Render();
var firstPageButton = component.Find("a.rz-pager-first");
@@ -134,7 +135,7 @@ namespace Radzen.Blazor.Tests
}
[Fact]
public async void RadzenPager_Last_And_Next_Buttons_Are_Disabled_When_On_The_Last_Page()
public async Task RadzenPager_Last_And_Next_Buttons_Are_Disabled_When_On_The_Last_Page()
{
using var ctx = new TestContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
@@ -146,7 +147,7 @@ namespace Radzen.Blazor.Tests
parameters.Add<bool>(p => p.ShowPagingSummary, true);
});
await component.Instance.GoToPage(9);
await component.InvokeAsync(() => component.Instance.GoToPage(9));
component.Render();
var lastPageButton = component.Find("a.rz-pager-last");

View File

@@ -508,7 +508,7 @@ namespace Radzen
/// <param name="item">The item.</param>
/// <param name="property">The property.</param>
/// <returns>System.Object.</returns>
public object? GetItemOrValueFromProperty(object? item, string property)
public virtual object? GetItemOrValueFromProperty(object? item, string property)
{
if (item != null)
{

View File

@@ -17,6 +17,12 @@ public class GroupRowRenderEventArgs
/// </summary>
public Group? Group { get; internal set; }
/// <summary>
/// Gets or sets a value indicating whether this group row is expandable.
/// </summary>
/// <value><c>true</c> if expandable; otherwise, <c>false</c>.</value>
public bool Expandable { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this group row is expanded.
/// </summary>

View File

@@ -41,6 +41,65 @@ namespace Radzen
return source.Provider.CreateQuery(selectExpression);
}
/// <summary>
/// Projects each element of a sequence into a collection of property values.
/// </summary>
public static IQueryable Select(this IQueryable source, IEnumerable<string> propertyNames)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(propertyNames);
var parameter = Expression.Parameter(source.ElementType, "x");
var bindings = new List<MemberBinding>();
var allProperties = source.ElementType.GetProperties();
foreach (var property in allProperties.Where(p => propertyNames.Contains(p.Name)))
{
bindings.Add(Expression.Bind(property, Expression.Property(parameter, property)));
}
var body = Expression.MemberInit(Expression.New(source.ElementType), bindings);
var delegateType = typeof(Func<,>).MakeGenericType(source.ElementType, source.ElementType);
var lambda = Expression.Lambda(delegateType, body, parameter);
var selectExpression = Expression.Call(typeof(Queryable),
nameof(Queryable.Select), [source.ElementType, source.ElementType], source.Expression,
Expression.Quote(lambda));
return source.Provider.CreateQuery(selectExpression);
}
/// <summary>
/// Projects each element of a sequence into a collection of property values.
/// </summary>
public static IQueryable<T> Select<T>(this IQueryable<T> source, IEnumerable<string> propertyNames)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(propertyNames);
var parameter = Expression.Parameter(typeof(T), "x");
var bindings = new List<MemberBinding>();
var allProperties = typeof(T).GetProperties();
foreach (var property in allProperties.Where(p => propertyNames.Contains(p.Name)))
{
bindings.Add(Expression.Bind(property, Expression.Property(parameter, property)));
}
var body = Expression.MemberInit(Expression.New(typeof(T)), bindings);
var lambda = Expression.Lambda<Func<T, T>>(body, parameter);
var selectExpression = Expression.Call(typeof(Queryable),
nameof(Queryable.Select), [typeof(T), typeof(T)], source.Expression,
Expression.Quote(lambda));
return source.Provider.CreateQuery<T>(selectExpression);
}
/// <summary>
/// Projects each element of a sequence to an IEnumerable and flattens the resulting sequences into one sequence.

View File

@@ -11,7 +11,7 @@
<IsPackable>true</IsPackable>
<PackageId>Radzen.Blazor</PackageId>
<Product>Radzen.Blazor</Product>
<Version>8.7.0</Version>
<Version>8.7.5</Version>
<Copyright>Radzen Ltd.</Copyright>
<Authors>Radzen Ltd.</Authors>
<Description>Radzen Blazor is the most sophisticated free UI component library for Blazor, featuring 100+ native components including DataGrid, Scheduler, Charts, and advanced theming with full support for Material Design and Fluent UI.</Description>

View File

@@ -1,9 +1,11 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Radzen.Blazor
{
@@ -93,22 +95,6 @@ namespace Radzen.Blazor
_ => false
};
internal readonly struct BarcodeRect
{
public readonly double X;
public readonly double Y;
public readonly double Width;
public readonly double Height;
public BarcodeRect(double x, double y, double width, double height)
{
X = x;
Y = y;
Width = width;
Height = height;
}
}
/// <summary>
/// Gets or sets the barcode value to encode.
/// </summary>
@@ -275,894 +261,20 @@ namespace Radzen.Blazor
if (vbWidth <= 0) vbWidth = 1;
return (geometry.bars, vbWidth, checksumText, null);
}
}
internal static class RadzenBarcodeEncoder
{
// Code 128 patterns (0..106). Each entry is 6 digits (bar/space/bar/space/bar/space) module widths.
// Stop code (106) is 7 digits in the spec (includes a final bar). We keep it as 7 digits and handle it.
static readonly string[] Code128Patterns = new[]
/// <summary>
/// Returns the SVG markup of the rendered QR code as a string.
/// </summary>
/// <returns>
/// A <see cref="Task{String}"/> representing the asynchronous operation. The task result contains the SVG markup of the QR code.
/// </returns>
public async Task<string> ToSvg()
{
"212222","222122","222221","121223","121322","131222","122213","122312","132212","221213",
"221312","231212","112232","122132","122231","113222","123122","123221","223211","221132",
"221231","213212","223112","312131","311222","321122","321221","312212","322112","322211",
"212123","212321","232121","111323","131123","131321","112313","132113","132311","211313",
"231113","231311","112133","112331","132131","113123","113321","133121","313121","211331",
"231131","213113","213311","213131","311123","311321","331121","312113","312311","332111",
"314111","221411","431111","111224","111422","121124","121421","141122","141221","112214",
"112412","122114","122411","142112","142211","241211","221114","413111","241112","134111",
"111242","121142","121241","114212","124112","124211","411212","421112","421211","212141",
"214121","412121","111143","111341","131141","114113","114311","411113","411311","113141",
"114131","311141","411131","211412","211214","211232","2331112"
};
public static IReadOnlyList<int> EncodeCode128B(string value) => EncodeCode128B(value, out _);
public static IReadOnlyList<int> EncodeCode128B(string value, out int checksum)
{
// Code 128 subset B supports ASCII 32..127 (inclusive). We treat 127 as DEL.
var codes = new List<int>(value.Length + 3);
const int startB = 104;
const int stop = 106;
codes.Add(startB);
for (int i = 0; i < value.Length; i++)
if (JSRuntime != null)
{
var ch = value[i];
int ascii = ch;
if (ascii < 32 || ascii > 127)
{
throw new ArgumentException($"Code128B supports ASCII 32..127. Invalid character: U+{ascii:X4}.");
}
// In Code128B, code value is ascii - 32
codes.Add(ascii - 32);
return await JSRuntime.InvokeAsync<string>("Radzen.outerHTML", Element);
}
int checksumValue = codes[0];
for (int i = 1; i < codes.Count; i++)
{
checksumValue += codes[i] * i;
}
checksumValue %= 103;
// expose checksum (0..102)
checksum = checksumValue;
codes.Add(checksumValue);
codes.Add(stop);
// Convert codes to module pattern widths, alternating bar/space.
// Most codes are 6 digits; stop is 7 digits.
var modules = new List<int>(codes.Count * 6);
foreach (var code in codes)
{
var p = Code128Patterns[code];
for (int i = 0; i < p.Length; i++)
{
modules.Add(p[i] - '0');
}
}
// Code 128 requires a 2-module termination bar after the stop pattern.
// Many pattern tables omit it because it can be represented by extending the
// final bar of the stop pattern by 2 modules.
if (modules.Count > 0)
{
modules[^1] += 2;
}
return modules;
}
static readonly Dictionary<char, string> Code39Map = new Dictionary<char, string>()
{
// Each pattern is 9 elements (bar/space alternating, starting with bar).
// 'n' = narrow (1), 'w' = wide (2). We expand to digits.
['0'] = "nnnwwnwnn",
['1'] = "wnnwnnnnw",
['2'] = "nnwwnnnnw",
['3'] = "wnwwnnnnn",
['4'] = "nnnwwnnnw",
['5'] = "wnnwwnnnn",
['6'] = "nnwwwnnnn",
['7'] = "nnnwnnwnw",
['8'] = "wnnwnnwnn",
['9'] = "nnwwnnwnn",
['A'] = "wnnnnwnnw",
['B'] = "nnwnnwnnw",
['C'] = "wnwnnwnnn",
['D'] = "nnnnwwnnw",
['E'] = "wnnnwwnnn",
['F'] = "nnwnwwnnn",
['G'] = "nnnnnwwnw",
['H'] = "wnnnnwwnn",
['I'] = "nnwnnwwnn",
['J'] = "nnnnwwwnn",
['K'] = "wnnnnnnww",
['L'] = "nnwnnnnww",
['M'] = "wnwnnnnwn",
['N'] = "nnnnwnnww",
['O'] = "wnnnwnnwn",
['P'] = "nnwnwnnwn",
['Q'] = "nnnnnnwww",
['R'] = "wnnnnnwwn",
['S'] = "nnwnnnwwn",
['T'] = "nnnnwnwwn",
['U'] = "wwnnnnnnw",
['V'] = "nwwnnnnnw",
['W'] = "wwwnnnnnn",
['X'] = "nwnnwnnnw",
['Y'] = "wwnnwnnnn",
['Z'] = "nwwnwnnnn",
['-'] = "nwnnnnwnw",
['.'] = "wwnnnnwnn",
[' '] = "nwwnnnwnn",
['$'] = "nwnwnwnnn",
['/'] = "nwnwnnnwn",
['+'] = "nwnnnwnwn",
['%'] = "nnnwnwnwn",
['*'] = "nwnnwnwnn", // start/stop
};
public static IReadOnlyList<int> EncodeCode39(string value)
{
// Code39 traditionally uses uppercase
var text = value.ToUpperInvariant();
foreach (var ch in text)
{
if (!Code39Map.ContainsKey(ch))
{
throw new ArgumentException($"Code39 does not support character '{ch}'.");
}
}
// Start + data + stop, inter-character gap (narrow space) between characters.
var full = "*" + text + "*";
var modules = new List<int>(full.Length * 10);
for (int idx = 0; idx < full.Length; idx++)
{
var pat = Code39Map[full[idx]];
for (int i = 0; i < pat.Length; i++)
{
modules.Add(pat[i] == 'w' ? 2 : 1);
}
// Inter-character gap (narrow space) except after last char.
if (idx != full.Length - 1)
{
modules.Add(1);
}
}
return modules;
}
public static IReadOnlyList<int> EncodeItf(string value)
{
var digits = new string(value.Where(char.IsDigit).ToArray());
if (digits.Length == 0)
{
throw new ArgumentException("ITF requires numeric input.");
}
if (digits.Length % 2 != 0)
{
// pad with leading zero (common behavior)
digits = "0" + digits;
}
const int narrow = 1;
const int wide = 3;
static string Pat(char d) => d switch
{
'0' => "nnwwn",
'1' => "wnnnw",
'2' => "nwnnw",
'3' => "wwnnn",
'4' => "nnwnw",
'5' => "wnwnn",
'6' => "nwwnn",
'7' => "nnnww",
'8' => "wnnwn",
'9' => "nwnwn",
_ => throw new ArgumentException("ITF requires numeric input.")
};
var widths = new List<int>(digits.Length * 10 + 16);
// Start: narrow bar, narrow space, narrow bar, narrow space (1010)
widths.Add(narrow);
widths.Add(narrow);
widths.Add(narrow);
widths.Add(narrow);
for (int i = 0; i < digits.Length; i += 2)
{
var a = Pat(digits[i]);
var b = Pat(digits[i + 1]);
for (int j = 0; j < 5; j++)
{
widths.Add(a[j] == 'w' ? wide : narrow); // bar
widths.Add(b[j] == 'w' ? wide : narrow); // space
}
}
// Stop: wide bar, narrow space, narrow bar (1101)
widths.Add(wide);
widths.Add(narrow);
widths.Add(narrow);
return widths;
}
public static IReadOnlyList<int> EncodeCodabar(string value)
{
// Wikipedia table mapping (bars: 1=wide, spaces: 0=wide) for the standard symbol set.
// Ensure start/stop are present; default to A ... B if missing.
var raw = (value ?? string.Empty).Trim().ToUpperInvariant();
if (raw.Length == 0)
{
throw new ArgumentException("Codabar requires a non-empty value.");
}
bool HasStartStop(string s)
{
if (s.Length < 2) return false;
bool isStart = s[0] == 'A' || s[0] == 'B' || s[0] == 'C' || s[0] == 'D';
bool isStop = s[s.Length - 1] == 'A' || s[s.Length - 1] == 'B' || s[s.Length - 1] == 'C' || s[s.Length - 1] == 'D';
return isStart && isStop;
}
var text = HasStartStop(raw) ? raw : $"A{raw}B";
static (string spaceBits, string barBits) Map(char ch) => ch switch
{
'0' => ("001", "0001"),
'1' => ("001", "0010"),
'2' => ("010", "0001"),
'3' => ("100", "1000"),
'4' => ("001", "0100"),
'5' => ("001", "1000"),
'6' => ("100", "0001"),
'7' => ("100", "0010"),
'8' => ("100", "0100"),
'9' => ("010", "1000"),
'-' => ("010", "0010"),
'$' => ("010", "0100"),
'.' => ("000", "0001"),
'/' => ("000", "0010"),
':' => ("000", "0100"),
'+' => ("000", "1000"),
'A' => ("011", "0100"),
'B' => ("110", "0001"),
'C' => ("011", "0001"),
'D' => ("011", "0010"),
_ => throw new ArgumentException($"Codabar does not support character '{ch}'.")
};
const int narrow = 1;
const int wide = 3;
var widths = new List<int>(text.Length * 8);
for (int idx = 0; idx < text.Length; idx++)
{
var ch = text[idx];
var (spaceBits, barBits) = Map(ch);
// Bars: 4 bits, 1=wide
int BarWidth(int pos) => barBits[pos] == '1' ? wide : narrow;
// Spaces: 3 bits, 0=wide (per wikipedia mapping table)
int SpaceWidth(int pos) => spaceBits[pos] == '0' ? wide : narrow;
widths.Add(BarWidth(0));
widths.Add(SpaceWidth(0));
widths.Add(BarWidth(1));
widths.Add(SpaceWidth(1));
widths.Add(BarWidth(2));
widths.Add(SpaceWidth(2));
widths.Add(BarWidth(3));
// Inter-character narrow space (except after last char).
if (idx != text.Length - 1)
{
widths.Add(narrow);
}
}
return widths;
}
static readonly string[] EanL = new[]
{
"0001101","0011001","0010011","0111101","0100011","0110001","0101111","0111011","0110111","0001011"
};
static readonly string[] EanG = new[]
{
"0100111","0110011","0011011","0100001","0011101","0111001","0000101","0010001","0001001","0010111"
};
static readonly string[] EanR = new[]
{
"1110010","1100110","1101100","1000010","1011100","1001110","1010000","1000100","1001000","1110100"
};
static readonly string[] Ean13Parity = new[]
{
"LLLLLL","LLGLGG","LLGGLG","LLGGGL","LGLLGG","LGGLLG","LGGGLL","LGLGLG","LGLGGL","LGGLGL"
};
static int ComputeEanCheckDigit(string digitsWithoutCheck)
{
// digitsWithoutCheck is 7/11/12 digits depending on symbology.
int sum = 0;
bool weight3 = true;
for (int i = digitsWithoutCheck.Length - 1; i >= 0; i--)
{
int d = digitsWithoutCheck[i] - '0';
sum += weight3 ? d * 3 : d;
weight3 = !weight3;
}
int mod = sum % 10;
return (10 - mod) % 10;
}
public static string EncodeEan13(string value, out string checksumText)
{
var digits = new string(value.Where(char.IsDigit).ToArray());
if (digits.Length != 12 && digits.Length != 13)
{
throw new ArgumentException("EAN-13 requires 12 or 13 digits.");
}
if (digits.Length == 12)
{
var check = ComputeEanCheckDigit(digits);
digits += check.ToString(CultureInfo.InvariantCulture);
}
else
{
var expected = ComputeEanCheckDigit(digits[..12]);
if (digits[12] - '0' != expected)
{
throw new ArgumentException("Invalid EAN-13 check digit.");
}
}
checksumText = digits[^1].ToString();
int first = digits[0] - '0';
var parity = Ean13Parity[first];
var sb = new StringBuilder(95);
sb.Append("101");
// digits 2..7 (index 1..6)
for (int i = 1; i <= 6; i++)
{
int d = digits[i] - '0';
sb.Append(parity[i - 1] == 'G' ? EanG[d] : EanL[d]);
}
sb.Append("01010");
for (int i = 7; i <= 12; i++)
{
int d = digits[i] - '0';
sb.Append(EanR[d]);
}
sb.Append("101");
return sb.ToString();
}
public static string EncodeUpcA(string value, out string checksumText)
{
var digits = new string(value.Where(char.IsDigit).ToArray());
if (digits.Length != 11 && digits.Length != 12)
{
throw new ArgumentException("UPC-A requires 11 or 12 digits.");
}
if (digits.Length == 11)
{
var check = ComputeEanCheckDigit(digits);
digits += check.ToString(CultureInfo.InvariantCulture);
}
else
{
var expected = ComputeEanCheckDigit(digits[..11]);
if (digits[11] - '0' != expected)
{
throw new ArgumentException("Invalid UPC-A check digit.");
}
}
checksumText = digits[^1].ToString();
var sb = new StringBuilder(95);
sb.Append("101");
for (int i = 0; i < 6; i++)
{
int d = digits[i] - '0';
sb.Append(EanL[d]);
}
sb.Append("01010");
for (int i = 6; i < 12; i++)
{
int d = digits[i] - '0';
sb.Append(EanR[d]);
}
sb.Append("101");
return sb.ToString();
}
public static string EncodeEan8(string value, out string checksumText)
{
var digits = new string(value.Where(char.IsDigit).ToArray());
if (digits.Length != 7 && digits.Length != 8)
{
throw new ArgumentException("EAN-8 requires 7 or 8 digits.");
}
if (digits.Length == 7)
{
var check = ComputeEanCheckDigit(digits);
digits += check.ToString(CultureInfo.InvariantCulture);
}
else
{
var expected = ComputeEanCheckDigit(digits[..7]);
if (digits[7] - '0' != expected)
{
throw new ArgumentException("Invalid EAN-8 check digit.");
}
}
checksumText = digits[^1].ToString();
var sb = new StringBuilder(67);
sb.Append("101");
for (int i = 0; i < 4; i++)
{
int d = digits[i] - '0';
sb.Append(EanL[d]);
}
sb.Append("01010");
for (int i = 4; i < 8; i++)
{
int d = digits[i] - '0';
sb.Append(EanR[d]);
}
sb.Append("101");
return sb.ToString();
}
public static string EncodeIsbnAsEan13(string value, out string checksumText)
{
var raw = new string(value.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
if (raw.Length == 10)
{
// ISBN-10 -> EAN-13: 978 + first 9 digits + EAN check
var core = raw[..9];
if (!core.All(char.IsDigit)) throw new ArgumentException("Invalid ISBN-10.");
return EncodeEan13("978" + core, out checksumText);
}
if (raw.Length == 13)
{
if (!raw.All(char.IsDigit)) throw new ArgumentException("Invalid ISBN-13.");
return EncodeEan13(raw, out checksumText);
}
throw new ArgumentException("ISBN requires 10 or 13 characters.");
}
public static string EncodeIssnAsEan13(string value, out string checksumText)
{
// ISSN EAN-13: 977 + first 7 digits + 00 + EAN check
var raw = new string(value.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
if (raw.Length != 8) throw new ArgumentException("ISSN requires 8 characters.");
var core = raw[..7];
if (!core.All(char.IsDigit)) throw new ArgumentException("Invalid ISSN.");
return EncodeEan13("977" + core + "00", out checksumText);
}
public static (IReadOnlyList<RadzenBarcode.BarcodeRect> bars, double vbWidth) EncodePharmacode(string value, double barHeight, int quietZone)
{
// Pharmacode one-track: numbers 3..131070
var digits = new string(value.Where(char.IsDigit).ToArray());
if (!int.TryParse(digits, NumberStyles.None, CultureInfo.InvariantCulture, out var n))
{
throw new ArgumentException("Pharmacode requires a numeric value.");
}
if (n < 3 || n > 131070)
{
throw new ArgumentException("Pharmacode value must be in range 3..131070.");
}
var bars = new List<(int width, bool isWide)>();
while (n > 0)
{
if (n % 2 == 0)
{
bars.Add((2, true));
n = (n - 2) / 2;
}
else
{
bars.Add((1, false));
n = (n - 1) / 2;
}
}
bars.Reverse();
double x = Math.Max(0, quietZone);
var rects = new List<RadzenBarcode.BarcodeRect>(bars.Count);
foreach (var b in bars)
{
rects.Add(new RadzenBarcode.BarcodeRect(x, 0, b.width, barHeight));
x += b.width + 1; // 1 module gap
}
var vbWidth = x + Math.Max(0, quietZone);
if (vbWidth <= 0) vbWidth = 1;
return (rects, vbWidth);
}
// POSTNET digit encoding from Wikipedia (weights 7,4,2,1,0). 1=full bar, 0=half bar.
static readonly Dictionary<char, string> PostnetDigitBits = new Dictionary<char, string>()
{
['0'] = "11000",
['1'] = "00011",
['2'] = "00101",
['3'] = "00110",
['4'] = "01001",
['5'] = "01010",
['6'] = "01100",
['7'] = "10001",
['8'] = "10010",
['9'] = "10100",
};
public static (IReadOnlyList<RadzenBarcode.BarcodeRect> bars, double vbWidth) EncodePostnet(string value, double barHeight, int quietZone, out string checksumText)
{
var digits = new string(value.Where(char.IsDigit).ToArray());
if ((digits.Length != 5 && digits.Length != 9 && digits.Length != 11))
{
throw new ArgumentException("POSTNET requires 5, 9, or 11 digits (ZIP / ZIP+4 / Delivery Point).");
}
int sum = digits.Sum(ch => ch - '0');
int check = (10 - (sum % 10)) % 10;
checksumText = check.ToString(CultureInfo.InvariantCulture);
var payload = digits + checksumText;
double fullH = barHeight;
double halfH = barHeight / 2.0;
double halfY = fullH - halfH;
double x = Math.Max(0, quietZone);
var rects = new List<RadzenBarcode.BarcodeRect>();
// Start frame bar (full)
rects.Add(new RadzenBarcode.BarcodeRect(x, 0, 1, fullH));
x += 2; // bar(1) + space(1)
foreach (var ch in payload)
{
var bits = PostnetDigitBits[ch];
for (int i = 0; i < 5; i++)
{
bool full = bits[i] == '1';
rects.Add(full
? new RadzenBarcode.BarcodeRect(x, 0, 1, fullH)
: new RadzenBarcode.BarcodeRect(x, halfY, 1, halfH));
x += 2;
}
}
// Stop frame bar (full)
rects.Add(new RadzenBarcode.BarcodeRect(x, 0, 1, fullH));
x += 1;
var vbWidth = x + Math.Max(0, quietZone);
if (vbWidth <= 0) vbWidth = 1;
return (rects, vbWidth);
}
// RM4SCC patterns and symbol matrix from Wikipedia:
// Top patterns (values 1..6) and Bottom patterns (values 1..6) are:
// 1=0011, 2=0101, 3=0110, 4=1001, 5=1010, 6=1100
static readonly string[] Rm4Patterns = new[] { "0011", "0101", "0110", "1001", "1010", "1100" };
// Matrix indexed by [topValue-1, bottomValue-1]
static readonly char[,] Rm4Matrix = new char[6, 6]
{
{ '0', '1', '2', '3', '4', '5' },
{ '6', '7', '8', '9', 'A', 'B' },
{ 'C', 'D', 'E', 'F', 'G', 'H' },
{ 'I', 'J', 'K', 'L', 'M', 'N' },
{ 'O', 'P', 'Q', 'R', 'S', 'T' },
{ 'U', 'V', 'W', 'X', 'Y', 'Z' }
};
static readonly Dictionary<char, (string top, string bottom)> Rm4CharToBits = BuildRm4CharToBits();
static Dictionary<char, (string top, string bottom)> BuildRm4CharToBits()
{
var dict = new Dictionary<char, (string top, string bottom)>();
for (int r = 0; r < 6; r++)
{
for (int c = 0; c < 6; c++)
{
dict[Rm4Matrix[r, c]] = (Rm4Patterns[r], Rm4Patterns[c]);
}
}
return dict;
}
public static (IReadOnlyList<RadzenBarcode.BarcodeRect> bars, double vbWidth) EncodeRm4scc(string value, double barHeight, int quietZone, out string checksumText)
{
var text = new string(value.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
if (string.IsNullOrEmpty(text))
{
throw new ArgumentException("RM4SCC requires alphanumeric input.");
}
// Only symbols present in the Wikipedia table (0-9, A-Z).
foreach (var ch in text)
{
if (!(ch is >= '0' and <= '9') && !(ch is >= 'A' and <= 'Z'))
{
throw new ArgumentException($"RM4SCC does not support character '{ch}'.");
}
}
// Compute checksum per Wikipedia: sum top values and bottom values separately, mod 6, 0 => 6.
int sumTop = 0;
int sumBottom = 0;
foreach (var ch in text)
{
if (!Rm4CharToBits.TryGetValue(ch, out var bits))
{
throw new ArgumentException($"RM4SCC does not support character '{ch}'.");
}
int t = Array.IndexOf(Rm4Patterns, bits.top) + 1;
int b = Array.IndexOf(Rm4Patterns, bits.bottom) + 1;
sumTop += t;
sumBottom += b;
}
int topVal = sumTop % 6;
topVal = topVal == 0 ? 6 : topVal;
int bottomVal = sumBottom % 6;
bottomVal = bottomVal == 0 ? 6 : bottomVal;
var checkChar = Rm4Matrix[topVal - 1, bottomVal - 1];
checksumText = checkChar.ToString();
// Encode start + data + checksum + stop.
// Start/stop are single bars; we use ascender for start and descender for stop.
double h = barHeight;
double third = h / 3.0;
double trackerY = third;
double trackerH = third;
RadzenBarcode.BarcodeRect BarRect(double x, bool top, bool bottom)
{
return (top, bottom) switch
{
(false, false) => new RadzenBarcode.BarcodeRect(x, trackerY, 1, trackerH), // tracker
(true, false) => new RadzenBarcode.BarcodeRect(x, 0, 1, trackerY + trackerH), // ascender (top + tracker)
(false, true) => new RadzenBarcode.BarcodeRect(x, trackerY, 1, h - trackerY), // descender (tracker + bottom)
(true, true) => new RadzenBarcode.BarcodeRect(x, 0, 1, h), // full
};
}
double xPos = Math.Max(0, quietZone);
var rects = new List<RadzenBarcode.BarcodeRect>();
// Start bar (ascender)
rects.Add(BarRect(xPos, top: true, bottom: false));
xPos += 2;
void AddSymbol(char ch)
{
var (topBits, bottomBits) = Rm4CharToBits[ch];
for (int i = 0; i < 4; i++)
{
bool top = topBits[i] == '1';
bool bottom = bottomBits[i] == '1';
rects.Add(BarRect(xPos, top, bottom));
xPos += 2;
}
}
foreach (var ch in text) AddSymbol(ch);
AddSymbol(checkChar);
// Stop bar (descender)
rects.Add(BarRect(xPos, top: false, bottom: true));
xPos += 1;
var vbWidth = xPos + Math.Max(0, quietZone);
if (vbWidth <= 0) vbWidth = 1;
return (rects, vbWidth);
}
public static string EncodeMsiPlessey(string value, out string checksumText)
{
var digits = new string(value.Where(char.IsDigit).ToArray());
if (digits.Length == 0) throw new ArgumentException("Plessey (MSI) requires numeric input.");
// Mod 10 (Luhn) check digit (common)
int check = ComputeLuhnCheckDigit(digits);
checksumText = check.ToString(CultureInfo.InvariantCulture);
digits += checksumText;
// MSI map from Wikipedia.
static string DigitMap(char d) => d switch
{
'0' => "100100100100",
'1' => "100100100110",
'2' => "100100110100",
'3' => "100100110110",
'4' => "100110100100",
'5' => "100110100110",
'6' => "100110110100",
'7' => "100110110110",
'8' => "110100100100",
'9' => "110100100110",
_ => throw new ArgumentException("MSI requires numeric input.")
};
var sb = new StringBuilder();
sb.Append("110"); // start
foreach (var ch in digits) sb.Append(DigitMap(ch));
sb.Append("1001"); // stop
return sb.ToString();
}
static int ComputeLuhnCheckDigit(string digits)
{
int sum = 0;
bool dbl = true;
for (int i = digits.Length - 1; i >= 0; i--)
{
int d = digits[i] - '0';
if (dbl)
{
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
dbl = !dbl;
}
return (10 - (sum % 10)) % 10;
}
public static IReadOnlyList<int> EncodeTelepen(string value, out string checksumText)
{
// Telepen algorithm per Wikipedia: even parity bytes, little-endian bit order, modulo-127 checksum.
var bytes = Encoding.ASCII.GetBytes(value ?? string.Empty);
int sum = 0;
for (int i = 0; i < bytes.Length; i++)
{
if (bytes[i] > 0x7F) throw new ArgumentException("Telepen supports ASCII only.");
sum = (sum + bytes[i]) % 127;
}
int check = (127 - sum) % 127;
checksumText = check.ToString(CultureInfo.InvariantCulture);
// Build payload: start '_' + data + checksum byte + stop 'z'
var payload = new List<byte>(bytes.Length + 3) { (byte)'_' };
payload.AddRange(bytes);
payload.Add((byte)check);
payload.Add((byte)'z');
// Build bit stream LSB-first with even parity bit as MSB.
var bitStream = new List<int>(payload.Count * 8);
foreach (var b0 in payload)
{
int b = b0 & 0x7F;
int ones = CountBits(b);
int parityBit = (ones % 2 == 0) ? 0 : 1; // make total even
int byteWithParity = b | (parityBit << 7);
for (int i = 0; i < 8; i++)
{
bitStream.Add((byteWithParity >> i) & 1);
}
}
// Encode bit stream into bar/space widths (narrow=1, wide=3).
// We produce alternating bar/space widths list, starting with bar.
const int narrow = 1;
const int wide = 3;
var widths = new List<int>();
int idx = 0;
while (idx < bitStream.Count)
{
if (bitStream[idx] == 1)
{
// "1" => narrow bar, narrow space
widths.Add(narrow);
widths.Add(narrow);
idx += 1;
continue;
}
// starts with 0
if (idx + 1 < bitStream.Count && bitStream[idx + 1] == 0)
{
// "00" => wide bar, narrow space
widths.Add(wide);
widths.Add(narrow);
idx += 2;
continue;
}
if (idx + 2 < bitStream.Count && bitStream[idx] == 0 && bitStream[idx + 1] == 1 && bitStream[idx + 2] == 0)
{
// "010" => wide bar, wide space
widths.Add(wide);
widths.Add(wide);
idx += 3;
continue;
}
// General block 0 1^k 0 with k>=2
if (idx + 3 >= bitStream.Count || bitStream[idx] != 0 || bitStream[idx + 1] != 1)
{
throw new ArgumentException("Invalid Telepen bit stream.");
}
int j = idx + 1;
while (j < bitStream.Count && bitStream[j] == 1) j++;
if (j >= bitStream.Count || bitStream[j] != 0)
{
throw new ArgumentException("Invalid Telepen bit stream.");
}
int k = j - (idx + 1); // number of 1s
if (k < 2) throw new ArgumentException("Invalid Telepen bit stream.");
// leading "01" => narrow bar, wide space
widths.Add(narrow);
widths.Add(wide);
// middle extra 1s (k-2) => narrow bar, narrow space
for (int m = 0; m < k - 2; m++)
{
widths.Add(narrow);
widths.Add(narrow);
}
// trailing "10" => narrow bar, wide space
widths.Add(narrow);
widths.Add(wide);
idx = j + 1;
}
return widths;
}
static int CountBits(int v)
{
int c = 0;
while (v != 0)
{
c += v & 1;
v >>= 1;
}
return c;
return string.Empty;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@ using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Text.Json;
using System.Threading.Tasks;
@@ -892,7 +893,7 @@ namespace Radzen.Blazor
}
}
void ToggleColumns()
async Task ToggleColumns()
{
if (selectedColumns == null)
{
@@ -906,8 +907,13 @@ namespace Radzen.Blazor
c.SetVisible(selected.Contains(c));
}
PickedColumnsChanged.InvokeAsync(new DataGridPickedColumnsChangedEventArgs<TItem>() { Columns = selected });
await PickedColumnsChanged.InvokeAsync(new DataGridPickedColumnsChangedEventArgs<TItem>() { Columns = selected });
SaveSettings();
if (QueryOnlyVisibleColumns)
{
await Reload();
}
}
/// <summary>
@@ -1627,6 +1633,13 @@ namespace Radzen.Blazor
[Parameter]
public int ColumnsPickerMaxSelectedLabels { get; set; } = 2;
/// <summary>
/// Gets or sets a value indicating whether only visible columns are included in the query.
/// </summary>
/// <value><c>true</c> if only visible columns are included; otherwise, <c>false</c>.</value>
[Parameter]
public bool QueryOnlyVisibleColumns { get; set; }
/// <summary>
/// Gets or sets the column picker all columns text.
/// </summary>
@@ -2016,7 +2029,8 @@ namespace Radzen.Blazor
}
}
return view;
return QueryOnlyVisibleColumns ? view
.Select(allColumns.Where(c => c.GetVisible() && !string.IsNullOrEmpty(c.Property)).Select(c => c.Property)) : view;
}
}
@@ -2496,7 +2510,7 @@ namespace Radzen.Blazor
internal Tuple<GroupRowRenderEventArgs, IReadOnlyDictionary<string, object>> GroupRowAttributes(RadzenDataGridGroupRow<TItem> item)
{
var args = new Radzen.GroupRowRenderEventArgs() { Group = item.Group, FirstRender = firstRender };
var args = new Radzen.GroupRowRenderEventArgs() { Group = item.Group, FirstRender = firstRender, Expandable = item.GroupResult.Count > 0 };
if (GroupRowRender != null)
{
@@ -3576,6 +3590,59 @@ namespace Radzen.Blazor
}
}
if (expandedItems != null)
{
expandedItems.Clear();
}
if (editedItems != null)
{
editedItems.Clear();
}
if (editContexts != null)
{
editContexts.Clear();
}
if (childData != null)
{
childData.Clear();
}
if (selectedItems != null)
{
selectedItems.Clear();
}
if (rowSpans != null)
{
rowSpans.Clear();
}
if (columns != null)
{
columns.Clear();
}
if (allPickableColumns != null)
{
allPickableColumns.Clear();
}
if (allColumns != null)
{
allColumns.Clear();
}
if (childColumns != null)
{
childColumns.Clear();
}
_value = null;
Data = null;
GC.SuppressFinalize(this);
}

View File

@@ -1310,7 +1310,7 @@ namespace Radzen.Blazor
internal bool HasCustomFilter()
{
return GetFilterOperator() == FilterOperator.Custom && GetCustomFilterExpression() != null;
return GetFilterOperator() == FilterOperator.Custom && !string.IsNullOrEmpty(GetCustomFilterExpression());
}
internal bool HasActiveFilter()

View File

@@ -26,9 +26,12 @@
}
<td class="rz-col-icon">
<span class="rz-column-title"></span>
<a id="@(Grid.GridId() + Group.GetHashCode())" aria-label=@Grid.ExpandGroupAriaLabel @onclick:preventDefault="true" @onclick="@(_ => Grid.ExpandGroupItem(this, rowArgs?.Item1.Expanded ?? false))">
<span class="@(Grid.ExpandedGroupItemStyle(this, Grid.allGroupsExpanded != null ? Grid.allGroupsExpanded : rowArgs?.Item1.Expanded ?? false))"></span>
</a>
@if (rowArgs?.Item1.Expandable == true)
{
<a id="@(Grid.GridId() + Group.GetHashCode())" aria-label=@Grid.ExpandGroupAriaLabel @onclick:preventDefault="true" @onclick="@(_ => Grid.ExpandGroupItem(this, rowArgs?.Item1.Expanded))">
<span class="@(Grid.ExpandedGroupItemStyle(this, Grid.allGroupsExpanded != null ? Grid.allGroupsExpanded : rowArgs?.Item1.Expanded))"></span>
</a>
}
</td>
}
<td colspan="@(TotalColumnCount + (Grid?.Groups.Count ?? 0) - 1 - Group.Level + ((Grid?.Template != null && Grid?.ShowExpandColumn == true) ? 1 : 0))">

View File

@@ -100,7 +100,10 @@ namespace Radzen.Blazor
if (AllowFiltering && key.Length == 1 && JSRuntime != null)
{
await JSRuntime.InvokeVoidAsync("Radzen.focusElement", SearchID);
await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", search, key);
if (JSRuntime is not IJSInProcessRuntime)
{
await JSRuntime.InvokeAsync<string>("Radzen.setInputValue", search, key);
}
}
await OnKeyPress(args, false);

View File

@@ -349,6 +349,7 @@ namespace Radzen.Blazor
skip = page * PageSize;
await InvokeAsync(Reload);
await PageChanged.InvokeAsync(new PagerEventArgs() { Skip = skip, Top = PageSize, PageIndex = CurrentPage });
StateHasChanged();
}
}

View File

@@ -6,6 +6,8 @@
int n = modules.GetLength(0);
int vb = n + 8; // quiet zone
var backgroundFill = GetSvgFillParts(Background);
// --- Center Image math (viewBox units == modules incl. quiet zone) ---
bool hasImage = !string.IsNullOrWhiteSpace(Image);
// Clamp percent for scan reliability (5%..60% of QR inner box)
@@ -32,14 +34,14 @@
height="@Size"
@ref="@Element" style="@Style" @attributes="Attributes" class="@GetCssClass()" id="@GetId()">
<!-- Background -->
<rect x="0" y="0" width="@vb" height="@vb" fill="@Background" />
<rect x="0" y="0" width="@vb" height="@vb" fill="@backgroundFill.Color" fill-opacity="@Format(backgroundFill.Opacity)" />
@if (modules is not null)
{
<!-- finder patterns / eyes -->
@DrawEye(4, 4, EyeShapeTopLeft ?? EyeShape, EyeColorTopLeft ?? EyeColor ?? Foreground)
@DrawEye(vb - 11, 4, EyeShapeTopRight ?? EyeShape, EyeColorTopRight ?? EyeColor ?? Foreground)
@DrawEye(4, vb - 11, EyeShapeBottomLeft ?? EyeShape, EyeColorBottomLeft ?? EyeColor ?? Foreground)
@DrawEye(4, 4, EyeShapeTopLeft ?? EyeShape, EyeColorTopLeft ?? EyeColor ?? Foreground, $"{GetId()}-eye-0")
@DrawEye(vb - 11, 4, EyeShapeTopRight ?? EyeShape, EyeColorTopRight ?? EyeColor ?? Foreground, $"{GetId()}-eye-1")
@DrawEye(4, vb - 11, EyeShapeBottomLeft ?? EyeShape, EyeColorBottomLeft ?? EyeColor ?? Foreground, $"{GetId()}-eye-2")
<!-- data modules (skip eye regions) -->
@for (var r = 0; r < n; r++)
@@ -95,7 +97,7 @@
{
// Draw a 7x7 eye whose top-left screen coordinate (including quiet zone) is (x,y).
// NOTE: x,y are *SVG units* (already offset by quiet zone).
private RenderFragment DrawEye(double x, double y, QRCodeEyeShape shape, string color)
private RenderFragment DrawEye(double x, double y, QRCodeEyeShape shape, string color, string maskId)
{
return __builder =>
{
@@ -104,10 +106,14 @@
{
case QRCodeEyeShape.Rounded:
{
<!-- Outer 7x7 rounded ring -->
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" rx="@Format(1.25)" ry="@Format(1.25)" fill="@color" />
<!-- Inner hole (background) -->
<rect x="@Format(x + 1)" y="@Format(y + 1)" width="@Format(5)" height="@Format(5)" rx="@Format(0.25)" ry="@Format(0.25)" fill="@Background" />
<defs>
<mask id="@maskId" maskUnits="userSpaceOnUse">
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" rx="@Format(1.25)" ry="@Format(1.25)" fill="white" />
<rect x="@Format(x + 1)" y="@Format(y + 1)" width="@Format(5)" height="@Format(5)" rx="@Format(0.25)" ry="@Format(0.25)" fill="black" />
</mask>
</defs>
<!-- Outer 7x7 rounded ring with transparent hole -->
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" rx="@Format(1.25)" ry="@Format(1.25)" fill="@color" mask="url(#@maskId)" />
<!-- Pupil 3x3 rounded -->
<rect x="@Format(x + 2)" y="@Format(y + 2)" width="@Format(3)" height="@Format(3)" rx="@Format(0.75)" ry="@Format(0.75)" fill="@color" />
}
@@ -115,9 +121,14 @@
case QRCodeEyeShape.Framed:
{
<!-- Bold square frame (thickness ~1.2) -->
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="@color" />
<rect x="@Format(x + 1.2)" y="@Format(y + 1.2)" width="@Format(7 - 2 * 1.2)" height="@Format(7 - 2 * 1.2)" fill="@Background" />
<defs>
<mask id="@maskId" maskUnits="userSpaceOnUse">
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="white" />
<rect x="@Format(x + 1.2)" y="@Format(y + 1.2)" width="@Format(7 - 2 * 1.2)" height="@Format(7 - 2 * 1.2)" fill="black" />
</mask>
</defs>
<!-- Bold square frame (thickness ~1.2) with transparent hole -->
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="@color" mask="url(#@maskId)" />
<!-- Pupil -->
<rect x="@Format(x + 2)" y="@Format(y + 2)" width="@Format(3)" height="@Format(3)" fill="@color" />
}
@@ -126,9 +137,14 @@
case QRCodeEyeShape.Square:
default:
{
<!-- Classic square: 7x7 outer, 5x5 inner (background), 3x3 pupil -->
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="@color" />
<rect x="@Format(x + 1)" y="@Format(y + 1)" width="@Format(5)" height="@Format(5)" fill="@Background" />
<defs>
<mask id="@maskId" maskUnits="userSpaceOnUse">
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="white" />
<rect x="@Format(x + 1)" y="@Format(y + 1)" width="@Format(5)" height="@Format(5)" fill="black" />
</mask>
</defs>
<!-- Classic square: 7x7 outer with transparent 5x5 hole -->
<rect x="@Format(x)" y="@Format(y)" width="@Format(7)" height="@Format(7)" fill="@color" mask="url(#@maskId)" />
<rect x="@Format(x + 2)" y="@Format(y + 2)" width="@Format(3)" height="@Format(3)" fill="@color" />
}
break;

View File

@@ -1,8 +1,9 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using Radzen.Blazor.Rendering;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading.Tasks;
namespace Radzen.Blazor
{
@@ -88,19 +89,19 @@ namespace Radzen.Blazor
/// <value>The size in CSS units. Default is "100%".</value>
[Parameter] public string Size { get; set; } = "100%";
/// <summary>
/// Gets or sets the color of the QR code modules (the dark squares/dots).
/// Supports any valid CSS color. Use high contrast with background for best scanability.
/// </summary>
/// <value>The foreground color. Default is "#000" (black).</value>
[Parameter] public string Foreground { get; set; } = "#000";
/// <summary>
/// Gets or sets the color of the QR code modules (the dark squares/dots).
/// Supports any valid CSS color. Use high contrast with background for best scanability.
/// </summary>
/// <value>The foreground color. Default is "#000" (black).</value>
[Parameter] public string Foreground { get; set; } = "#000";
/// <summary>
/// Gets or sets the background color of the QR code.
/// Should contrast well with the foreground color for reliable scanning.
/// </summary>
/// <value>The background color. Default is "#FFF" (white).</value>
[Parameter] public string Background { get; set; } = "#FFF";
/// <summary>
/// Gets or sets the background color of the QR code.
/// Should contrast well with the foreground color for reliable scanning.
/// </summary>
/// <value>The background color. Default is "#FFF" (white).</value>
[Parameter] public string Background { get; set; } = "#FFF";
/// <summary>
/// Gets or sets the visual shape of the QR code modules (data squares).
@@ -156,6 +157,28 @@ namespace Radzen.Blazor
/// </summary>
protected override string GetComponentCssClass() => "rz-qrcode";
private static string Format(double v) => v.ToString(CultureInfo.InvariantCulture);
private static (string Color, double Opacity) GetSvgFillParts(string? color)
{
if (string.IsNullOrWhiteSpace(color))
{
return ("none", 1);
}
if (string.Equals(color, "transparent", StringComparison.OrdinalIgnoreCase))
{
return ("rgb(0, 0, 0)", 0);
}
var rgb = RGB.Parse(color);
if (rgb == null)
{
return (color, 1);
}
var opacity = Math.Clamp(rgb.Alpha, 0, 1);
var fill = $"rgb({Format(rgb.Red)}, {Format(rgb.Green)}, {Format(rgb.Blue)})";
return (fill, opacity);
}
private static bool IsFinderCell(int r, int c, int n)
{
bool inTL = r < 7 && c < 7;
@@ -163,6 +186,21 @@ namespace Radzen.Blazor
bool inBL = r >= n - 7 && c < 7;
return inTL || inTR || inBL;
}
/// <summary>
/// Returns the SVG markup of the rendered QR code as a string.
/// </summary>
/// <returns>
/// A <see cref="Task{String}"/> representing the asynchronous operation. The task result contains the SVG markup of the QR code.
/// </returns>
public async Task<string> ToSvg()
{
if (JSRuntime != null)
{
return await JSRuntime.InvokeAsync<string>("Radzen.outerHTML", Element);
}
return string.Empty;
}
}
}

View File

@@ -1,7 +1,8 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Globalization;
using Radzen.Blazor.Rendering;
using System.Text;
namespace Radzen.Blazor;
@@ -29,7 +30,10 @@ public enum RadzenQREcc
High
}
internal static class RadzenQREncoder
/// <summary>
/// Provides QR encoding utilities for UTF-8 strings and raw bytes.
/// </summary>
public static class RadzenQREncoder
{
/// <summary>Encode a UTF-8 string into a QR module matrix.</summary>
public static bool[,] EncodeUtf8(string value, RadzenQREcc ecc, int minVersion = 1, int maxVersion = 40)
@@ -93,6 +97,8 @@ internal static class RadzenQREncoder
/// <summary>Encode raw bytes into a QR module matrix.</summary>
public static bool[,] EncodeBytes(byte[] data, RadzenQREcc ecc = RadzenQREcc.Medium, int minVersion = 1, int maxVersion = 40)
{
ArgumentNullException.ThrowIfNull(data);
if (minVersion < 1 || maxVersion > 40 || minVersion > maxVersion)
throw new ArgumentOutOfRangeException(nameof(minVersion), "Version range must be within 1..40");
@@ -152,24 +158,93 @@ internal static class RadzenQREncoder
}
/// <summary>Render a module matrix into an SVG string with a 4-module quiet zone.</summary>
public static string ToSvg(bool[,] modules, int moduleSize = 8, string foreground = "#000000", string background = "#FFFFFF")
public static string ToSvg(
bool[,] modules,
int moduleSize = 8,
string foreground = "#000000",
string background = "#FFFFFF",
QRCodeModuleShape moduleShape = QRCodeModuleShape.Square,
QRCodeEyeShape eyeShape = QRCodeEyeShape.Square,
QRCodeEyeShape? eyeShapeTopLeft = null,
QRCodeEyeShape? eyeShapeTopRight = null,
QRCodeEyeShape? eyeShapeBottomLeft = null,
string? eyeColor = null,
string? eyeColorTopLeft = null,
string? eyeColorTopRight = null,
string? eyeColorBottomLeft = null,
string? image = null,
string imageBackground = "#FFF")
{
ArgumentNullException.ThrowIfNull(modules);
ArgumentNullException.ThrowIfNull(foreground);
ArgumentNullException.ThrowIfNull(background);
ArgumentNullException.ThrowIfNull(imageBackground);
int n = modules.GetLength(0);
int vb = n + 8; // 4 modules of quiet zone on each side
int px = vb * moduleSize;
var sb = new StringBuilder(n * n + 1024);
sb.Append(CultureInfo.InvariantCulture, $"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{px}\" height=\"{px}\" viewBox=\"0 0 {vb} {vb}\" shape-rendering=\"crispEdges\">");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"0\" y=\"0\" width=\"{vb}\" height=\"{vb}\" fill=\"{background}\"/>");
var backgroundFill = GetSvgFillParts(background);
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"0\" y=\"0\" width=\"{vb}\" height=\"{vb}\" fill=\"{backgroundFill.Color}\" fill-opacity=\"{Format(backgroundFill.Opacity)}\"/>");
var baseEyeColor = eyeColor ?? foreground;
AppendEye(sb, 4, 4, eyeShapeTopLeft ?? eyeShape, eyeColorTopLeft ?? baseEyeColor, "eye-mask-0");
AppendEye(sb, vb - 11, 4, eyeShapeTopRight ?? eyeShape, eyeColorTopRight ?? baseEyeColor, "eye-mask-1");
AppendEye(sb, 4, vb - 11, eyeShapeBottomLeft ?? eyeShape, eyeColorBottomLeft ?? baseEyeColor, "eye-mask-2");
for (int r = 0; r < n; r++)
{
for (int c = 0; c < n; c++)
{
if (modules[r, c])
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{c + 4}\" y=\"{r + 4}\" width=\"1\" height=\"1\" fill=\"{foreground}\"/>");
if (!modules[r, c]) continue;
if (IsFinderCell(r, c, n)) continue;
var x = c + 4;
var y = r + 4;
if (moduleShape == QRCodeModuleShape.Circle)
{
sb.Append(CultureInfo.InvariantCulture, $"<circle cx=\"{Format(x + 0.5)}\" cy=\"{Format(y + 0.5)}\" r=\"0.5\" fill=\"{foreground}\"/>");
}
else if (moduleShape == QRCodeModuleShape.Rounded)
{
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"1\" height=\"1\" rx=\"0.25\" ry=\"0.25\" fill=\"{foreground}\"/>");
}
else
{
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"1\" height=\"1\" fill=\"{foreground}\"/>");
}
}
}
if (!string.IsNullOrWhiteSpace(image))
{
const double imageSizePercent = 20;
const double imagePaddingModules = 1.0;
const double imageCornerRadius = 0.75;
const double imageBackgroundOpacity = 1.0;
double pct = Math.Clamp(imageSizePercent, 5, 60);
double boxModules = Math.Max(5, Math.Round(n * (pct / 100.0)));
double pad = Math.Max(0, imagePaddingModules);
double cutoutW = boxModules + 2 * pad;
double cutoutH = boxModules + 2 * pad;
double centerX = vb / 2.0;
double centerY = vb / 2.0;
double cutoutX = centerX - cutoutW / 2.0;
double cutoutY = centerY - cutoutH / 2.0;
double imgX = centerX - boxModules / 2.0;
double imgY = centerY - boxModules / 2.0;
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(cutoutX)}\" y=\"{Format(cutoutY)}\" width=\"{Format(cutoutW)}\" height=\"{Format(cutoutH)}\" rx=\"{Format(imageCornerRadius)}\" ry=\"{Format(imageCornerRadius)}\" fill=\"{imageBackground}\" fill-opacity=\"{Format(Math.Clamp(imageBackgroundOpacity, 0, 1))}\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<image x=\"{Format(imgX)}\" y=\"{Format(imgY)}\" width=\"{Format(boxModules)}\" height=\"{Format(boxModules)}\" preserveAspectRatio=\"xMidYMid meet\" href=\"{image}\"/>");
}
sb.Append("</svg>");
return sb.ToString();
}
@@ -1065,4 +1140,69 @@ internal static class RadzenQREncoder
for (int i = len - 1; i >= 0; i--) this.Add((val >> i) & 1);
}
}
private static bool IsFinderCell(int r, int c, int n)
{
bool inTL = r < 7 && c < 7;
bool inTR = r < 7 && c >= n - 7;
bool inBL = r >= n - 7 && c < 7;
return inTL || inTR || inBL;
}
private static void AppendEye(StringBuilder sb, double x, double y, QRCodeEyeShape shape, string color, string maskId)
{
switch (shape)
{
case QRCodeEyeShape.Rounded:
sb.Append(CultureInfo.InvariantCulture, $"<defs><mask id=\"{maskId}\" maskUnits=\"userSpaceOnUse\">");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" rx=\"1.25\" ry=\"1.25\" fill=\"white\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1)}\" y=\"{Format(y + 1)}\" width=\"5\" height=\"5\" rx=\"0.25\" ry=\"0.25\" fill=\"black\"/>");
sb.Append("</mask></defs>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" rx=\"1.25\" ry=\"1.25\" fill=\"{color}\" mask=\"url(#{maskId})\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 2)}\" y=\"{Format(y + 2)}\" width=\"3\" height=\"3\" rx=\"0.75\" ry=\"0.75\" fill=\"{color}\"/>");
break;
case QRCodeEyeShape.Framed:
sb.Append(CultureInfo.InvariantCulture, $"<defs><mask id=\"{maskId}\" maskUnits=\"userSpaceOnUse\">");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"white\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1.2)}\" y=\"{Format(y + 1.2)}\" width=\"{Format(7 - 2 * 1.2)}\" height=\"{Format(7 - 2 * 1.2)}\" fill=\"black\"/>");
sb.Append("</mask></defs>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"{color}\" mask=\"url(#{maskId})\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 2)}\" y=\"{Format(y + 2)}\" width=\"3\" height=\"3\" fill=\"{color}\"/>");
break;
case QRCodeEyeShape.Square:
default:
sb.Append(CultureInfo.InvariantCulture, $"<defs><mask id=\"{maskId}\" maskUnits=\"userSpaceOnUse\">");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"white\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1)}\" y=\"{Format(y + 1)}\" width=\"5\" height=\"5\" fill=\"black\"/>");
sb.Append("</mask></defs>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"{color}\" mask=\"url(#{maskId})\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 2)}\" y=\"{Format(y + 2)}\" width=\"3\" height=\"3\" fill=\"{color}\"/>");
break;
}
}
private static (string Color, double Opacity) GetSvgFillParts(string? color)
{
if (string.IsNullOrWhiteSpace(color))
{
return ("none", 1);
}
if (string.Equals(color, "transparent", StringComparison.OrdinalIgnoreCase))
{
return ("rgb(0, 0, 0)", 0);
}
var rgb = RGB.Parse(color);
if (rgb == null)
{
return (color, 1);
}
var opacity = Math.Clamp(rgb.Alpha, 0, 1);
var fill = $"rgb({Format(rgb.Red)}, {Format(rgb.Green)}, {Format(rgb.Blue)})";
return (fill, opacity);
}
private static string Format(double v) => v.ToString(CultureInfo.InvariantCulture);
}

View File

@@ -6,9 +6,9 @@
<Line class="rz-line" X1="@x1" Y1="@y1" X2="@x1" Y2="@y2" Stroke="@YAxis.Stroke" StrokeWidth="@YAxis.StrokeWidth" LineType="@YAxis.LineType" />
@for (var idx = start; idx <= end; idx += step)
{
var value = Chart?.ValueScale?.Value(idx) ?? 0;
var value = Chart?.ValueScale?.Value(idx);
var y = Chart?.ValueScale?.Scale(idx) ?? 0;
var text = YAxis != null && Chart?.ValueScale != null ? YAxis.Format(Chart.ValueScale, value) : "";
var text = value != null && YAxis != null && Chart?.ValueScale != null ? YAxis.Format(Chart.ValueScale, value) : "";
if (YAxis?.Ticks?.Template != null)
{
@@ -18,7 +18,7 @@
@YAxis.Ticks.Template(context)
</ValueAxisTick>
}
else
else if (!String.IsNullOrEmpty(text))
{
<ValueAxisTick X="@x1" Y="@y" Text="@text" Stroke="@(YAxis?.Ticks?.Stroke ?? YAxis?.Stroke)" StrokeWidth="@(YAxis?.Ticks?.StrokeWidth ?? 0)" LineType="@(YAxis?.Ticks?.LineType ?? LineType.Solid)"/>
}

View File

@@ -38,6 +38,17 @@ window.Radzen = {
}
};
},
downloadFile: function (fileName, data, mimeType) {
const blob = new Blob([data], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = fileName;
a.click();
URL.revokeObjectURL(url);
},
mask: function (id, mask, pattern, characterPattern) {
var el = document.getElementById(id);
if (el) {
@@ -2315,6 +2326,12 @@ window.Radzen = {
var rect = el.getBoundingClientRect();
return { left: rect.left, top: rect.top, width: rect.width, height: rect.height };
},
outerHTML: function (arg) {
var el = arg instanceof Element || arg instanceof HTMLDocument
? arg
: document.getElementById(arg);
return el ? el.outerHTML : '';
},
endDrag: function (ref) {
document.removeEventListener('mousemove', ref.mouseMoveHandler);
document.removeEventListener('mouseup', ref.mouseUpHandler);

View File

@@ -1,7 +1,9 @@
@inject IJSRuntime JS
<RadzenRow Gap="2rem">
<RadzenColumn Size="12" SizeMD="6">
<RadzenStack AlignItems="AlignItems.Center" JustifyContent="JustifyContent.Center" Style="height: 100%;">
<RadzenBarcode Value="@value"
<RadzenBarcode @ref="barCode" Value="@value"
Type="@type"
Foreground="@foreground"
Background="@background"
@@ -79,6 +81,11 @@
<RadzenTextArea @bind-Value="@valueStyle" class="rz-w-100" Rows="3" />
</RadzenFormField>
<RadzenButton Text="Save SVG"
Icon="download"
ButtonStyle="ButtonStyle.Primary"
Click="@(_ => SaveSvg())" />
<RadzenText TextStyle="TextStyle.Caption">
Tip: some types require numeric-only input (EAN/UPC/ITF/MSI/POSTNET/Pharmacode). Changing the type auto-fills a valid sample value.
</RadzenText>
@@ -87,6 +94,7 @@
</RadzenRow>
@code {
RadzenBarcode barCode;
RadzenBarcodeType type = RadzenBarcodeType.Code128;
IEnumerable<RadzenBarcodeType> types = Enum.GetValues<RadzenBarcodeType>();
@@ -123,6 +131,19 @@
_ => value
};
}
async Task SaveSvg(bool custom = false)
{
var svg = custom ? RadzenBarcodeEncoder.ToSvg(
type,
value,
barHeight: barHeight,
quietZoneModules: quietZone,
foreground: foreground,
background: background) : await barCode.ToSvg();
await JS.InvokeVoidAsync("Radzen.downloadFile", "barcode.svg", svg, "image/svg+xml;charset=utf-8");
}
}

View File

@@ -11,8 +11,7 @@
ColumnWidth="300px"
AllowColumnPicking="true"
PickedColumnsChanged="@PickedColumnsChanged"
ColumnsPickerAllowFiltering="true"
>
ColumnsPickerAllowFiltering="true" QueryOnlyVisibleColumns="true">
<Columns>
<RadzenDataGridColumn
Property=@nameof(Employee.EmployeeID)

View File

@@ -7,6 +7,7 @@
Enable default column picker by setting the <strong>AllowColumnPicking</strong> grid property to true.
You can disable picking for specific columns by setting their <strong>Pickable</strong> property to false.
The example below also sets <strong>ColumnsPickerAllowFiltering</strong> on the grid to make picking columns easier.
Use <strong>QueryOnlyVisibleColumns</strong> to tell the grid to only include currently visible columns in the IQueryable query.
</RadzenText>
<RadzenText TextStyle="TextStyle.Subtitle1" TagName="TagName.P" class="rz-pb-4">
Documentation:

View File

@@ -85,6 +85,8 @@
{
args.Expanded = allGroupsExpanded != null ? allGroupsExpanded : false;
}
args.Expandable = args.Group.Data.Key != "Vice President, Sales";
}
void OnGroupRowExpand(Group group)

View File

@@ -1,9 +1,10 @@
@using Microsoft.AspNetCore.Components.Forms
@inject IJSRuntime JS
<RadzenRow Gap="2rem" AlignItems="AlignItems.Start">
<RadzenColumn Size="12" SizeMD="6">
<RadzenStack AlignItems="AlignItems.Center" JustifyContent="JustifyContent.Center" Style="height: 100%;">
<RadzenQRCode Value="Radzen Blazor"
<RadzenQRCode @ref="qrCode" Value="Radzen Blazor"
Foreground="@foreground"
Background="@background"
EyeColor="@eyeColor"
@@ -109,11 +110,18 @@
</RadzenFormField>
</RadzenStack>
</RadzenFieldset>
<RadzenButton Text="Save SVG"
Icon="download"
ButtonStyle="ButtonStyle.Primary"
Click="@(_ => SaveSvg())" />
</RadzenStack>
</RadzenColumn>
</RadzenRow>
@code {
RadzenQRCode qrCode;
string foreground = "#0f62fe";
string background = "#eef4ff";
@@ -158,4 +166,28 @@
bottomLeftEyeColor = null;
imageBackground = "#FFFFFF";
}
}
async Task SaveSvg(bool custom = false)
{
const string value = "Radzen Blazor";
var modules = RadzenQREncoder.EncodeUtf8(value, RadzenQREcc.Quartile);
var svg = custom ? RadzenQREncoder.ToSvg(
modules,
moduleSize: 8,
foreground: foreground,
background: background,
moduleShape: moduleShape,
eyeShape: eyeShape,
eyeShapeTopLeft: topLeftEyeShape,
eyeShapeTopRight: topRightEyeShape,
eyeShapeBottomLeft: bottomLeftEyeShape,
eyeColor: eyeColor,
eyeColorTopLeft: topLeftEyeColor,
eyeColorTopRight: topRightEyeColor,
eyeColorBottomLeft: bottomLeftEyeColor,
image: imageUrl,
imageBackground: imageBackground) : await qrCode.ToSvg();
await JS.InvokeVoidAsync("Radzen.downloadFile", "qrcode.svg", svg, "image/svg+xml;charset=utf-8");
}
}

View File

@@ -2025,15 +2025,6 @@ namespace RadzenBlazorDemos
Description = "This example demonstrates different color schemes, custom colors and styling of Radzen Blazor Chart component.",
Tags = new [] { "chart", "graph", "styling" }
},
new Example
{
Name = "Spider Chart",
Path = "spider-chart",
Title = "Blazor Spider Chart Component | Free UI Components by Radzen",
Description = "Radzen Blazor Spider Chart for displaying multivariate data in a radial format.",
Tags = new [] { "spider", "radar", "chart", "multivariate", "radial", "web" },
New = true
},
}
},
new Example
@@ -2045,6 +2036,16 @@ namespace RadzenBlazorDemos
Tags = new [] { "chart", "sparkline" }
},
new Example
{
Name = "Spider Chart",
Path = "spider-chart",
Title = "Blazor Spider Chart Component | Free UI Components by Radzen",
Description = "Radzen Blazor Spider Chart for displaying multivariate data in a radial format.",
Tags = new [] { "spider", "radar", "chart", "multivariate", "radial", "web" },
Icon = "\ueb39",
New = true
},
new Example
{
Name = "Arc Gauge",
Path = "arc-gauge",