diff --git a/EgwCoreLib.BlazorTest/Pages/TestLoading.razor b/EgwCoreLib.BlazorTest/Pages/TestLoading.razor
new file mode 100644
index 0000000..51306b3
--- /dev/null
+++ b/EgwCoreLib.BlazorTest/Pages/TestLoading.razor
@@ -0,0 +1,14 @@
+@page "/TestLoading"
+
+
TestLoading
+
+
+
+
+
+
+
+
+
+
+
diff --git a/EgwCoreLib.BlazorTest/Pages/TestLoading.razor.cs b/EgwCoreLib.BlazorTest/Pages/TestLoading.razor.cs
new file mode 100644
index 0000000..24296ba
--- /dev/null
+++ b/EgwCoreLib.BlazorTest/Pages/TestLoading.razor.cs
@@ -0,0 +1,58 @@
+namespace EgwCoreLib.BlazorTest.Pages
+{
+ public partial class TestLoading
+ {
+ #region Protected Properties
+
+ protected double expTime { get; set; } = 20;
+ protected int numSteps { get; set; } = 4;
+
+ #endregion Protected Properties
+
+ #region Protected Methods
+
+ protected async Task StartLongTimer()
+ {
+ double stdWait = expTime / numSteps;
+ int nextWait = 1000;
+ int stepVal = maxVal / numSteps;
+ // imposto i valori x progress..
+ maxVal = 100;
+ expTimeMSec = stdWait * 1000;
+ //nextVal = stepVal;
+ //currVal = 0;
+ for (int currStep = 1; currStep <= numSteps; currStep++)
+ {
+ //await Task.Delay(1);
+ // aggiorno valori
+ currVal = (currStep - 1) * stepVal;
+ nextVal = currStep * stepVal;
+ // se max mi fermo...
+ if (nextVal > maxVal)
+ {
+ nextVal = maxVal;
+ }
+ await InvokeAsync(StateHasChanged);
+ nextWait = (int)(rnd.Next(2000, 2500) * stdWait);
+ // attendo step successivi...
+ await Task.Delay(nextWait);
+ }
+ await Task.Delay(1);
+ currVal = maxVal;
+ await InvokeAsync(StateHasChanged);
+ }
+
+ #endregion Protected Methods
+
+ #region Private Fields
+
+ private int currVal = 100;
+ private double expTimeMSec = 10000;
+ private int maxVal = 100;
+ private int nextVal = 100;
+ private Random rnd = new Random();
+ private string titleMsg = "Test Progress";
+
+ #endregion Private Fields
+ }
+}
\ No newline at end of file
diff --git a/EgwCoreLib.Razor/ProgressDisplay.razor b/EgwCoreLib.Razor/ProgressDisplay.razor
new file mode 100644
index 0000000..7c2164d
--- /dev/null
+++ b/EgwCoreLib.Razor/ProgressDisplay.razor
@@ -0,0 +1,33 @@
+@*
+*@
+
+@if (IsLoading)
+{
+
+}
\ No newline at end of file
diff --git a/EgwCoreLib.Razor/ProgressDisplay.razor.cs b/EgwCoreLib.Razor/ProgressDisplay.razor.cs
new file mode 100644
index 0000000..03a4d2d
--- /dev/null
+++ b/EgwCoreLib.Razor/ProgressDisplay.razor.cs
@@ -0,0 +1,157 @@
+using Microsoft.AspNetCore.Components;
+
+namespace EgwCoreLib.Razor
+{
+ public partial class ProgressDisplay : IDisposable
+ {
+ #region Public Properties
+
+ [Parameter]
+ public double CurrVal
+ {
+ get => ActVal;
+ set
+ {
+ ActVal = value;
+ StartVal = value;
+ IsLoading = ActVal < MaxVal;
+ }
+ }
+
+ [Parameter]
+ public double ExpTimeMSec { get; set; } = 10000;
+
+ [Parameter]
+ public bool IsLoading { get; set; } = false;
+
+ [Parameter]
+ public double MaxVal { get; set; } = 100;
+
+ [Parameter]
+ public double NextVal { get; set; } = 100;
+
+ [Parameter]
+ public string Title { get; set; } = "Progress";
+
+ #endregion Public Properties
+
+ #region Public Methods
+
+ public void Dispose()
+ {
+ periodicTimer?.Dispose();
+ }
+
+ #endregion Public Methods
+
+ #region Protected Fields
+
+ protected PeriodicTimer? periodicTimer = null;
+
+ #endregion Protected Fields
+
+ #region Protected Properties
+
+ protected string currWidth { get; set; } = "";
+ protected string modalCss { get; set; } = "alert alert-success";
+
+ #endregion Protected Properties
+
+ #region Protected Methods
+
+#if false
+ protected override void OnInitialized()
+ {
+ RunTimer(); // fire-and-forget
+ }
+#endif
+
+ protected override async Task OnParametersSetAsync()
+ {
+ // salvo valori aggiornamento...
+ if (ExpTimeMSec > 0)
+ {
+ lastUpdate = DateTime.Now;
+ nextUpdate = lastUpdate.AddMilliseconds(ExpTimeMSec);
+ await RefreshDisplay();
+ }
+ if(IsLoading)
+ {
+ periodicTimer?.Dispose();
+ periodicTimer = new PeriodicTimer(TimeSpan.FromMilliseconds(RefInt));
+ RunTimer();
+ }
+ else
+ {
+ periodicTimer = null;
+ }
+ }
+
+ protected async Task RefreshDisplay()
+ {
+ currWidth = $"{(double)ActVal / MaxVal:P0}";
+ DisplayMsg = $"{ActVal / MaxVal:P2}";
+ await InvokeAsync(StateHasChanged);
+ }
+
+ #endregion Protected Methods
+
+ #region Private Fields
+
+ [Parameter]
+ public int RefreshInterval
+ {
+ get => RefInt;
+ set
+ {
+ if (RefInt != value && value > 0)
+ {
+ RefInt = value;
+#if false
+ periodicTimer?.Dispose();
+ periodicTimer = new PeriodicTimer(TimeSpan.FromMilliseconds(value));
+ RunTimer();
+#endif
+ }
+ }
+ }
+ private int RefInt { get; set; } = 100;
+
+ #endregion Private Fields
+
+ #region Private Properties
+
+ private double ActVal { get; set; } = 100;
+ private string DisplayMsg { get; set; } = "Loading...";
+ private DateTime lastUpdate { get; set; } = DateTime.Now.AddMinutes(-1);
+ private DateTime nextUpdate { get; set; } = DateTime.Now;
+ private double StartVal { get; set; } = 100;
+
+ #endregion Private Properties
+
+ #region Private Methods
+
+ private async void RunTimer()
+ {
+ while (periodicTimer!=null && await periodicTimer.WaitForNextTickAsync())
+ {
+ if (IsLoading)
+ {
+ DateTime adesso = DateTime.Now;
+ if (nextUpdate <= adesso)
+ {
+ nextUpdate = adesso.AddMilliseconds(RefreshInterval);
+ }
+ // calcolo delta ms...
+ var deltaMs = adesso.Subtract(lastUpdate).TotalMilliseconds;
+ var denomMs = nextUpdate.Subtract(lastUpdate).TotalMilliseconds;
+ // aggiorno display...
+ ActVal = StartVal + (NextVal - StartVal) * (deltaMs / denomMs);
+ await RefreshDisplay();
+ }
+ }
+ }
+
+ #endregion Private Methods
+ }
+}
\ No newline at end of file