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>
145 lines
5.5 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|
|
}
|