Compare commits

...

14 Commits

Author SHA1 Message Date
Atanas Korchev
fc6fe54635 Fix 302 redirect during SSR that hurt SEO for clean URLs
During server-side prerendering the ThemeChanged handler would call
NavigateTo which resulted in a 302 redirect (e.g. /datagrid → /datagrid?theme=material3).
This conflicted with the canonical tag pointing back to the clean URL,
confusing Google and causing ranking drops. Unsubscribe from ThemeChanged
during SSR so the redirect no longer occurs. Interactive theme switching
is unaffected since WebAssembly uses a separate scoped instance.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 21:33:34 +02:00
Vladimir Enchev
505ffa7fe4 Splitter ChangeStateOnResize added 2026-02-06 12:36:29 +02:00
Vladimir Enchev
4f82ac9599 Accessing sub properties of nullable types improved 2026-02-06 10:36:53 +02:00
Vladimir Enchev
5bbf7c1fde build fixed 2026-02-04 14:46:25 +02:00
Vladimir Enchev
e416cede62 DropDownBase possible null reference exception with browser autofil 2026-02-04 12:08:41 +02:00
Vladimir Enchev
270a7e0f80 DropDown, ListBox and DropDownDatGrid paste using context menu into search box not filtering
Fix #2437
2026-02-04 11:28:12 +02:00
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
23 changed files with 527 additions and 161 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

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Xunit;
@@ -114,6 +114,11 @@ namespace Radzen.Blazor.Tests
public List<string> Values { get; set; }
}
public class Order
{
public DateTime? OrderDate { get; set; }
}
[Fact]
public void GetProperty_Should_Resolve_DescriptionProperty()
{
@@ -137,6 +142,14 @@ namespace Radzen.Blazor.Tests
Assert.NotNull(idProperty);
}
[Fact]
public void GetPropertyType_Resolves_NullableDateTime_Date()
{
var propertyType = PropertyAccess.GetPropertyType(typeof(Order), "OrderDate.Date");
Assert.Equal(typeof(DateTime), propertyType);
}
interface ISimpleInterface : ISimpleNestedInterface
{
string Description { get; set; }

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Xunit;
using Radzen;
using System.Linq.Expressions;
namespace Radzen.Blazor.Tests
{
@@ -24,6 +25,11 @@ namespace Radzen.Blazor.Tests
public int Priority { get; set; }
}
private class Order
{
public DateTime? OrderDate { get; set; }
}
private List<TestItem> GetTestData()
{
return new List<TestItem>
@@ -36,6 +42,24 @@ namespace Radzen.Blazor.Tests
};
}
[Fact]
public void GetNestedPropertyExpression_Handles_NullableDateTime_Date()
{
var parameter = Expression.Parameter(typeof(Order), "x");
var expression = QueryableExtension.GetNestedPropertyExpression(parameter, "OrderDate.Date");
var getter = Expression.Lambda<Func<Order, DateTime>>(expression, parameter).Compile();
var order = new Order { OrderDate = new DateTime(2024, 2, 3, 14, 30, 0) };
var result = getter(order);
Assert.Equal(order.OrderDate.Value.Date, result);
var nullOrder = new Order { OrderDate = null };
var nullResult = getter(nullOrder);
Assert.Equal(default(DateTime), nullResult);
}
// OrderBy tests
[Fact]
public void OrderBy_SortsAscending_ByDefault()

View File

@@ -651,7 +651,11 @@ namespace Radzen
/// <param name="shouldSelectOnChange">Should select item on item change with keyboard.</param>
protected virtual async Task HandleKeyPress(Microsoft.AspNetCore.Components.Web.KeyboardEventArgs args, bool isFilter = false, bool? shouldSelectOnChange = null)
{
if (Disabled || Data == null || args == null)
ArgumentNullException.ThrowIfNull(args);
var key = args.Code != null ? args.Code : args.Key;
if (Disabled || Data == null || args == null || key == null)
return;
List<object> items = Enumerable.Empty<object>().ToList();
@@ -675,8 +679,6 @@ namespace Radzen
}
}
var key = args.Code != null ? args.Code : args.Key;
if (!args.AltKey && (key == "ArrowDown" || key == "ArrowLeft" || key == "ArrowUp" || key == "ArrowRight"))
{
preventKeydown = true;
@@ -936,6 +938,25 @@ namespace Radzen
}
}
/// <summary>
/// Handles filter input changes (e.g. paste).
/// </summary>
/// <param name="args">The <see cref="ChangeEventArgs"/> instance containing the event data.</param>
protected virtual async Task OnFilterInput(ChangeEventArgs args)
{
ArgumentNullException.ThrowIfNull(args);
searchText = $"{args.Value}";
await SearchTextChanged.InvokeAsync(searchText);
if (ResetSelectedIndexOnFilter)
{
selectedIndex = -1;
}
Debounce(DebounceFilter, FilterDelay);
}
/// <summary>
/// Gets the load data arguments.
/// </summary>

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

