Add tray health printer icons
This commit is contained in:
@@ -140,6 +140,13 @@ Wichtig:
|
||||
5. Im Tab `Allgemein` Polling aktivieren und Intervall setzen.
|
||||
6. Mit `Jetzt prüfen` kann sofort ein einzelner Backend-Poll ausgelöst werden.
|
||||
|
||||
## Tray-Status
|
||||
|
||||
Das Tray-Icon zeigt den aktuellen Zustand:
|
||||
|
||||
- Grün: Worker ist aktiviert, Backend-Konfiguration ist vollständig, Drucker ist verfügbar und der letzte Backend-Kontakt war erfolgreich.
|
||||
- Rot: Konfiguration fehlt, Drucker ist nicht verfügbar, Worker ist deaktiviert oder der Backend-Kontakt ist fehlgeschlagen.
|
||||
|
||||
## Nicht mehr im Agent
|
||||
|
||||
- keine Layout-JSON-Verwaltung
|
||||
|
||||
@@ -11,6 +11,9 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
||||
private readonly SettingsStore _settingsStore;
|
||||
private readonly PrinterService _printerService;
|
||||
private readonly BackendPollingWorker _backendWorker;
|
||||
private readonly Icon _healthyIcon;
|
||||
private readonly Icon _unhealthyIcon;
|
||||
private readonly System.Windows.Forms.Timer _statusTimer;
|
||||
private SettingsForm? _settingsForm;
|
||||
|
||||
public TrayApplicationContext(
|
||||
@@ -21,6 +24,8 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
||||
_settingsStore = settingsStore;
|
||||
_printerService = printerService;
|
||||
_backendWorker = backendWorker;
|
||||
_healthyIcon = TrayIconFactory.CreatePrinterIcon(Color.FromArgb(36, 160, 72));
|
||||
_unhealthyIcon = TrayIconFactory.CreatePrinterIcon(Color.FromArgb(210, 48, 48));
|
||||
|
||||
var menu = new ContextMenuStrip();
|
||||
menu.Items.Add("Einstellungen", null, (_, _) => ShowSettings());
|
||||
@@ -28,8 +33,8 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
||||
|
||||
_notifyIcon = new NotifyIcon
|
||||
{
|
||||
Text = "LabelPrintAgent",
|
||||
Icon = SystemIcons.Application,
|
||||
Text = "LabelPrintAgent: nicht verbunden",
|
||||
Icon = _unhealthyIcon,
|
||||
ContextMenuStrip = menu,
|
||||
Visible = true
|
||||
};
|
||||
@@ -40,6 +45,11 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
||||
ShowSettings();
|
||||
}
|
||||
};
|
||||
|
||||
_statusTimer = new System.Windows.Forms.Timer { Interval = 5000 };
|
||||
_statusTimer.Tick += (_, _) => UpdateTrayStatus();
|
||||
_statusTimer.Start();
|
||||
UpdateTrayStatus();
|
||||
}
|
||||
|
||||
private void ShowSettings()
|
||||
@@ -64,9 +74,68 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
||||
|
||||
protected override void ExitThreadCore()
|
||||
{
|
||||
_statusTimer.Stop();
|
||||
_statusTimer.Dispose();
|
||||
_notifyIcon.Visible = false;
|
||||
_notifyIcon.Dispose();
|
||||
_healthyIcon.Dispose();
|
||||
_unhealthyIcon.Dispose();
|
||||
_settingsForm?.Dispose();
|
||||
base.ExitThreadCore();
|
||||
}
|
||||
|
||||
private void UpdateTrayStatus()
|
||||
{
|
||||
var (isConfigured, configurationMessage) = GetConfigurationStatus();
|
||||
var workerStatus = _backendWorker.LastStatus;
|
||||
var isHealthy = isConfigured && workerStatus.IsHealthy;
|
||||
_notifyIcon.Icon = isHealthy ? _healthyIcon : _unhealthyIcon;
|
||||
_notifyIcon.Text = BuildTooltip(isHealthy, isConfigured ? workerStatus.Message : configurationMessage);
|
||||
}
|
||||
|
||||
private (bool IsConfigured, string Message) GetConfigurationStatus()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = _settingsStore.Load();
|
||||
if (!settings.Worker.Enabled)
|
||||
{
|
||||
return (false, "Worker deaktiviert.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings.Backend.BaseUrl))
|
||||
{
|
||||
return (false, "Backend-URL fehlt.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings.Backend.AgentId))
|
||||
{
|
||||
return (false, "Agent-ID fehlt.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings.Printer.PrinterName))
|
||||
{
|
||||
return (false, "Drucker fehlt.");
|
||||
}
|
||||
|
||||
if (!_printerService.IsPrinterAvailable(settings.Printer.PrinterName))
|
||||
{
|
||||
return (false, "Drucker nicht verfügbar.");
|
||||
}
|
||||
|
||||
return (true, "Konfiguration vollständig.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Could not evaluate tray status");
|
||||
return (false, "Statusprüfung fehlgeschlagen.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildTooltip(bool isHealthy, string message)
|
||||
{
|
||||
var prefix = isHealthy ? "LabelPrintAgent: verbunden" : "LabelPrintAgent: nicht bereit";
|
||||
var tooltip = $"{prefix} - {message}";
|
||||
return tooltip.Length <= 63 ? tooltip : tooltip[..63];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LabelPrintAgent.App;
|
||||
|
||||
internal static class TrayIconFactory
|
||||
{
|
||||
public static Icon CreatePrinterIcon(Color statusColor)
|
||||
{
|
||||
using var bitmap = new Bitmap(32, 32);
|
||||
using var graphics = Graphics.FromImage(bitmap);
|
||||
graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
graphics.Clear(Color.Transparent);
|
||||
|
||||
using var statusBrush = new SolidBrush(statusColor);
|
||||
using var blackBrush = new SolidBrush(Color.FromArgb(32, 32, 32));
|
||||
using var whiteBrush = new SolidBrush(Color.White);
|
||||
using var outlinePen = new Pen(Color.FromArgb(32, 32, 32), 2);
|
||||
using var paperPen = new Pen(Color.FromArgb(32, 32, 32), 1.5f);
|
||||
|
||||
graphics.FillEllipse(statusBrush, 1, 1, 30, 30);
|
||||
graphics.DrawEllipse(outlinePen, 1.5f, 1.5f, 29, 29);
|
||||
|
||||
var paper = new RectangleF(9, 5, 14, 9);
|
||||
graphics.FillRectangle(whiteBrush, paper);
|
||||
graphics.DrawRectangle(paperPen, paper.X, paper.Y, paper.Width, paper.Height);
|
||||
|
||||
var printerBody = new RectangleF(6, 13, 20, 10);
|
||||
graphics.FillRectangle(blackBrush, printerBody);
|
||||
graphics.DrawRectangle(outlinePen, printerBody.X, printerBody.Y, printerBody.Width, printerBody.Height);
|
||||
|
||||
var outputPaper = new RectangleF(10, 20, 12, 7);
|
||||
graphics.FillRectangle(whiteBrush, outputPaper);
|
||||
graphics.DrawRectangle(paperPen, outputPaper.X, outputPaper.Y, outputPaper.Width, outputPaper.Height);
|
||||
|
||||
graphics.FillEllipse(whiteBrush, 21, 15, 2.5f, 2.5f);
|
||||
|
||||
var handle = bitmap.GetHicon();
|
||||
try
|
||||
{
|
||||
using var icon = Icon.FromHandle(handle);
|
||||
return (Icon)icon.Clone();
|
||||
}
|
||||
finally
|
||||
{
|
||||
DestroyIcon(handle);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool DestroyIcon(IntPtr hIcon);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace LabelPrintAgent.Backend;
|
||||
|
||||
public sealed class AgentHealthStatus
|
||||
{
|
||||
public AgentHealthStatus(bool isHealthy, string message)
|
||||
{
|
||||
IsHealthy = isHealthy;
|
||||
Message = message;
|
||||
ChangedAt = DateTime.Now;
|
||||
}
|
||||
|
||||
public bool IsHealthy { get; }
|
||||
public string Message { get; }
|
||||
public DateTime ChangedAt { get; }
|
||||
}
|
||||
@@ -12,6 +12,7 @@ public sealed class BackendPollingWorker : IDisposable
|
||||
private readonly SemaphoreSlim _semaphore = new(1, 1);
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
private Task? _task;
|
||||
private AgentHealthStatus _lastStatus = new(false, "Noch nicht mit dem Backend verbunden.");
|
||||
|
||||
public BackendPollingWorker(SettingsStore settingsStore, BackendClient backendClient, PrinterService printerService)
|
||||
{
|
||||
@@ -20,6 +21,10 @@ public sealed class BackendPollingWorker : IDisposable
|
||||
_printerService = printerService;
|
||||
}
|
||||
|
||||
public event EventHandler<AgentHealthStatus>? StatusChanged;
|
||||
|
||||
public AgentHealthStatus LastStatus => _lastStatus;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_task is not null)
|
||||
@@ -43,6 +48,7 @@ public sealed class BackendPollingWorker : IDisposable
|
||||
var settings = _settingsStore.Load();
|
||||
if (!settings.Worker.Enabled || string.IsNullOrWhiteSpace(settings.Backend.BaseUrl))
|
||||
{
|
||||
SetStatus(false, "Worker deaktiviert oder Backend-URL fehlt.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -50,12 +56,14 @@ public sealed class BackendPollingWorker : IDisposable
|
||||
if (string.IsNullOrWhiteSpace(printerName) || !_printerService.IsPrinterAvailable(printerName))
|
||||
{
|
||||
Log.Warning("No available printer configured for backend polling");
|
||||
SetStatus(false, "Kein verfügbarer Drucker konfiguriert.");
|
||||
return;
|
||||
}
|
||||
|
||||
var job = await _backendClient.GetNextJobAsync(cancellationToken);
|
||||
if (job is null)
|
||||
{
|
||||
SetStatus(true, "Backend verbunden. Kein Druckjob vorhanden.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -66,17 +74,25 @@ public sealed class BackendPollingWorker : IDisposable
|
||||
if (result.Success)
|
||||
{
|
||||
await _backendClient.ReportPrintedAsync(job.JobId, printerName, cancellationToken);
|
||||
SetStatus(true, $"Job {job.JobId} erfolgreich gedruckt.");
|
||||
return;
|
||||
}
|
||||
|
||||
await _backendClient.ReportErrorAsync(job.JobId, printerName, result.ErrorMessage ?? "Druck fehlgeschlagen.", cancellationToken);
|
||||
SetStatus(false, $"Job {job.JobId} konnte nicht gedruckt werden.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Could not process backend label job {JobId}", job.JobId);
|
||||
await _backendClient.ReportErrorAsync(job.JobId, printerName, ex.Message, cancellationToken);
|
||||
SetStatus(false, $"Job {job.JobId} fehlgeschlagen: {ex.Message}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
SetStatus(false, $"Backend nicht verbunden: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
@@ -98,6 +114,7 @@ public sealed class BackendPollingWorker : IDisposable
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Backend polling failed");
|
||||
SetStatus(false, $"Backend nicht verbunden: {ex.Message}");
|
||||
}
|
||||
|
||||
var interval = Math.Max(1, _settingsStore.Load().Worker.PollIntervalSeconds);
|
||||
@@ -119,4 +136,11 @@ public sealed class BackendPollingWorker : IDisposable
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_semaphore.Dispose();
|
||||
}
|
||||
|
||||
private void SetStatus(bool isHealthy, string message)
|
||||
{
|
||||
var status = new AgentHealthStatus(isHealthy, message);
|
||||
_lastStatus = status;
|
||||
StatusChanged?.Invoke(this, status);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user