Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/BootstrapBlazor/Components/AutoFill/AutoFill.razor
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@namespace BootstrapBlazor.Components
@namespace BootstrapBlazor.Components
@using Microsoft.AspNetCore.Components.Web.Virtualization
@typeparam TValue
@inherits PopoverCompleteBase<TValue>
Expand Down Expand Up @@ -39,9 +39,7 @@
}
else
{
<Virtualize ItemSize="RowHeight" OverscanCount="OverscanCount" ItemsProvider="LoadItems"
Placeholder="RenderPlaceholderRow" ItemContent="RenderRow" @ref="_virtualizeElement">
</Virtualize>
@RenderVirtualize()
}
</div>
}
Expand Down
21 changes: 21 additions & 0 deletions src/BootstrapBlazor/Components/AutoFill/AutoFill.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,17 @@ public partial class AutoFill<TValue>
[NotNull]
public Func<VirtualizeQueryOption, Task<QueryData<TValue>>>? OnQueryAsync { get; set; }

#if NET11_0_OR_GREATER
/// <summary>
/// <para lang="zh">获得/设置虚拟滚动项目比较器。远程数据源返回新实例时,应按唯一标识进行比较</para>
/// <para lang="en">Gets or sets the virtualized item comparer. It should compare unique identifiers when the remote data source returns new instances.</para>
/// <para>v<version>11.0.0</version></para>
/// </summary>
[Parameter]
[NotNull]
public IEqualityComparer<TValue>? ItemComparer { get; set; }
#endif

/// <summary>
/// <para lang="zh">获得/设置 点击清除按钮回调方法 默认为 null</para>
/// <para lang="en">Gets or sets the callback method when the clear button is clicked. Default is null</para>
Expand Down Expand Up @@ -198,6 +209,10 @@ protected override void OnParametersSet()
_displayText = GetDisplayText(Value);
_clientValue = _displayText;
Items ??= [];

#if NET11_0_OR_GREATER
ItemComparer ??= EqualityComparer<TValue>.Default;
#endif
}

/// <summary>
Expand Down Expand Up @@ -343,4 +358,10 @@ protected override async Task OnBeforeBlurAsync()
await InvokeVoidAsync("setValue", Id, "");
}
}

private RenderFragment RenderVirtualize() => VirtualizeHelper.Render(LoadItems, RenderRow, RenderPlaceholderRow, RowHeight, OverscanCount,
#if NET11_0_OR_GREATER
ItemComparer,
#endif
element => _virtualizeElement = element);
}
4 changes: 1 addition & 3 deletions src/BootstrapBlazor/Components/Select/MultiSelect.razor
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,7 @@
}
else
{
<Virtualize ItemSize="RowHeight" OverscanCount="OverscanCount" ItemsProvider="LoadItems"
Placeholder="RenderPlaceHolderRow" ItemContent="RenderRow" @ref="_virtualizeElement">
</Virtualize>
@RenderVirtualize()
}
</div>
}
Expand Down
6 changes: 6 additions & 0 deletions src/BootstrapBlazor/Components/Select/MultiSelect.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,12 @@ private async ValueTask<ItemsProviderResult<SelectedItem>> LoadItems(ItemsProvid
return _result;
}

private RenderFragment RenderVirtualize() => VirtualizeHelper.Render(LoadItems, RenderRow, RenderPlaceHolderRow, RowHeight, OverscanCount,
#if NET11_0_OR_GREATER
VirtualizeItemComparer,
#endif
element => _virtualizeElement = element);

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down
6 changes: 2 additions & 4 deletions src/BootstrapBlazor/Components/Select/Select.razor
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@namespace BootstrapBlazor.Components
@namespace BootstrapBlazor.Components
@using Microsoft.AspNetCore.Components.Web.Virtualization
@typeparam TValue
@inherits SimpleSelectBase<TValue>
Expand Down Expand Up @@ -49,9 +49,7 @@
}
else
{
<Virtualize ItemSize="RowHeight" OverscanCount="OverscanCount" ItemsProvider="LoadItems"
Placeholder="RenderPlaceHolderRow" ItemContent="RenderRow" @ref="_virtualizeElement">
</Virtualize>
@RenderVirtualize()
}
</div>
}
Expand Down
6 changes: 6 additions & 0 deletions src/BootstrapBlazor/Components/Select/Select.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,12 @@ private async ValueTask<ItemsProviderResult<SelectedItem>> LoadItems(ItemsProvid
return _result;
}