@@ -26,61 +26,79 @@ public static class PropertyAccess
if (propertyName.Contains('[', StringComparison.Ordinal))
{
var arg = Expression.Parameter(typeof(TItem));
return Expression.Lambda<Func<TItem, TValue>>(QueryableExtension.GetNestedPropertyExpression(arg, propertyName ?? string.Empty, type), arg).Compile();
var arg0 = Expression.Parameter(typeof(TItem), "x");
return Expression.Lambda<Func<TItem, TValue>>(
QueryableExtension.GetNestedPropertyExpression(arg0, propertyName, type),
arg0
).Compile();
}
else
var arg = Expression.Parameter(typeof(TItem), "x");
Expression body = arg;
if (type != null)
body = Expression.Convert(body, type);
Expression AccessNoInterface(Expression instance, string memberName)
{
var arg = Expression.Parameter(typeof(TItem));
Expression body = arg;
if (type != null)
if (instance.Type.IsInterface)
{
body = Expression.Convert(body, type);
var declaringType =
new[] { instance.Type }
.Concat(instance.Type.GetInterfaces())
.FirstOrDefault(t => t.GetProperty(memberName) != null);
if (declaringType == null)
throw new InvalidOperationException($"Member '{memberName}' not found on interface '{instance.Type}'.");
return Expression.Property(instance, declaringType, memberName);
}
foreach (var member in propertyName.Split("."))
try
{
if (body.Type.IsInterface)
{
body = Expression.Property(body,
new[] { body.Type }.Concat(body.Type.GetInterfaces()).FirstOrDefault(t => t.GetProperty(member) != null)!,
member
);
}
else
{
try
{
body = Expression.PropertyOrField(body, member);
}
catch (AmbiguousMatchException)
{
var property = body.Type.GetProperty(member, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
if (property != null)
{
body = Expression.Property(body, property);
}
else
{
var field = body.Type.GetField(member, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
if (field != null)
{
body = Expression.Field(body, field);
}
}
}
}
return Expression.PropertyOrField(instance, memberName);
}
catch (AmbiguousMatchException)
{
var prop = instance.Type.GetProperty(memberName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
if (prop != null) return Expression.Property(instance, prop);
body = Expression.Convert(body, typeof(TValue));
var field = instance.Type.GetField(memberName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
if (field != null) return Expression.Field(instance, field);
return Expression.Lambda<Func<TItem, TValue>>(body, arg).Compile();
throw;
}
}
Expression AccessWithNullPropagation(Expression instance, string memberName)
{
var underlying = Nullable.GetUnderlyingType(instance.Type);
if (underlying != null && memberName is not ("Value" or "HasValue"))
{
var hasValue = Expression.Property(instance, "HasValue");
var value = Expression.Property(instance, "Value");
var accessed = AccessNoInterface(value, memberName);
return Expression.Condition(hasValue, accessed, Expression.Default(accessed.Type));
}
if (!instance.Type.IsValueType)
{
var notNull = Expression.NotEqual(instance, Expression.Constant(null, instance.Type));
var accessed = AccessNoInterface(instance, memberName);
return Expression.Condition(notNull, accessed, Expression.Default(accessed.Type));
}
return AccessNoInterface(instance, memberName);
}
foreach (var member in propertyName.Split('.'))
body = AccessWithNullPropagation(body, member);
body = Expression.Convert(body, typeof(TValue));
return Expression.Lambda<Func<TItem, TValue>>(body, arg).Compile();
}
/// <summary>
@@ -402,7 +420,7 @@ public static class PropertyAccess
if (type != null)
{
return !type.IsInterface ?
type.GetProperty(property ?? "")?.PropertyType :
(Nullable.GetUnderlyingType(type) ?? type).GetProperty(property ?? "")?.PropertyType :
new Type[] { type }
.Concat(type.GetInterfaces())
.FirstOrDefault(t => t.GetProperty(property ?? "") != null)?

View File

@@ -81,7 +81,10 @@ namespace Radzen
}
catch (NotSupportedException)
{
// HttpNavigationManager does not support that
// HttpNavigationManager does not support RegisterLocationChangingHandler.
// This means we are server-side rendering. Unsubscribe from ThemeChanged to
// avoid calling NavigateTo which would cause a 302 redirect during prerendering.
themeService.ThemeChanged -= OnThemeChanged;
}
#endif
}

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.
@@ -419,79 +478,150 @@ namespace Radzen
internal static Expression GetNestedPropertyExpression(Expression expression, string property, Type? type = null)
{
ArgumentNullException.ThrowIfNull(expression);
if (string.IsNullOrWhiteSpace(property)) return Expression.Constant(null, typeof(object));
var parts = property.Split(separator, 2);
string currentPart = parts[0];
var currentPart = parts[0];
static Expression BuildIsNull(Expression expr)
{
var underlying = Nullable.GetUnderlyingType(expr.Type);
if (underlying != null)
{
return Expression.Not(Expression.Property(expr, "HasValue"));
}
if (!expr.Type.IsValueType)
{
return Expression.Equal(expr, Expression.Constant(null, expr.Type));
}
return Expression.Constant(false);
}
static Expression UnwrapNullableIfNeeded(Expression expr)
{
return Nullable.GetUnderlyingType(expr.Type) != null
? Expression.Property(expr, "Value")
: expr;
}
static Expression AccessMember(Expression instance, string memberName)
{
var t = instance.Type;
if (t.IsInterface)
{
var declaring =
new[] { t }.Concat(t.GetInterfaces())
.FirstOrDefault(i => i.GetProperty(memberName) != null);
if (declaring == null)
throw new InvalidOperationException($"Member '{memberName}' not found on interface '{t}'.");
return Expression.Property(instance, declaring, memberName);
}
try
{
return Expression.PropertyOrField(instance, memberName);
}
catch (AmbiguousMatchException)
{
var prop = t.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
if (prop != null) return Expression.Property(instance, prop);
var field = t.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
if (field != null) return Expression.Field(instance, field);
throw;
}
}
static Expression NullPropagate(Expression parent, Expression accessed)
{
var isNull = BuildIsNull(parent);
var whenNull = Expression.Default(accessed.Type);
return Expression.Condition(isNull, whenNull, accessed);
}
Expression member;
if (expression.Type.IsGenericType && typeof(IDictionary<,>).IsAssignableFrom(expression.Type.GetGenericTypeDefinition()) ||
typeof(IDictionary).IsAssignableFrom(expression.Type) || typeof(System.Data.DataRow).IsAssignableFrom(expression.Type))
var parentForAccess = UnwrapNullableIfNeeded(expression);
if ((parentForAccess.Type.IsGenericType && typeof(IDictionary<,>).IsAssignableFrom(parentForAccess.Type.GetGenericTypeDefinition())) ||
typeof(IDictionary).IsAssignableFrom(parentForAccess.Type) ||
typeof(System.Data.DataRow).IsAssignableFrom(parentForAccess.Type))
{
var key = currentPart.Split('"')[1];
var typeString = currentPart.Split('(')[0];
var indexer = typeof(System.Data.DataRow).IsAssignableFrom(expression.Type) ?
Expression.Property(expression, expression.Type.GetProperty("Item", new[] { typeof(string) })!, Expression.Constant(key)) :
Expression.Property(expression, expression.Type.GetProperty("Item")!, Expression.Constant(key));
member = Expression.Convert(
indexer,
parts.Length > 1 ? indexer.Type : type ?? Type.GetType(typeString.EndsWith('?') ? $"System.Nullable`1[System.{typeString.TrimEnd('?')}]" : $"System.{typeString}") ?? typeof(object));
var indexer =
typeof(System.Data.DataRow).IsAssignableFrom(parentForAccess.Type)
? Expression.Property(parentForAccess, parentForAccess.Type.GetProperty("Item", new[] { typeof(string) })!, Expression.Constant(key))
: Expression.Property(parentForAccess, parentForAccess.Type.GetProperty("Item")!, Expression.Constant(key));
var targetType =
parts.Length > 1
? indexer.Type
: type
?? Type.GetType(
typeString.EndsWith('?')
? $"System.Nullable`1[System.{typeString.TrimEnd('?')}]"
: $"System.{typeString}"
)
?? typeof(object);
member = Expression.Convert(indexer, targetType);
member = NullPropagate(expression, member);
}
else if (currentPart.Contains('[', StringComparison.Ordinal)) // Handle array or list indexing
else if (currentPart.Contains('[', StringComparison.Ordinal))
{
var indexStart = currentPart.IndexOf('[', StringComparison.Ordinal);
var propertyName = currentPart.Substring(0, indexStart);
var indexString = currentPart.Substring(indexStart + 1, currentPart.Length - indexStart - 2);
member = Expression.PropertyOrField(expression, propertyName);
if (int.TryParse(indexString, out int index))
var collection = AccessMember(parentForAccess, propertyName);
collection = NullPropagate(expression, collection);
if (!int.TryParse(indexString, out var index))
throw new ArgumentException($"Invalid index format: {indexString}");
var underlyingCollection = Nullable.GetUnderlyingType(collection.Type) != null
? Expression.Property(collection, "Value")
: collection;
Expression indexed;
if (underlyingCollection.Type.IsArray)
{
if (member.Type.IsArray)
{
member = Expression.ArrayIndex(member, Expression.Constant(index));
}
else if (member.Type.IsGenericType &&
(member.Type.GetGenericTypeDefinition() == typeof(List<>) ||
typeof(IList<>).IsAssignableFrom(member.Type.GetGenericTypeDefinition())))
{
var itemProperty = member.Type.GetProperty("Item");
if (itemProperty != null)
{
member = Expression.Property(member, itemProperty, Expression.Constant(index));
}
}
indexed = Expression.ArrayIndex(underlyingCollection, Expression.Constant(index));
}
else
{
throw new ArgumentException($"Invalid index format: {indexString}");
var itemProp = underlyingCollection.Type.GetProperty("Item");
if (itemProp == null)
throw new InvalidOperationException($"Type '{underlyingCollection.Type}' has no indexer 'Item'.");
indexed = Expression.Property(underlyingCollection, itemProp, Expression.Constant(index));
}
}
else if (expression != null && expression.Type != null && expression.Type.IsInterface)
{
member = Expression.Property(expression,
new[] { expression.Type }.Concat(expression.Type.GetInterfaces()).FirstOrDefault(t => t.GetProperty(currentPart) != null)!,
currentPart
);
member = NullPropagate(collection, indexed);
}
else
{
if (expression == null || string.IsNullOrEmpty(currentPart))
{
return Expression.Constant(null, typeof(object));
var accessed = AccessMember(parentForAccess, currentPart);
member = NullPropagate(expression, accessed);
}
var p = expression.Type?.GetProperty(currentPart, BindingFlags.Public | BindingFlags.Instance);
member = p != null ? Expression.Property(expression, p) : Expression.PropertyOrField(expression, currentPart);
}
if (parts.Length > 1)
return GetNestedPropertyExpression(member, parts[1], type);
if (expression != null && expression.Type != null && expression.Type.IsValueType && Nullable.GetUnderlyingType(expression.Type) == null)
{
expression = Expression.Convert(expression, typeof(object));
}
return parts.Length > 1 ? GetNestedPropertyExpression(member, parts[1], type) :
(Nullable.GetUnderlyingType(member.Type) != null || member.Type == typeof(string)) ?
expression != null ? Expression.Condition(Expression.Equal(expression, Expression.Constant(null)), Expression.Constant(null, member.Type), member) : member :
member;
return member;
}
internal static Expression GetExpression<T>(ParameterExpression parameter, FilterDescriptor filter, FilterCaseSensitivity filterCaseSensitivity, Type type)

View File

@@ -11,7 +11,7 @@
<IsPackable>true</IsPackable>
<PackageId>Radzen.Blazor</PackageId>
<Product>Radzen.Blazor</Product>
<Version>8.7.3</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

@@ -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

@@ -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))">
<span class="@(Grid.ExpandedGroupItemStyle(this, Grid.allGroupsExpanded != null ? Grid.allGroupsExpanded : rowArgs?.Item1.Expanded))"></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

