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 _loadingProcesses; public event EventHandler LoadingCancelled; public MaterialCancellableLoader() { InitializeComponent(); RemoveMessageFilter(); _loadingProcesses = new Dictionary(); 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 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); } } }