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>
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
<Window x:Class="AutoBidder.Dialogs.ProgressDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Operazione in corso"
|
||||
Width="480" SizeToContent="Height"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ShowInTaskbar="False" ResizeMode="NoResize"
|
||||
Background="{DynamicResource Brush.Bg}">
|
||||
|
||||
<StackPanel Margin="22,18">
|
||||
|
||||
<TextBlock x:Name="TitleText"
|
||||
FontSize="{StaticResource Font.Size.Lg}" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource Brush.Text}"/>
|
||||
|
||||
<TextBlock x:Name="MessageText" Style="{StaticResource Hint}" Margin="0,6,0,14"
|
||||
TextWrapping="Wrap"/>
|
||||
|
||||
<ProgressBar x:Name="Bar" Height="6" Minimum="0" Maximum="100" IsIndeterminate="True"/>
|
||||
|
||||
<Grid Margin="0,10,0,0">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="CountText" Style="{StaticResource Hint}" Text=""/>
|
||||
<TextBlock x:Name="ElapsedText" Style="{StaticResource Hint}" Margin="12,0,0,0" Text=""/>
|
||||
</StackPanel>
|
||||
<Button x:Name="CancelButton" Content="Annulla" HorizontalAlignment="Right"
|
||||
Style="{StaticResource SmallRoundedButton}"
|
||||
Background="{DynamicResource Brush.SurfaceAlt}"
|
||||
Foreground="{DynamicResource Brush.Text}"
|
||||
Padding="18,5" Click="CancelButton_Click"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,144 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user