@@ -151,8 +151,8 @@
<div class="rz-multiselect-filter-container">
<input id="@SearchID" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" class="rz-inputtext" role="textbox" type="text"
onclick="Radzen.preventDefaultAndStopPropagation(event)" aria-label="@SearchAriaLabel"
@ref="@search" @oninput=@(args => { searchText = $"{args.Value}"; SearchTextChanged.InvokeAsync(searchText);})
@onchange="@((args) => OnFilter(args))" @onkeydown="@((args) => OnFilterKeyPress(args))" value="@searchText" autocomplete="@FilterAutoCompleteType" />
@ref="@search" @oninput="@OnFilterInput"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress" value="@searchText" autocomplete="@FilterAutoCompleteType" />
<span class="notranslate rz-multiselect-filter-icon rzi rzi-search"></span>
</div>
}
@@ -197,7 +197,7 @@
<div class="rz-dropdown-filter-container">
<input aria-label="@SearchAriaLabel" id="@SearchID" @ref="@search" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" placeholder="@FilterPlaceholder" class="rz-dropdown-filter rz-inputtext" autocomplete="@FilterAutoCompleteType" aria-autocomplete="none" type="text"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress"
@bind:event="oninput" @bind:get="searchText" @bind:set="@(args => { searchText = $"{args}"; SearchTextChanged.InvokeAsync(searchText);})" />
@bind:event="oninput" @bind:get="searchText" @bind:set="@(args => OnFilterInput(new ChangeEventArgs(){ Value = args }))" />
<span class="notranslate rz-dropdown-filter-icon rzi rzi-search"></span>
</div>
</text>
@@ -208,7 +208,7 @@
<div class="rz-dropdown-filter-container">
<input aria-label="@SearchAriaLabel" id="@SearchID" @ref="@search" tabindex="@(Disabled ? "-1" : $"{TabIndex}")" placeholder="@FilterPlaceholder" class="rz-dropdown-filter rz-inputtext" autocomplete="@FilterAutoCompleteType" aria-autocomplete="none" type="text"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress" value="@searchText"
@oninput="@((ChangeEventArgs args) => { searchText = $"{args.Value}"; SearchTextChanged.InvokeAsync(searchText);})" />
@oninput="@OnFilterInput" />
<span class="notranslate rz-dropdown-filter-icon rzi rzi-search"></span>
</div>
</text>

