fix: 使用 CommunityToolkit.Mvvm
This commit is contained in:
@@ -3,11 +3,14 @@ using CloudPrint.Client.Infrastructure;
|
||||
using CloudPrint.Client.Models;
|
||||
using CloudPrint.Client.Services;
|
||||
using CloudPrint.Client.Services.Abstractions;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Grpc.Core;
|
||||
using ObservableObject = CommunityToolkit.Mvvm.ComponentModel.ObservableObject;
|
||||
|
||||
namespace CloudPrint.Client.ViewModels;
|
||||
|
||||
public sealed class MainViewModel : ObservableObject
|
||||
public sealed partial class MainViewModel : ObservableObject
|
||||
{
|
||||
private const int MaxReconnectAttempts = 5;
|
||||
private static readonly TimeSpan SubscriptionWatchInterval = TimeSpan.FromSeconds(5);
|
||||
@@ -28,16 +31,42 @@ public sealed class MainViewModel : ObservableObject
|
||||
private CancellationTokenSource? _queueCts;
|
||||
private CancellationTokenSource? _processingCts;
|
||||
private Task? _queueWorker;
|
||||
private PrintJob? _selectedJob;
|
||||
private PrintJob? _processingJob;
|
||||
private PrinterInfo? _selectedPrinter;
|
||||
private ConnectionState _connectionState = ConnectionState.Disconnected;
|
||||
private string _lastMessage = "已就绪";
|
||||
private PrinterInfo? _subscribedPrinter;
|
||||
private bool _isReconnecting;
|
||||
private bool _isManualDisconnect = true;
|
||||
private bool _autoProcessQueue = true;
|
||||
private bool _isQueuePaused;
|
||||
private bool _isQueueProcessing;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(CancelSelectedJobCommand))]
|
||||
private PrintJob? selectedJob;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(CanSubscribe))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ConnectCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SubscribeCommand))]
|
||||
private PrinterInfo? selectedPrinter;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(ConnectionStatus))]
|
||||
[NotifyPropertyChangedFor(nameof(CanSubscribe))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SubscribeCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(DisconnectCommand))]
|
||||
private ConnectionState connectionState = ConnectionState.Disconnected;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(QueueStatusText))]
|
||||
private bool autoProcessQueue = true;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(QueueStatusText))]
|
||||
private bool isQueuePaused;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(QueueStatusText))]
|
||||
private bool isQueueProcessing;
|
||||
|
||||
[ObservableProperty]
|
||||
private string lastMessage = "已就绪";
|
||||
|
||||
public MainViewModel()
|
||||
: this(new PrintService(), new FileService(), new GrpcService(), new WebSocketService(), new JsonSettingsService(), new WpfUiDispatcher(), new FileAppLogger())
|
||||
@@ -70,17 +99,6 @@ public sealed class MainViewModel : ObservableObject
|
||||
_backendClient.LogReceived += (_, entry) => AddLog(entry);
|
||||
_webSocketService.LogReceived += (_, entry) => AddLog(entry);
|
||||
|
||||
RefreshPrintersCommand = new RelayCommand(RefreshPrinters);
|
||||
ConnectCommand = new AsyncRelayCommand(ConnectAsync, () => SelectedPrinter is not null);
|
||||
SubscribeCommand = new AsyncRelayCommand(SubscribeAsync, () => CanSubscribe);
|
||||
ProcessSelectedJobCommand = new AsyncRelayCommand(ProcessSelectedJobAsync, () => Jobs.Any(job => job.Status == PrintJobStatus.Pending) && !IsQueueProcessing);
|
||||
CancelSelectedJobCommand = new AsyncRelayCommand(CancelSelectedJobAsync, () => CanCancelSelectedJob);
|
||||
PauseQueueCommand = new RelayCommand(PauseQueue, () => AutoProcessQueue && !IsQueuePaused);
|
||||
ResumeQueueCommand = new RelayCommand(ResumeQueue, () => AutoProcessQueue && IsQueuePaused);
|
||||
DisconnectCommand = new RelayCommand(Disconnect, () => _backendClient.IsConnected || _webSocketService.IsConnected);
|
||||
SaveSettingsCommand = new RelayCommand(SaveSettings);
|
||||
ClearLogsCommand = new RelayCommand(ClearLogs);
|
||||
|
||||
RefreshPrinters();
|
||||
}
|
||||
|
||||
@@ -91,47 +109,6 @@ public sealed class MainViewModel : ObservableObject
|
||||
public LibraryViewModel Library { get; }
|
||||
public SettingsViewModel SettingsPanel { get; }
|
||||
|
||||
public PrintJob? SelectedJob
|
||||
{
|
||||
get => _selectedJob;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedJob, value))
|
||||
{
|
||||
CancelSelectedJobCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PrinterInfo? SelectedPrinter
|
||||
{
|
||||
get => _selectedPrinter;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedPrinter, value))
|
||||
{
|
||||
(ConnectCommand as AsyncRelayCommand)?.RaiseCanExecuteChanged();
|
||||
(SubscribeCommand as AsyncRelayCommand)?.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectionState ConnectionState
|
||||
{
|
||||
get => _connectionState;
|
||||
private set
|
||||
{
|
||||
if (SetProperty(ref _connectionState, value))
|
||||
{
|
||||
OnPropertyChanged(nameof(ConnectionStatus));
|
||||
OnPropertyChanged(nameof(CanSubscribe));
|
||||
SubscribeCommand.RaiseCanExecuteChanged();
|
||||
DisconnectCommand.RaiseCanExecuteChanged();
|
||||
Library.RefreshCommandState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ConnectionStatus => ConnectionState switch
|
||||
{
|
||||
ConnectionState.Connecting => "连接中",
|
||||
@@ -145,49 +122,6 @@ public sealed class MainViewModel : ObservableObject
|
||||
public bool CanSubscribe => SelectedPrinter is not null && _backendClient.IsConnected;
|
||||
public bool CanCancelSelectedJob => SelectedJob is { Status: PrintJobStatus.Pending or PrintJobStatus.Printing };
|
||||
|
||||
public bool AutoProcessQueue
|
||||
{
|
||||
get => _autoProcessQueue;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _autoProcessQueue, value))
|
||||
{
|
||||
OnPropertyChanged(nameof(QueueStatusText));
|
||||
RaiseQueueCommandStates();
|
||||
if (value)
|
||||
{
|
||||
StartQueueProcessingIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsQueuePaused
|
||||
{
|
||||
get => _isQueuePaused;
|
||||
private set
|
||||
{
|
||||
if (SetProperty(ref _isQueuePaused, value))
|
||||
{
|
||||
OnPropertyChanged(nameof(QueueStatusText));
|
||||
RaiseQueueCommandStates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsQueueProcessing
|
||||
{
|
||||
get => _isQueueProcessing;
|
||||
private set
|
||||
{
|
||||
if (SetProperty(ref _isQueueProcessing, value))
|
||||
{
|
||||
OnPropertyChanged(nameof(QueueStatusText));
|
||||
RaiseQueueCommandStates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string QueueStatusText => AutoProcessQueue switch
|
||||
{
|
||||
false => "自动队列已关闭,可手动处理下一任务",
|
||||
@@ -196,23 +130,31 @@ public sealed class MainViewModel : ObservableObject
|
||||
_ => "自动队列已开启,等待新任务"
|
||||
};
|
||||
|
||||
public string LastMessage
|
||||
partial void OnConnectionStateChanged(ConnectionState value)
|
||||
{
|
||||
get => _lastMessage;
|
||||
private set => SetProperty(ref _lastMessage, value);
|
||||
Library.RefreshCommandState();
|
||||
}
|
||||
|
||||
public RelayCommand RefreshPrintersCommand { get; }
|
||||
public AsyncRelayCommand ConnectCommand { get; }
|
||||
public AsyncRelayCommand SubscribeCommand { get; }
|
||||
public AsyncRelayCommand ProcessSelectedJobCommand { get; }
|
||||
public AsyncRelayCommand CancelSelectedJobCommand { get; }
|
||||
public RelayCommand PauseQueueCommand { get; }
|
||||
public RelayCommand ResumeQueueCommand { get; }
|
||||
public RelayCommand DisconnectCommand { get; }
|
||||
public RelayCommand SaveSettingsCommand { get; }
|
||||
public RelayCommand ClearLogsCommand { get; }
|
||||
partial void OnAutoProcessQueueChanged(bool value)
|
||||
{
|
||||
RaiseQueueCommandStates();
|
||||
if (value)
|
||||
{
|
||||
StartQueueProcessingIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnIsQueuePausedChanged(bool value)
|
||||
{
|
||||
RaiseQueueCommandStates();
|
||||
}
|
||||
|
||||
partial void OnIsQueueProcessingChanged(bool value)
|
||||
{
|
||||
RaiseQueueCommandStates();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void RefreshPrinters()
|
||||
{
|
||||
Printers.Clear();
|
||||
@@ -225,6 +167,9 @@ public sealed class MainViewModel : ObservableObject
|
||||
AddLog(AppLogLevel.Info, $"发现 {Printers.Count} 台打印机");
|
||||
}
|
||||
|
||||
private bool CanConnect() => SelectedPrinter is not null;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanConnect))]
|
||||
private async Task ConnectAsync()
|
||||
{
|
||||
if (SelectedPrinter is null)
|
||||
@@ -246,16 +191,20 @@ public sealed class MainViewModel : ObservableObject
|
||||
StartHeartbeatLoop();
|
||||
|
||||
ConnectionState = ConnectionState.Connected;
|
||||
DisconnectCommand.RaiseCanExecuteChanged();
|
||||
DisconnectCommand.NotifyCanExecuteChanged();
|
||||
Library.RefreshCommandState();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResetConnectionsAfterFailure();
|
||||
ConnectionState = ConnectionState.Error;
|
||||
AddLog(AppLogEntry.FromException($"连接失败:{ex.Message}", ex));
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanSubscribeCommand() => CanSubscribe;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanSubscribeCommand))]
|
||||
private async Task SubscribeAsync()
|
||||
{
|
||||
if (SelectedPrinter is null)
|
||||
@@ -268,19 +217,24 @@ public sealed class MainViewModel : ObservableObject
|
||||
{
|
||||
ConnectionState = ConnectionState.Subscribing;
|
||||
await _backendClient.SubscribePrintJobsAsync(SelectedPrinter).ConfigureAwait(true);
|
||||
_subscribedPrinter = SelectedPrinter;
|
||||
ConnectionState = ConnectionState.Subscribed;
|
||||
StartSubscriptionWatchLoop();
|
||||
ProcessSelectedJobCommand.RaiseCanExecuteChanged();
|
||||
ProcessSelectedJobCommand.NotifyCanExecuteChanged();
|
||||
Library.RefreshCommandState();
|
||||
StartQueueProcessingIfNeeded();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResetConnectionsAfterFailure();
|
||||
ConnectionState = ConnectionState.Error;
|
||||
AddLog(AppLogEntry.FromException($"订阅失败:{ex.Message}", ex));
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanProcessSelectedJob() => Jobs.Any(job => job.Status == PrintJobStatus.Pending) && !IsQueueProcessing;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanProcessSelectedJob))]
|
||||
private async Task ProcessSelectedJobAsync()
|
||||
{
|
||||
var job = GetNextPendingJob(preferSelected: true);
|
||||
@@ -309,6 +263,9 @@ public sealed class MainViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanCancelSelectedJobCommand() => CanCancelSelectedJob;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanCancelSelectedJobCommand))]
|
||||
private async Task CancelSelectedJobAsync()
|
||||
{
|
||||
if (SelectedJob is null)
|
||||
@@ -335,8 +292,8 @@ public sealed class MainViewModel : ObservableObject
|
||||
}
|
||||
finally
|
||||
{
|
||||
ProcessSelectedJobCommand.RaiseCanExecuteChanged();
|
||||
CancelSelectedJobCommand.RaiseCanExecuteChanged();
|
||||
ProcessSelectedJobCommand.NotifyCanExecuteChanged();
|
||||
CancelSelectedJobCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,8 +308,9 @@ public sealed class MainViewModel : ObservableObject
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureJobPrinter(e.Job);
|
||||
Jobs.Insert(0, e.Job);
|
||||
ProcessSelectedJobCommand.RaiseCanExecuteChanged();
|
||||
ProcessSelectedJobCommand.NotifyCanExecuteChanged();
|
||||
});
|
||||
|
||||
if (isDuplicate)
|
||||
@@ -364,6 +322,7 @@ public sealed class MainViewModel : ObservableObject
|
||||
StartQueueProcessingIfNeeded();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SaveSettings()
|
||||
{
|
||||
ConfigureBackendMode();
|
||||
@@ -379,6 +338,22 @@ public sealed class MainViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetConnectionsAfterFailure()
|
||||
{
|
||||
_heartbeatCts?.Cancel();
|
||||
_subscriptionWatchCts?.Cancel();
|
||||
_queueCts?.Cancel();
|
||||
_processingCts?.Cancel();
|
||||
_subscribedPrinter = null;
|
||||
_backendClient.Disconnect();
|
||||
_ = _webSocketService.DisconnectAsync();
|
||||
NotifyConnectionCommandStates();
|
||||
Library.RefreshCommandState();
|
||||
}
|
||||
|
||||
private bool CanDisconnect() => _backendClient.IsConnected || _webSocketService.IsConnected;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanDisconnect))]
|
||||
private void Disconnect()
|
||||
{
|
||||
_isManualDisconnect = true;
|
||||
@@ -386,19 +361,27 @@ public sealed class MainViewModel : ObservableObject
|
||||
_subscriptionWatchCts?.Cancel();
|
||||
_queueCts?.Cancel();
|
||||
_processingCts?.Cancel();
|
||||
_subscribedPrinter = null;
|
||||
_backendClient.Disconnect();
|
||||
_ = _webSocketService.DisconnectAsync();
|
||||
ConnectionState = ConnectionState.Disconnected;
|
||||
AddLog(AppLogLevel.Warning, "客户端已断开连接");
|
||||
NotifyConnectionCommandStates();
|
||||
Library.RefreshCommandState();
|
||||
}
|
||||
|
||||
private bool CanPauseQueue() => AutoProcessQueue && !IsQueuePaused;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanPauseQueue))]
|
||||
private void PauseQueue()
|
||||
{
|
||||
IsQueuePaused = true;
|
||||
AddLog(AppLogLevel.Warning, "自动队列已暂停,当前任务完成后停止处理后续任务");
|
||||
}
|
||||
|
||||
private bool CanResumeQueue() => AutoProcessQueue && IsQueuePaused;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanResumeQueue))]
|
||||
private void ResumeQueue()
|
||||
{
|
||||
IsQueuePaused = false;
|
||||
@@ -521,10 +504,19 @@ public sealed class MainViewModel : ObservableObject
|
||||
{
|
||||
_uiDispatcher.Invoke(() =>
|
||||
{
|
||||
ProcessSelectedJobCommand.RaiseCanExecuteChanged();
|
||||
CancelSelectedJobCommand.RaiseCanExecuteChanged();
|
||||
PauseQueueCommand.RaiseCanExecuteChanged();
|
||||
ResumeQueueCommand.RaiseCanExecuteChanged();
|
||||
ProcessSelectedJobCommand.NotifyCanExecuteChanged();
|
||||
CancelSelectedJobCommand.NotifyCanExecuteChanged();
|
||||
PauseQueueCommand.NotifyCanExecuteChanged();
|
||||
ResumeQueueCommand.NotifyCanExecuteChanged();
|
||||
});
|
||||
}
|
||||
|
||||
private void NotifyConnectionCommandStates()
|
||||
{
|
||||
_uiDispatcher.Invoke(() =>
|
||||
{
|
||||
SubscribeCommand.NotifyCanExecuteChanged();
|
||||
DisconnectCommand.NotifyCanExecuteChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -642,6 +634,7 @@ public sealed class MainViewModel : ObservableObject
|
||||
await _backendClient.RegisterPrinterAsync(SelectedPrinter, Settings.ApiKey, cancellationToken).ConfigureAwait(false);
|
||||
await _backendClient.SendHeartbeatAsync(SelectedPrinter, cancellationToken).ConfigureAwait(false);
|
||||
await _backendClient.SubscribePrintJobsAsync(SelectedPrinter, cancellationToken).ConfigureAwait(false);
|
||||
_subscribedPrinter = SelectedPrinter;
|
||||
await _webSocketService.ConnectAsync(new Uri(ToWebSocketAddress(Settings.ServerAddress)), cancellationToken).ConfigureAwait(false);
|
||||
_uiDispatcher.Invoke(() => ConnectionState = ConnectionState.Subscribed);
|
||||
AddLog(AppLogLevel.Success, "自动重连成功,任务订阅已恢复");
|
||||
@@ -672,6 +665,7 @@ public sealed class MainViewModel : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ClearLogs()
|
||||
{
|
||||
Logs.Clear();
|
||||
@@ -724,6 +718,23 @@ public sealed class MainViewModel : ObservableObject
|
||||
return !Jobs.Any(existing => GetJobKey(existing).Equals(key, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private void EnsureJobPrinter(PrintJob job)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(job.PrinterName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var printer = _subscribedPrinter ?? SelectedPrinter;
|
||||
if (printer is null)
|
||||
{
|
||||
AddLog(AppLogLevel.Error, $"打印任务缺少本机打印机:{GetJobDisplayName(job)}");
|
||||
return;
|
||||
}
|
||||
|
||||
job.PrinterName = printer.Name;
|
||||
}
|
||||
|
||||
private static string GetJobKey(PrintJob job)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(job.JobNo))
|
||||
@@ -764,4 +775,4 @@ public sealed class MainViewModel : ObservableObject
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user