private RenderFragment RenderVirtualize() => VirtualizeHelper.Render(LoadItems, RenderRow, RenderPlaceHolderRow, RowHeight, OverscanCount,
#if NET11_0_OR_GREATER
VirtualizeItemComparer,
#endif
element => _virtualizeElement = element);

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions src/BootstrapBlazor/Components/Select/SimpleSelectBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ public abstract class SimpleSelectBase<TValue> : SelectBase<TValue>
[NotNull]
protected Virtualize<SelectedItem>? _virtualizeElement = default;

#if NET11_0_OR_GREATER
/// <summary>
/// <para lang="zh">获得虚拟滚动项目比较器</para>
/// <para lang="en">Gets the virtualized item comparer</para>
/// </summary>
protected static IEqualityComparer<SelectedItem> VirtualizeItemComparer { get; } = new SelectedItemComparer();
#endif

/// <summary>
/// <para lang="zh">获得/设置 最后选中的值字符串</para>
/// <para lang="en">Gets or sets the last selected value string</para>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@namespace BootstrapBlazor.Components
@namespace BootstrapBlazor.Components
@using Microsoft.AspNetCore.Components.Web.Virtualization
@typeparam TValue
@inherits SelectBase<List<TValue>>
Expand Down Expand Up @@ -77,9 +77,7 @@
}
else
{
<Virtualize ItemSize="RowHeight" OverscanCount="OverscanCount" ItemsProvider="LoadItems"
Placeholder="RenderPlaceHolderRow" ItemContent="RenderRow" @ref="_virtualizeElement">
</Virtualize>
@RenderVirtualize()
}
</div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,12 @@ public partial class MultiSelectGeneric<TValue> : IModelEqualityComparer<TValue>
[NotNull]
private Virtualize<SelectedItem<TValue>>? _virtualizeElement = default;

#if NET11_0_OR_GREATER
private IEqualityComparer<SelectedItem<TValue>> VirtualizeItemComparer => _virtualizeItemComparer ??= new SelectedItemComparer<TValue>(this);

private IEqualityComparer<SelectedItem<TValue>>? _virtualizeItemComparer;
#endif

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down Expand Up @@ -357,6 +363,12 @@ private async ValueTask<ItemsProviderResult<SelectedItem<TValue>>> LoadItems(Ite
int GetCountByTotal() => _totalCount == 0 ? request.Count : Math.Min(request.Count, _totalCount - request.StartIndex);
}

private RenderFragment RenderVirtualize() => VirtualizeHelper.Render(LoadItems, RenderRow, RenderPlaceHolderRow, RowHeight, OverscanCount,
#if NET11_0_OR_GREATER
VirtualizeItemComparer,
#endif
element => _virtualizeElement = element);

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@namespace BootstrapBlazor.Components
@namespace BootstrapBlazor.Components
@using Microsoft.AspNetCore.Components.Web.Virtualization
@typeparam TValue
@inherits SelectBase<TValue>
Expand Down Expand Up @@ -48,7 +48,7 @@
}
else
{
<Virtualize ItemSize="RowHeight" OverscanCount="OverscanCount" ItemsProvider="LoadItems" Placeholder="RenderPlaceHolderRow" ItemContent="RenderRow" @ref="VirtualizeElement" />
@RenderVirtualize()
}
</div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ public partial class SelectGeneric<TValue> : ISelectGeneric<TValue>, IModelEqual
[NotNull]
private Virtualize<SelectedItem<TValue>>? VirtualizeElement { get; set; }

#if NET11_0_OR_GREATER
private IEqualityComparer<SelectedItem<TValue>> VirtualizeItemComparer => _virtualizeItemComparer ??= new SelectedItemComparer<TValue>(this);

private IEqualityComparer<SelectedItem<TValue>>? _virtualizeItemComparer;
#endif