View File

@@ -1,4 +1,4 @@
@using Radzen
@using Radzen
@using System.Collections
@using System.Collections.Generic
@using Microsoft.AspNetCore.Components.Forms
@@ -204,7 +204,7 @@
<text>
<input aria-label="@SearchAriaLabel" class="rz-lookup-search-input" id="@SearchID" @ref="@search" tabindex="0" placeholder="@SearchTextPlaceholder"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress" style="@(ShowSearch ? "" : "margin-right:0px;")"
@bind:event="oninput" @bind:get="searchText" @bind:set=@(args => { searchText = $"{args}"; SearchTextChanged.InvokeAsync(searchText);}) />
@bind:event="oninput" @bind:get="searchText" @bind:set=@(args => OnFilterInput(new ChangeEventArgs(){ Value = args }))" />
</text>
};
#else
@@ -212,7 +212,7 @@
<text>
<input aria-label="@SearchAriaLabel" class="rz-lookup-search-input" id="@SearchID" @ref="@search" tabindex="0" placeholder="@SearchTextPlaceholder"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress" value="@searchText" style="@(ShowSearch ? "" : "margin-right:0px;")"
@oninput=@((ChangeEventArgs args) => { searchText = $"{args.Value}"; SearchTextChanged.InvokeAsync(searchText);}) />
@oninput="@OnFilterInput" />
</text>
};
#endif

