using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using TeamAAS.Dialogs.Dialogs; namespace TeamAAS.Logging.Controls { /// /// 实时日志控件:订阅 联动显示 AppLogger / PluginLogger 的日志。 /// - 类别筛选:Debug/Info/Warning/Error/Critical 多选开关; /// - 显示判定:公共日志恒显;插件日志由各节点「显示详细度」闸门决定是否进流(与是否落盘无关); /// - 插件条目上的 L2/L4 为归一化档位标签,仅用于区分常规/详细,不参与显隐。 /// public partial class LogView : UserControl { /// 显示上限依赖属性的默认值(可在 XAML 用 MaxEntries 覆盖)。 private const int DefaultMaxEntries = 500; /// 显示上限:控件最多保留的最近条目数,超出移除最旧。可在 XAML 用 MaxEntries 设置。 public static readonly DependencyProperty MaxEntriesProperty = DependencyProperty.Register(nameof(MaxEntries), typeof(int), typeof(LogView), new PropertyMetadata(DefaultMaxEntries, OnMaxEntriesChanged)); /// 显示上限(最近 N 条),默认 500,<=0 视为 1。 public int MaxEntries { get => (int)GetValue(MaxEntriesProperty); set => SetValue(MaxEntriesProperty, value); } private static void OnMaxEntriesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => (d as LogView)?.TrimToMax(); private readonly ObservableCollection _items = new ObservableCollection(); private readonly ICollectionView _view; private ScrollViewer _scrollViewer; private bool _subscribed; public LogView() { InitializeComponent(); _view = CollectionViewSource.GetDefaultView(_items); _view.Filter = o => IsCategoryEnabled(((LogEntry)o).Category); LogList.ItemsSource = _view; Loaded += OnLoaded; Unloaded += OnUnloaded; } private void OnLoaded(object sender, RoutedEventArgs e) { if (_subscribed) return; // 首次加载回填历史缓冲(新建控件实例时 _items 为空) if (_items.Count == 0) { foreach (var entry in LogStream.Snapshot()) AppendInternal(entry); } LogStream.Published += OnPublished; _subscribed = true; ScrollToEnd(); } private void OnUnloaded(object sender, RoutedEventArgs e) { if (!_subscribed) return; LogStream.Published -= OnPublished; _subscribed = false; } private void OnPublished(LogEntry entry) { var dispatcher = Application.Current?.Dispatcher; if (dispatcher != null && !dispatcher.CheckAccess()) dispatcher.BeginInvoke(new Action(() => Append(entry))); else Append(entry); } private void Append(LogEntry entry) { bool atBottom = IsScrolledToEnd(); AppendInternal(entry); if (atBottom) ScrollToEnd(); } private void AppendInternal(LogEntry entry) { _items.Add(entry); TrimToMax(); } private void TrimToMax() { int max = MaxEntries <= 0 ? 1 : MaxEntries; while (_items.Count > max) _items.RemoveAt(0); } private void CategoryToggle_Changed(object sender, RoutedEventArgs e) { // 初始化阶段(ToggleButton 尚未就绪)不刷新 if (_view != null && LogList != null) _view.Refresh(); } private void BtnClear_Click(object sender, RoutedEventArgs e) { _items.Clear(); } // 双击一条日志 → 弹出详情窗(含来源/时间/类别/完整内容,支持复制) private void LogList_MouseDoubleClick(object sender, MouseButtonEventArgs e) { var entry = FindEntry(e.OriginalSource as DependencyObject) ?? LogList.SelectedItem as LogEntry; if (entry == null) return; var window = new LogDetailWindow(entry) { Owner = Window.GetWindow(this) }; window.ShowDialog(); } // 从双击命中点沿(视觉/逻辑)树上溯,找到 DataContext 为 LogEntry 的元素 private static LogEntry FindEntry(DependencyObject node) { while (node != null) { if (node is FrameworkElement fe && fe.DataContext is LogEntry entry) return entry; node = node is Visual ? VisualTreeHelper.GetParent(node) : LogicalTreeHelper.GetParent(node); } return null; } private bool IsCategoryEnabled(LogLevel category) { switch (category) { case LogLevel.Warning: return TgWarning == null || TgWarning.IsChecked == true; case LogLevel.Error: return TgError == null || TgError.IsChecked == true; default: return TgInfo == null || TgInfo.IsChecked == true; // Info } } private bool IsScrolledToEnd() { if (_scrollViewer == null) _scrollViewer = FindScrollViewer(LogList); if (_scrollViewer == null) return true; return _scrollViewer.VerticalOffset >= _scrollViewer.ScrollableHeight - 4; } private void ScrollToEnd() { if (LogList.Items.Count > 0) LogList.ScrollIntoView(LogList.Items[LogList.Items.Count - 1]); } private static ScrollViewer FindScrollViewer(DependencyObject root) { if (root == null) return null; int count = VisualTreeHelper.GetChildrenCount(root); for (int i = 0; i < count; i++) { var child = VisualTreeHelper.GetChild(root, i); if (child is ScrollViewer sv) return sv; var result = FindScrollViewer(child); if (result != null) return result; } return null; } } }