/// <summary>
/// <para lang="zh">获得/设置 绑定数据集</para>
/// <para lang="en">Gets or sets Bound Dataset</para>
Expand Down Expand Up @@ -326,6 +332,12 @@ private async ValueTask<ItemsProviderResult<SelectedItem<TValue>>> LoadItems(Ite
int GetCountByTotal() => TotalCount == 0 ? request.Count : Math.Min(request.Count, TotalCount - request.StartIndex);
}

private RenderFragment RenderVirtualize() => VirtualizeHelper.Render(LoadItems, RenderRow, RenderPlaceHolderRow, RowHeight, OverscanCount,
#if NET11_0_OR_GREATER
VirtualizeItemComparer,
#endif
element => VirtualizeElement = element);

private async Task RefreshVirtualizeElement()
{
if (IsVirtualize && OnQueryAsync != null)
Expand Down
5 changes: 1 addition & 4 deletions src/BootstrapBlazor/Components/Table/Table.razor
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,7 @@
{
@if (OnQueryAsync != null)
{
<Virtualize @ref="_virtualizeElement"
ItemSize="RowHeight" OverscanCount="@OverscanCount" ItemsProvider="LoadItems"
ItemContent="RenderRow" Placeholder="RenderPlaceholderRow">
</Virtualize>
@RenderVirtualize()
}
else
{
Expand Down
12 changes: 12 additions & 0 deletions src/BootstrapBlazor/Components/Table/Table.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ public partial class Table<TItem> : ITable, IModelEqualityComparer<TItem> where
[NotNull]
private Virtualize<TItem>? _virtualizeElement = null;

#if NET11_0_OR_GREATER
private IEqualityComparer<TItem> VirtualizeItemComparer => _virtualizeItemComparer ??= new ModelHashSetComparer<TItem>(this);

private IEqualityComparer<TItem>? _virtualizeItemComparer;
#endif

/// <summary>
/// <para lang="zh">获得 Table 组件样式表</para>
/// <para lang="en">Get Table Component CSS Class</para>
Expand Down Expand Up @@ -1846,6 +1852,12 @@ private async ValueTask<ItemsProviderResult<TItem>> LoadItems(ItemsProviderReque
return new ItemsProviderResult<TItem>(QueryItems, TotalCount);
}

private RenderFragment RenderVirtualize() => VirtualizeHelper.Render(LoadItems, RenderRow, RenderPlaceholderRow, RowHeight, OverscanCount,
#if NET11_0_OR_GREATER
VirtualizeItemComparer,
#endif
element => _virtualizeElement = element);

private Func<Task> TriggerDoubleClickCell(ITableColumn col, TItem item) => async () =>
{
if (OnDoubleClickCellCallback != null)
Expand Down
35 changes: 35 additions & 0 deletions src/BootstrapBlazor/Components/Virtualization/VirtualizeHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone

using Microsoft.AspNetCore.Components.Web.Virtualization;

namespace BootstrapBlazor.Components;

internal static class VirtualizeHelper
{
public static RenderFragment Render<TItem>(
ItemsProviderDelegate<TItem> itemsProvider,
RenderFragment<TItem> itemContent,
RenderFragment<PlaceholderContext> placeholder,
float itemSize,
int overscanCount,
#if NET11_0_OR_GREATER
IEqualityComparer<TItem> itemComparer,
#endif
Action<Virtualize<TItem>> componentRef) => builder =>
{
builder.OpenComponent<Virtualize<TItem>>(0);
builder.AddAttribute(1, nameof(Virtualize<TItem>.ItemsProvider), itemsProvider);
builder.AddAttribute(2, nameof(Virtualize<TItem>.ItemContent), itemContent);
builder.AddAttribute(3, nameof(Virtualize<TItem>.Placeholder), placeholder);
builder.AddAttribute(4, nameof(Virtualize<TItem>.ItemSize), itemSize);
builder.AddAttribute(5, nameof(Virtualize<TItem>.OverscanCount), overscanCount);
#if NET11_0_OR_GREATER
builder.AddAttribute(6, nameof(Virtualize<TItem>.ItemComparer), itemComparer);
#endif
builder.AddComponentReferenceCapture(7, component => componentRef((Virtualize<TItem>)component));
builder.CloseComponent();
};
}
24 changes: 24 additions & 0 deletions src/BootstrapBlazor/Misc/SelectedItemComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone

namespace BootstrapBlazor.Components;

#if NET11_0_OR_GREATER
internal sealed class SelectedItemComparer : IEqualityComparer<SelectedItem>
{
public bool Equals(SelectedItem? x, SelectedItem? y) => ReferenceEquals(x, y) || (x is not null && y is not null && x.Value == y.Value);

public int GetHashCode(SelectedItem obj) => obj.Value.GetHashCode();
}

internal sealed class SelectedItemComparer<TValue>(IModelEqualityComparer<TValue> comparer) : IEqualityComparer<SelectedItem<TValue>>
{
private readonly ModelHashSetComparer<TValue> _comparer = new(comparer);

public bool Equals(SelectedItem<TValue>? x, SelectedItem<TValue>? y) => ReferenceEquals(x, y) || (x is not null && y is not null && _comparer.Equals(x.Value, y.Value));

public int GetHashCode(SelectedItem<TValue> obj) => obj.Value is null ? 0 : _comparer.GetHashCode(obj.Value);
}
#endif
84 changes: 84 additions & 0 deletions test/UnitTest/Misc/SelectedItemComparerTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone

using System.ComponentModel.DataAnnotations;
using System.Reflection;

namespace UnitTest.Misc;

#if NET11_0_OR_GREATER
public class SelectedItemComparerTest : BootstrapBlazorTestBase
{
[Fact]
public void SelectedItem_Equals_Ok()
{
var comparer = CreateComparer<SelectedItem>("BootstrapBlazor.Components.SelectedItemComparer");
var item = new SelectedItem("1", "Test 1");

Assert.True(comparer.Equals(null, null));
Assert.True(comparer.Equals(item, item));
Assert.False(comparer.Equals(null, item));
Assert.False(comparer.Equals(item, null));
Assert.True(comparer.Equals(item, new SelectedItem("1", "Test 2")));
Assert.False(comparer.Equals(item, new SelectedItem("2", "Test 1")));
Assert.Equal(item.Value.GetHashCode(), comparer.GetHashCode(item));
}

[Fact]
public void SelectedItemGeneric_Equals_Ok()
{
var modelComparer = new MockModelEqualityComparer<Model>()
{
ModelEqualityComparer = (x, y) => x.Id == y.Id
};
var comparer = CreateComparer<SelectedItem<Model>>(
"BootstrapBlazor.Components.SelectedItemComparer`1",
[typeof(Model)],
modelComparer);
var item = new SelectedItem<Model>(new() { Id = 1 }, "Test 1");

Assert.True(comparer.Equals(null, null));
Assert.True(comparer.Equals(item, item));
Assert.False(comparer.Equals(null, item));
Assert.False(comparer.Equals(item, null));
Assert.True(comparer.Equals(item, new SelectedItem<Model>(new() { Id = 1 }, "Test 2")));
Assert.False(comparer.Equals(item, new SelectedItem<Model>(new() { Id = 2 }, "Test 1")));
Assert.Equal(1, comparer.GetHashCode(item));
Assert.Equal(0, comparer.GetHashCode(new SelectedItem<Model>(null!, "Null")));
}

private static IEqualityComparer<TItem> CreateComparer<TItem>(string typeName, Type[]? genericArguments = null, params object[] arguments)
{
var type = typeof(SelectedItem).Assembly.GetType(typeName, throwOnError: true)!;
if (genericArguments is not null)
{
type = type.MakeGenericType(genericArguments);
}

return (IEqualityComparer<TItem>)Activator.CreateInstance(
type,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
binder: null,
args: arguments,
culture: null)!;
}

private sealed class Model
{
public int Id { get; set; }

public override int GetHashCode() => Id;
}

private sealed class MockModelEqualityComparer<TModel> : IModelEqualityComparer<TModel>
{
public Func<TModel, TModel, bool>? ModelEqualityComparer { get; set; }

public Type CustomKeyAttribute { get; set; } = typeof(KeyAttribute);

public bool Equals(TModel? x, TModel? y) => this.Equals<TModel>(x, y);
}
}
#endif
Loading