View File

@@ -622,18 +622,12 @@ namespace Radzen.Blazor
foreach (string word in words)
{
if (TextProperty != null)
{
query = query.Where(TextProperty, word, FilterOperator, FilterCaseSensitivity);
}
query = query.Where(TextProperty!, word, FilterOperator, FilterCaseSensitivity);
}
}
else
{
if (TextProperty != null)
{
query = query.Where(TextProperty, searchText, FilterOperator, FilterCaseSensitivity);
}
query = query.Where(TextProperty!, searchText, FilterOperator, FilterCaseSensitivity);
}
}
}

View File

@@ -1,4 +1,4 @@
@using Radzen
@using Radzen
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.JSInterop
@using Microsoft.AspNetCore.Components.Rendering
@@ -65,7 +65,7 @@
<text>
<input id="@SearchID" @ref="@search" disabled="@Disabled" class="rz-inputtext" role="textbox" type="text" placeholder="@Placeholder"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress" @onkeydown:stopPropagation="true"
@bind:event="oninput" @bind:get="searchText" @bind:set=@(args => { searchText = $"{args}"; SearchTextChanged.InvokeAsync(searchText);}) aria-label="@SearchAriaLabel" />
@bind:event="oninput" @bind:get="searchText" @bind:set=@(args => OnFilterInput(new ChangeEventArgs(){ Value = args }))" aria-label="@SearchAriaLabel" />
</text>
};
#else
@@ -73,7 +73,7 @@
<text>
<input id="@SearchID" @ref="@search" disabled="@Disabled" class="rz-inputtext" role="textbox" type="text" placeholder="@Placeholder"
@onchange="@OnFilter" @onkeydown="@OnFilterKeyPress" value="@searchText" @onkeydown:stopPropagation="true"
@oninput=@((ChangeEventArgs args) => { searchText = $"{args.Value}"; SearchTextChanged.InvokeAsync(searchText);}) aria-label="@SearchAriaLabel" />
@oninput="@OnFilterInput" aria-label="@SearchAriaLabel" />
</text>
};
#endif

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,5 +1,7 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using Radzen.Blazor.Rendering;
using System;
using System.Globalization;
using System.Threading.Tasks;
@@ -155,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;

