- ProgressForm: gerarchia visiva, contrasto testo, barra piatta blu al posto della ProgressBar nativa verde, footer separato dal bottone Annulla. - Barra Avanti/Indietro: footer separato con linea, padding allineato ai pannelli di ogni fase, riordinata sopra la barra di stato come convenzione standard invece che sotto. - Fase 5 (Verifica completezza): bottone principale reso primario, lista file .asm passata da griglia di icone troncate a lista a riga singola leggibile. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
176 lines
7.8 KiB
C#
176 lines
7.8 KiB
C#
namespace PrepStep;
|
|
|
|
public sealed class ProgressForm : Form
|
|
{
|
|
private readonly Label _lblTitolo = new();
|
|
private readonly Label _lblCartella = new();
|
|
private readonly Panel _barraTraccia = new();
|
|
private readonly Panel _barraRiempimento = new();
|
|
private readonly Label _lblPercentuale = new();
|
|
private readonly Label _lblConteggio = new();
|
|
private readonly Label _lblDettaglio = new();
|
|
private readonly Label _lblEta = new();
|
|
private readonly Button _btnAnnulla = new();
|
|
private readonly Panel _separatore = new();
|
|
private readonly Panel _piePagina = new();
|
|
private readonly ToolTip _tipCartella = new();
|
|
|
|
private const int Margine = 24;
|
|
// SystemColors.GrayText (~3.9:1 su bianco) è sotto la soglia WCAG AA 4.5:1 per testo
|
|
// normale: usiamo un grigio più scuro, leggibile ma comunque secondario.
|
|
private static readonly Color GrigioTesto = Color.FromArgb(90, 90, 90);
|
|
|
|
public event EventHandler? Annullato;
|
|
|
|
public ProgressForm()
|
|
{
|
|
Text = "Aggiornamento indice";
|
|
FormBorderStyle = FormBorderStyle.FixedDialog;
|
|
StartPosition = FormStartPosition.CenterParent;
|
|
MinimizeBox = false;
|
|
MaximizeBox = false;
|
|
ControlBox = false;
|
|
BackColor = Color.White;
|
|
ClientSize = new Size(480, 270);
|
|
|
|
_lblTitolo.Text = "Aggiornamento indice in corso...";
|
|
_lblTitolo.Location = new Point(Margine, 22);
|
|
_lblTitolo.AutoSize = true;
|
|
_lblTitolo.Font = new Font(Font.FontFamily, 11f, FontStyle.Bold);
|
|
|
|
_lblCartella.Text = "Preparazione...";
|
|
_lblCartella.Location = new Point(Margine, 52);
|
|
// AutoSize=false + AutoEllipsis: un percorso lungo (share di rete) va troncato con "..."
|
|
// invece di sforare il bordo del dialog, che è FixedDialog e non si può allargare.
|
|
// Il testo completo resta disponibile nel tooltip (stesso pattern di Form1._lblImportStatus).
|
|
_lblCartella.AutoSize = false;
|
|
_lblCartella.AutoEllipsis = true;
|
|
_lblCartella.Size = new Size(ClientSize.Width - Margine * 2, 20);
|
|
_lblCartella.ForeColor = GrigioTesto;
|
|
|
|
// Barra piatta disegnata a mano (traccia grigia + riempimento) invece della ProgressBar
|
|
// nativa: quella di Windows è verde e in stile "a blocchi", stona con il blu del resto
|
|
// dell'app (UiStile.Primario). Larghezza = margine destro reale meno lo spazio per
|
|
// "100%" in grassetto, così barra+percentuale rispettano lo stesso margine destro del
|
|
// resto del contenuto (prima finivano ~25px prima, sbilanciate rispetto al margine
|
|
// sinistro di 24px).
|
|
_barraTraccia.Location = new Point(Margine, 90);
|
|
_barraTraccia.Size = new Size(ClientSize.Width - Margine * 2 - 46, 10);
|
|
_barraTraccia.BackColor = Color.FromArgb(230, 230, 230);
|
|
_barraRiempimento.Location = Point.Empty;
|
|
_barraRiempimento.Size = new Size(0, _barraTraccia.Height);
|
|
_barraRiempimento.BackColor = UiStile.ColorePrimario;
|
|
_barraTraccia.Controls.Add(_barraRiempimento);
|
|
|
|
_lblPercentuale.AutoSize = true;
|
|
_lblPercentuale.Font = new Font(Font, FontStyle.Bold);
|
|
_lblPercentuale.TextAlign = ContentAlignment.MiddleRight;
|
|
|
|
_lblConteggio.Location = new Point(Margine, 122);
|
|
_lblConteggio.AutoSize = true;
|
|
|
|
_lblDettaglio.Location = new Point(Margine, 146);
|
|
_lblDettaglio.AutoSize = true;
|
|
_lblDettaglio.ForeColor = GrigioTesto;
|
|
|
|
_lblEta.Location = new Point(Margine, 174);
|
|
_lblEta.AutoSize = true;
|
|
|
|
// Footer separato dal contenuto (linea + sfondo leggermente diverso) invece del
|
|
// bottone appiccicato al bordo inferiore del dialog: ancorato in basso, quindi la
|
|
// spaziatura sopra/sotto resta corretta anche se la ClientSize cambia. Altezza footer
|
|
// 60px e ClientSize 270 scelti per lasciare sotto l'ultima riga (eta, Bottom≈189) lo
|
|
// stesso margine (~20px) usato sopra il titolo, invece dei ~7px sbilanciati di prima.
|
|
_separatore.BackColor = Color.FromArgb(230, 230, 230);
|
|
_separatore.Size = new Size(ClientSize.Width, 1);
|
|
_separatore.Location = new Point(0, ClientSize.Height - 60);
|
|
_separatore.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom;
|
|
|
|
_piePagina.BackColor = Color.FromArgb(247, 247, 248);
|
|
_piePagina.Size = new Size(ClientSize.Width, 59);
|
|
_piePagina.Location = new Point(0, ClientSize.Height - 59);
|
|
_piePagina.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom;
|
|
|
|
_btnAnnulla.Text = "Annulla";
|
|
UiStile.Secondario(_btnAnnulla);
|
|
UiStile.Arrotonda(_btnAnnulla);
|
|
_btnAnnulla.Click += (_, e) => Annullato?.Invoke(this, e);
|
|
_piePagina.Controls.Add(_btnAnnulla);
|
|
|
|
Controls.AddRange(new Control[]
|
|
{
|
|
_lblTitolo, _lblCartella, _barraTraccia, _lblPercentuale,
|
|
_lblConteggio, _lblDettaglio, _lblEta, _separatore, _piePagina
|
|
});
|
|
|
|
// Posizionato dopo l'aggiunta ai Controls: con AutoSize il Width vero si conosce
|
|
// solo a layout avvenuto, non subito dopo la creazione del bottone.
|
|
_btnAnnulla.Location = new Point(_piePagina.Width - Margine - _btnAnnulla.Width,
|
|
(_piePagina.Height - _btnAnnulla.Height) / 2);
|
|
_btnAnnulla.Anchor = AnchorStyles.Right | AnchorStyles.Top;
|
|
}
|
|
|
|
public void Aggiorna(ScanProgress p)
|
|
{
|
|
_lblCartella.Text = $"Scansione: {p.Cartella}";
|
|
_tipCartella.SetToolTip(_lblCartella, _lblCartella.Text);
|
|
|
|
if (p.PrevistiTotali > 0)
|
|
{
|
|
double frazione = Math.Min(1.0, (double)p.FattiTotali / p.PrevistiTotali);
|
|
_barraRiempimento.Width = (int)(frazione * _barraTraccia.Width);
|
|
_lblPercentuale.Text = $"{frazione * 100:0}%";
|
|
}
|
|
else
|
|
{
|
|
// ponytail: prima del conteggio totale (poco probabile: richiede la scansione
|
|
// precedente salvata nel DB) la barra resta vuota invece di un'animazione
|
|
// indeterminata — se serve, aggiungere un Timer che pulsa _barraRiempimento.
|
|
_barraRiempimento.Width = 0;
|
|
_lblPercentuale.Text = "";
|
|
}
|
|
// Riallineato al margine destro reale ogni volta: AutoSize aggiorna Width non appena
|
|
// Text cambia, quindi la Location va ricalcolata qui (non solo una volta nel costruttore).
|
|
_lblPercentuale.Location = new Point(
|
|
ClientSize.Width - Margine - _lblPercentuale.Width, _barraTraccia.Top - 3);
|
|
|
|
_lblConteggio.Text = p.PrevistiTotali > 0
|
|
? $"{p.FattiTotali:N0} / ~{p.PrevistiTotali:N0} file"
|
|
: $"{p.FattiTotali:N0} file processati";
|
|
|
|
_lblDettaglio.Text = $"nuovi: {p.Nuovi:N0} aggiornati: {p.Aggiornati:N0} invariati: {p.Invariati:N0}"
|
|
+ (p.Rimossi > 0 ? $" rimossi: {p.Rimossi:N0}" : "");
|
|
|
|
_lblEta.Text = $"Trascorso: {FormatTempo(p.Trascorso)} — {StimaRimanente(p)}";
|
|
}
|
|
|
|
private static string StimaRimanente(ScanProgress p)
|
|
{
|
|
if (p.PrevistiTotali <= 0 || p.FattiTotali <= 0)
|
|
{
|
|
return "stima non disponibile";
|
|
}
|
|
double filePerSecondo = p.FattiTotali / Math.Max(p.Trascorso.TotalSeconds, 0.001);
|
|
if (filePerSecondo <= 0)
|
|
{
|
|
return "stima non disponibile";
|
|
}
|
|
long rimanenti = Math.Max(p.PrevistiTotali - p.FattiTotali, 0);
|
|
var eta = TimeSpan.FromSeconds(rimanenti / filePerSecondo);
|
|
return $"tempo rimanente stimato: {FormatTempo(eta)}";
|
|
}
|
|
|
|
private static string FormatTempo(TimeSpan t)
|
|
{
|
|
if (t.TotalHours >= 1)
|
|
{
|
|
return $"{(int)t.TotalHours}h {t.Minutes}m {t.Seconds}s";
|
|
}
|
|
if (t.TotalMinutes >= 1)
|
|
{
|
|
return $"{t.Minutes}m {t.Seconds}s";
|
|
}
|
|
return $"{t.Seconds}s";
|
|
}
|
|
}
|