Files
HawkeyeVision/framework/MaterialSkin.Core/Controls/MaterialCancellableLoader.cs
2025-09-16 10:42:43 +02:00

182 lines
5.6 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
namespace MaterialSkin.Controls
{
public partial class MaterialCancellableLoader : MaterialForm
{
private readonly Dictionary<string, (MaterialProgressBar ProgressBar, Label Label, MaterialFlatButton CancelButton, CancellationTokenSource CancellationTokenSource)> _loadingProcesses;
public event EventHandler<string> LoadingCancelled;
public MaterialCancellableLoader()
{
InitializeComponent();
RemoveMessageFilter();
_loadingProcesses = new Dictionary<string, (MaterialProgressBar, Label, MaterialFlatButton, CancellationTokenSource)>();
this.MaximizeBox = false;
this.MinimizeBox = false;
this.StartPosition = FormStartPosition.CenterScreen;
this.Width = 450;
}
public CancellationToken StartLoading(string title)
{
if (_loadingProcesses.ContainsKey(title))
{
return _loadingProcesses[title].CancellationTokenSource.Token;
}
var cancellationTokenSource = new CancellationTokenSource();
var progressBar = new MaterialProgressBar
{
Width = this.ClientSize.Width - 120, // Leave space for cancel button
Height = 20,
Top = 50 + (_loadingProcesses.Count * 60),
Left = 10,
Style = ProgressBarStyle.Marquee,
MarqueeAnimationSpeed = 30,
Value = 50,
BackColor = Color.White
};
var label = new Label
{
Text = title,
AutoSize = true,
Top = progressBar.Top - 20,
Left = progressBar.Left,
BackColor = Color.White,
ForeColor = SkinManager.GetPrimaryTextColor()
};
var cancelButton = new MaterialFlatButton
{
Text = "Cancel",
Width = 80,
Height = 30,
Top = progressBar.Top - 5,
Left = progressBar.Right + 10,
UseVisualStyleBackColor = true
};
// Capture the title in the closure properly
var capturedTitle = title;
cancelButton.Click += (sender, e) =>
{
CancelLoading(capturedTitle);
};
_loadingProcesses[title] = (progressBar, label, cancelButton, cancellationTokenSource);
this.Controls.Add(label);
this.Controls.Add(progressBar);
this.Controls.Add(cancelButton);
if (!this.Visible)
{
this.Show();
}
this.Refresh();
return cancellationTokenSource.Token;
}
public void FinishLoading(string title)
{
if (_loadingProcesses.TryGetValue(title, out var components))
{
this.Controls.Remove(components.ProgressBar);
this.Controls.Remove(components.Label);
this.Controls.Remove(components.CancelButton);
components.CancellationTokenSource.Dispose();
_loadingProcesses.Remove(title);
RearrangeControls();
}
if (_loadingProcesses.Count == 0)
{
this.Hide();
}
}
public void CancelLoading(string title)
{
if (_loadingProcesses.TryGetValue(title, out var components))
{
components.CancellationTokenSource.Cancel();
LoadingCancelled?.Invoke(this, title);
FinishLoading(title);
}
}
public void CancelAllLoading()
{
var titles = _loadingProcesses.Keys.ToList();
foreach (var title in titles)
{
CancelLoading(title);
}
}
public bool IsLoading(string title)
{
return _loadingProcesses.ContainsKey(title);
}
public bool HasActiveLoading => _loadingProcesses.Count > 0;
public IEnumerable<string> ActiveLoadingTitles => _loadingProcesses.Keys.ToList();
private void RearrangeControls()
{
int index = 0;
foreach (var components in _loadingProcesses.Values)
{
var newTop = 50 + (index * 60);
components.ProgressBar.Top = newTop;
components.Label.Top = newTop - 20;
components.CancelButton.Top = newTop - 5;
index++;
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
// Don't allow closing if there are active loading processes
if (_loadingProcesses.Count > 0)
{
e.Cancel = true;
return;
}
base.OnFormClosing(e);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Cancel all ongoing operations and dispose resources
foreach (var components in _loadingProcesses.Values)
{
components.CancellationTokenSource?.Dispose();
}
_loadingProcesses.Clear();
}
base.Dispose(disposing);
}
}
}