View File

@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Globalization;
using Radzen.Blazor.Rendering;
using System.Text;
namespace Radzen.Blazor;
@@ -185,12 +186,13 @@ public static class RadzenQREncoder
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, background);
AppendEye(sb, vb - 11, 4, eyeShapeTopRight ?? eyeShape, eyeColorTopRight ?? baseEyeColor, background);
AppendEye(sb, 4, vb - 11, eyeShapeBottomLeft ?? eyeShape, eyeColorBottomLeft ?? baseEyeColor, background);
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++)
{
@@ -1147,28 +1149,60 @@ public static class RadzenQREncoder
return inTL || inTR || inBL;
}
private static void AppendEye(StringBuilder sb, double x, double y, QRCodeEyeShape shape, string color, string background)
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, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" rx=\"1.25\" ry=\"1.25\" fill=\"{color}\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1)}\" y=\"{Format(y + 1)}\" width=\"5\" height=\"5\" rx=\"0.25\" ry=\"0.25\" fill=\"{background}\"/>");
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, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"{color}\"/>");
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=\"{background}\"/>");
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, $"<rect x=\"{Format(x)}\" y=\"{Format(y)}\" width=\"7\" height=\"7\" fill=\"{color}\"/>");
sb.Append(CultureInfo.InvariantCulture, $"<rect x=\"{Format(x + 1)}\" y=\"{Format(y + 1)}\" width=\"5\" height=\"5\" fill=\"{background}\"/>");
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

@@ -155,6 +155,12 @@ namespace Radzen.Blazor
/// </summary>
public bool IsResizing { get; private set; }
/// <summary>
/// Value indicating if the splitter should call StateHasChanged on resizing.
/// </summary>
[Parameter]
public bool ChangeStateOnResize { get; set; } = true;
/// <summary>
/// Called on pane resizing.
/// </summary>
@@ -163,7 +169,10 @@ namespace Radzen.Blazor
{
IsResizing = true;
StateHasChanged();
if (ChangeStateOnResize)
{
StateHasChanged();
}
await Task.CompletedTask;
}

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)