Files
Mimante/Mimante/Dialogs/ProgressDialog.xaml.cs
T
Alby96andClaude Fable 5.1 3985935958 Ricalcolo dei prodotti dallo storico, finestra di avanzamento, operazioni sull'apprendimento, barre uguali
Nella scheda Prodotti c'è il pulsante che rilegge tutto lo storico e rifà da capo i
numeri di ogni articolo: aste viste, prezzo minimo, massimo e medio, puntate del
vincitore, vittorie, osservazioni. I limiti scritti a mano e la stellina non cambiano.
Il ricalcolo si costruisce da parte e sostituisce le schede solo alla fine: un
annullamento a metà lascia quelle di prima.

Ogni operazione massiva passa da una finestra di avanzamento (ProgressDialog): titolo,
a che punto si è, barra, conto fatto/totale, tempo passato e Annulla. Modale quando è
questione di secondi, altrimenti si continua a usare l'applicazione: il recupero delle
puntate dei vincitori e lo studio delle aste vanno piano di proposito.

La scheda Apprendimento ha un menu di operazioni: studiare le aste chiuse non ancora
apprese (lo studio all'avvio si ferma dopo qualche minuto, questo va in fondo),
ricostruire il profilo per prodotto e fascia oraria, rifare il bandit dalle decisioni
registrate, azzerare lo sfidante o la calibrazione, ricominciare da capo. Lo studio
delle aste è una sola routine con budget di tempo facoltativo, avanzamento e
annullamento; quello che è già studiato resta studiato.

Le barre in alto di tutte le schede hanno la stessa altezza (stile TabToolbar): passando
da una scheda all'altra i pulsanti restano allo stesso posto.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 14:47:47 +02:00

145 lines
5.5 KiB
C#

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using AutoBidder.Utilities;
namespace AutoBidder.Dialogs
{
/// <summary>
/// La finestra di avanzamento delle operazioni lunghe: titolo, una riga che dice a
/// che punto si è, la barra, il conto fatto/totale, il tempo passato e Annulla.
///
/// <para>Si usa da <see cref="RunAsync{T}"/>: si passa il lavoro da fare, che riceve
/// un <see cref="IProgress{T}"/> e un token di annullamento; la finestra si apre, segue
/// l'avanzamento e si chiude da sola. In modalità «modale» la finestra principale
/// resta disabilitata finché il lavoro non finisce; altrimenti si può continuare a
/// usare l'applicazione (le aste in corso non si fermano mai, in nessun caso).</para>
/// </summary>
public partial class ProgressDialog : Window
{
private readonly CancellationTokenSource _cts = new();
private readonly Stopwatch _elapsed = Stopwatch.StartNew();
private readonly DispatcherTimer _clock;
private bool _finished;
public CancellationToken Token => _cts.Token;
public ProgressDialog(string title, string message, bool cancellable)
{
InitializeComponent();
Title = title;
TitleText.Text = title;
MessageText.Text = message;
CancelButton.Visibility = cancellable ? Visibility.Visible : Visibility.Collapsed;
_clock = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_clock.Tick += (_, _) => ElapsedText.Text = _elapsed.Elapsed.TotalSeconds < 60
? $"{_elapsed.Elapsed.TotalSeconds:N0} s"
: $"{(int)_elapsed.Elapsed.TotalMinutes} min {_elapsed.Elapsed.Seconds:00} s";
_clock.Start();
}
/// <summary>Aggiorna barra e testi. Va chiamato sul thread dell'interfaccia.</summary>
public void Report(ProgressStep step)
{
if (_finished) return;
if (step.Total > 0)
{
Bar.IsIndeterminate = false;
Bar.Maximum = step.Total;
Bar.Value = Math.Clamp(step.Done, 0, step.Total);
CountText.Text = $"{step.Done:N0} / {step.Total:N0}";
}
else
{
Bar.IsIndeterminate = true;
CountText.Text = step.Done > 0 ? step.Done.ToString("N0") : "";
}
if (!string.IsNullOrEmpty(step.Message)) MessageText.Text = step.Message;
}
private void CancelButton_Click(object sender, RoutedEventArgs e) => RequestCancel();
private void RequestCancel()
{
if (_cts.IsCancellationRequested) return;
_cts.Cancel();
CancelButton.IsEnabled = false;
MessageText.Text = "Annullamento in corso: aspetto che il passo in corso finisca…";
}
/// <summary>
/// La X della finestra non chiude niente a metà: chiede l'annullamento, se
/// l'operazione lo ammette, e la finestra se ne va quando il lavoro è finito.
/// </summary>
protected override void OnClosing(CancelEventArgs e)
{
if (!_finished)
{
e.Cancel = true;
if (CancelButton.Visibility == Visibility.Visible) RequestCancel();
}
base.OnClosing(e);
}
private void Finish()
{
_finished = true;
_clock.Stop();
Close();
}
/// <summary>
/// Esegue <paramref name="work"/> mostrando la finestra di avanzamento sopra
/// <paramref name="owner"/>. Restituisce l'esito: risultato, annullata, o errore.
/// </summary>
/// <param name="modal">Vero: la finestra principale è disabilitata finché non si finisce.</param>
/// <param name="cancellable">Vero: c'è il pulsante Annulla e il token viene annullato.</param>
public static async Task<ProgressOutcome<T>> RunAsync<T>(
Window? owner, string title, string message,
Func<IProgress<ProgressStep>, CancellationToken, Task<T>> work,
bool modal = true, bool cancellable = true)
{
var dialog = new ProgressDialog(title, message, cancellable);
if (owner != null && owner.IsLoaded) dialog.Owner = owner;
// Progress<T> cattura il contesto di sincronizzazione di chi lo crea: da qui,
// il thread dell'interfaccia. I rapporti arrivano già sul thread giusto.
var progress = new Progress<ProgressStep>(dialog.Report);
if (modal && owner != null) owner.IsEnabled = false;
dialog.Show();
try
{
var result = await work(progress, dialog.Token);
return dialog.Token.IsCancellationRequested
? ProgressOutcome<T>.CancelledByUser()
: ProgressOutcome<T>.Ok(result);
}
catch (OperationCanceledException)
{
return ProgressOutcome<T>.CancelledByUser();
}
catch (Exception ex)
{
return ProgressOutcome<T>.Failed(ex);
}
finally
{
if (modal && owner != null) owner.IsEnabled = true;
dialog.Finish();
owner?.Activate();
}
}
}
}