将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。

This commit is contained in:
QI Mingxuan
2026-04-16 17:31:13 +08:00
parent 6ec4c3ddaa
commit 2bd6e566c3
581 changed files with 74600 additions and 222 deletions
@@ -0,0 +1,49 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Serilog.Core;
using Serilog.Events;
namespace XP.Common.Logging.ViewModels
{
/// <summary>
/// Serilog 自定义 Sink,将日志事件转发到 RealTimeLogViewerViewModel,并维护环形缓冲区
/// Custom Serilog Sink that forwards log events and maintains a ring buffer for history
/// </summary>
public class RealTimeLogSink : ILogEventSink
{
private const int BufferCapacity = 500;
private readonly ConcurrentQueue<LogEvent> _buffer = new();
/// <summary>
/// 全局单例实例 | Global singleton instance
/// </summary>
public static RealTimeLogSink Instance { get; } = new();
/// <summary>
/// 日志事件到达时触发 | Fired when a log event arrives
/// </summary>
public event Action<LogEvent>? LogEventReceived;
public void Emit(LogEvent logEvent)
{
// 入队缓冲区 | Enqueue to buffer
_buffer.Enqueue(logEvent);
while (_buffer.Count > BufferCapacity)
{
_buffer.TryDequeue(out _);
}
LogEventReceived?.Invoke(logEvent);
}
/// <summary>
/// 获取缓冲区中的历史日志快照 | Get a snapshot of buffered history logs
/// </summary>
public List<LogEvent> GetBufferedHistory()
{
return _buffer.ToList();
}
}
}
@@ -0,0 +1,329 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using Prism.Commands;
using Prism.Mvvm;
using Serilog.Events;
using XP.Common.Localization;
namespace XP.Common.Logging.ViewModels
{
/// <summary>
/// 实时日志查看器 ViewModel,管理日志条目、过滤、自动滚动等逻辑
/// Real-time log viewer ViewModel, manages log entries, filtering, auto-scroll, etc.
/// </summary>
public class RealTimeLogViewerViewModel : BindableBase
{
private readonly Dispatcher _dispatcher;
private readonly ObservableCollection<LogDisplayEntry> _allEntries = new();
private string _filterText = string.Empty;
private bool _isAutoScroll = true;
private int _maxLines = 2000;
private int _totalCount;
private int _filteredCount;
private bool _showDebug = true;
private bool _showInfo = true;
private bool _showWarning = true;
private bool _showError = true;
private bool _showFatal = true;
public RealTimeLogViewerViewModel()
{
_dispatcher = Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher;
FilteredEntries = new ObservableCollection<LogDisplayEntry>();
ClearCommand = new DelegateCommand(ClearAll);
}
/// <summary>
/// 过滤后的日志条目集合(绑定到 UI| Filtered log entries (bound to UI)
/// </summary>
public ObservableCollection<LogDisplayEntry> FilteredEntries { get; }
/// <summary>
/// 过滤关键词 | Filter keyword
/// </summary>
public string FilterText
{
get => _filterText;
set
{
if (SetProperty(ref _filterText, value))
{
ApplyFilter();
}
}
}
/// <summary>
/// 是否自动滚动到底部 | Whether to auto-scroll to bottom
/// </summary>
public bool IsAutoScroll
{
get => _isAutoScroll;
set => SetProperty(ref _isAutoScroll, value);
}
/// <summary>
/// 最大行数限制 | Maximum line count limit
/// </summary>
public int MaxLines
{
get => _maxLines;
set => SetProperty(ref _maxLines, value);
}
/// <summary>
/// 总日志条数 | Total log entry count
/// </summary>
public int TotalCount
{
get => _totalCount;
private set => SetProperty(ref _totalCount, value);
}
/// <summary>
/// 过滤后的日志条数 | Filtered log entry count
/// </summary>
public int FilteredCount
{
get => _filteredCount;
private set => SetProperty(ref _filteredCount, value);
}
/// <summary>
/// 是否显示 Debug 级别日志 | Whether to show Debug level logs
/// </summary>
public bool ShowDebug
{
get => _showDebug;
set { if (SetProperty(ref _showDebug, value)) ApplyFilter(); }
}
/// <summary>
/// 是否显示 Info 级别日志 | Whether to show Information level logs
/// </summary>
public bool ShowInfo
{
get => _showInfo;
set { if (SetProperty(ref _showInfo, value)) ApplyFilter(); }
}
/// <summary>
/// 是否显示 Warning 级别日志 | Whether to show Warning level logs
/// </summary>
public bool ShowWarning
{
get => _showWarning;
set { if (SetProperty(ref _showWarning, value)) ApplyFilter(); }
}
/// <summary>
/// 是否显示 Error 级别日志 | Whether to show Error level logs
/// </summary>
public bool ShowError
{
get => _showError;
set { if (SetProperty(ref _showError, value)) ApplyFilter(); }
}
/// <summary>
/// 是否显示 Fatal 级别日志 | Whether to show Fatal level logs
/// </summary>
public bool ShowFatal
{
get => _showFatal;
set { if (SetProperty(ref _showFatal, value)) ApplyFilter(); }
}
/// <summary>
/// 状态栏文本 | Status bar text
/// </summary>
public string StatusText
{
get
{
bool hasLevelFilter = !(_showDebug && _showInfo && _showWarning && _showError && _showFatal);
bool hasTextFilter = !string.IsNullOrEmpty(_filterText);
if (hasLevelFilter || hasTextFilter)
return LocalizationHelper.Get("LogViewer_StatusFiltered", TotalCount, FilteredCount);
return LocalizationHelper.Get("LogViewer_StatusTotal", TotalCount);
}
}
/// <summary>
/// 清空日志命令 | Clear log command
/// </summary>
public DelegateCommand ClearCommand { get; }
/// <summary>
/// 自动滚动请求事件,View 层订阅此事件执行滚动 | Auto-scroll request event
/// </summary>
public event Action? ScrollToBottomRequested;
/// <summary>
/// 添加日志事件(线程安全,可从任意线程调用)| Add log event (thread-safe)
/// </summary>
public void AddLogEvent(LogEvent logEvent)
{
if (logEvent == null) return;
var entry = new LogDisplayEntry(logEvent);
if (_dispatcher.CheckAccess())
{
AddEntryInternal(entry);
}
else
{
_dispatcher.InvokeAsync(() => AddEntryInternal(entry));
}
}
/// <summary>
/// 内部添加日志条目 | Internal add log entry
/// </summary>
private void AddEntryInternal(LogDisplayEntry entry)
{
_allEntries.Add(entry);
// 超出最大行数时移除最旧的条目 | Remove oldest entries when exceeding max lines
while (_allEntries.Count > _maxLines)
{
var removed = _allEntries[0];
_allEntries.RemoveAt(0);
FilteredEntries.Remove(removed);
}
// 判断是否匹配过滤条件 | Check if entry matches filter
if (MatchesFilter(entry))
{
FilteredEntries.Add(entry);
}
TotalCount = _allEntries.Count;
FilteredCount = FilteredEntries.Count;
RaisePropertyChanged(nameof(StatusText));
// 请求自动滚动 | Request auto-scroll
if (_isAutoScroll)
{
ScrollToBottomRequested?.Invoke();
}
}
/// <summary>
/// 应用过滤器 | Apply filter
/// </summary>
private void ApplyFilter()
{
FilteredEntries.Clear();
foreach (var entry in _allEntries)
{
if (MatchesFilter(entry))
{
FilteredEntries.Add(entry);
}
}
FilteredCount = FilteredEntries.Count;
RaisePropertyChanged(nameof(StatusText));
}
/// <summary>
/// 判断条目是否匹配过滤条件 | Check if entry matches filter
/// </summary>
private bool MatchesFilter(LogDisplayEntry entry)
{
// 级别过滤 | Level filter
var levelMatch = entry.LevelFull switch
{
"Debug" => _showDebug,
"Information" => _showInfo,
"Warning" => _showWarning,
"Error" => _showError,
"Fatal" => _showFatal,
_ => true
};
if (!levelMatch) return false;
// 关键词过滤 | Keyword filter
if (string.IsNullOrWhiteSpace(_filterText)) return true;
return entry.Message.Contains(_filterText, StringComparison.OrdinalIgnoreCase)
|| entry.Level.Contains(_filterText, StringComparison.OrdinalIgnoreCase)
|| entry.Source.Contains(_filterText, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// 清空所有日志 | Clear all logs
/// </summary>
private void ClearAll()
{
_allEntries.Clear();
FilteredEntries.Clear();
TotalCount = 0;
FilteredCount = 0;
RaisePropertyChanged(nameof(StatusText));
}
}
/// <summary>
/// 日志显示条目模型 | Log display entry model
/// </summary>
public class LogDisplayEntry
{
public LogDisplayEntry(LogEvent logEvent)
{
Timestamp = logEvent.Timestamp.LocalDateTime;
TimestampDisplay = Timestamp.ToString("HH:mm:ss.fff");
Level = logEvent.Level.ToString().Substring(0, 3).ToUpper();
LevelFull = logEvent.Level.ToString();
// 渲染消息文本 | Render message text
Message = logEvent.RenderMessage();
// 如果有异常,追加异常信息 | Append exception info if present
if (logEvent.Exception != null)
{
Message += Environment.NewLine + logEvent.Exception.ToString();
}
// 提取 SourceContext | Extract SourceContext
if (logEvent.Properties.TryGetValue("SourceContext", out var sourceValue))
{
Source = sourceValue.ToString().Trim('"');
}
else
{
Source = string.Empty;
}
// 根据日志级别设置颜色 | Set color based on log level
LevelColor = logEvent.Level switch
{
LogEventLevel.Fatal => new SolidColorBrush(Color.FromRgb(180, 0, 0)),
LogEventLevel.Error => new SolidColorBrush(Colors.Red),
LogEventLevel.Warning => new SolidColorBrush(Color.FromRgb(200, 130, 0)),
LogEventLevel.Debug => new SolidColorBrush(Colors.Gray),
LogEventLevel.Verbose => new SolidColorBrush(Colors.LightGray),
_ => new SolidColorBrush(Color.FromRgb(30, 30, 30))
};
// Freeze 画刷,使其可跨线程访问(避免 DependencySource 线程异常)
// Freeze the brush so it can be accessed from any thread
LevelColor.Freeze();
}
public DateTime Timestamp { get; }
public string TimestampDisplay { get; }
public string Level { get; }
public string LevelFull { get; }
public string Message { get; }
public string Source { get; }
public SolidColorBrush LevelColor { get; }
}
}