diff --git a/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs b/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs index 8fc62dfe..d1a58ea9 100644 --- a/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Reactive.Linq; +using System.Text; using System.Text.RegularExpressions; using ReactiveUI; using ReactiveUI.Fody.Helpers; @@ -9,7 +10,7 @@ namespace ServiceLib.ViewModels; public class MsgViewModel : MyReactiveObject { private readonly ConcurrentQueue _queueMsg = new(); - private readonly int _numMaxMsg = 500; + private readonly int _numMaxMsg = 500; // 仅用于限制队列,不做显示裁剪 private bool _lastMsgFilterNotAvailable; private bool _blLockShow = false; @@ -26,63 +27,45 @@ public class MsgViewModel : MyReactiveObject MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty; AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true; - this.WhenAnyValue( - x => x.MsgFilter) - .Subscribe(c => DoMsgFilter()); - - this.WhenAnyValue( - x => x.AutoRefresh, - y => y == true) - .Subscribe(c => { _config.MsgUIItem.AutoRefresh = AutoRefresh; }); + this.WhenAnyValue(x => x.MsgFilter).Subscribe(c => DoMsgFilter()); + this.WhenAnyValue(x => x.AutoRefresh, y => y == true) + .Subscribe(c => { _config.MsgUIItem.AutoRefresh = AutoRefresh; }); AppEvents.SendMsgViewRequested - .AsObservable() - //.ObserveOn(RxApp.MainThreadScheduler) - .Subscribe(async content => await AppendQueueMsg(content)); + .AsObservable() + .Subscribe(async content => await AppendQueueMsg(content)); } private async Task AppendQueueMsg(string msg) { - //if (msg == Global.CommandClearMsg) - //{ - // ClearMsg(); - // return; - //} - if (AutoRefresh == false) - { - return; - } - _ = EnqueueQueueMsg(msg); - - if (_blLockShow) - { - return; - } - if (!_config.UiItem.ShowInTaskbar) - { - return; - } + if (AutoRefresh == false) return; + await EnqueueQueueMsg(msg); + if (_blLockShow || !_config.UiItem.ShowInTaskbar) return; _blLockShow = true; await Task.Delay(500); - var txt = string.Join("", _queueMsg.ToArray()); - await _updateView?.Invoke(EViewAction.DispatcherShowMsg, txt); + + var sbDelta = new StringBuilder(); + while (_queueMsg.TryDequeue(out var line)) + { + sbDelta.Append(line); + } + + var delta = sbDelta.ToString(); + if (delta.Length > 0) + await _updateView?.Invoke(EViewAction.DispatcherShowMsg, delta); _blLockShow = false; } private async Task EnqueueQueueMsg(string msg) { - //filter msg if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable) { try { - if (!Regex.IsMatch(msg, MsgFilter)) - { - return; - } + if (!Regex.IsMatch(msg, MsgFilter)) return; } catch (Exception ex) { @@ -91,26 +74,17 @@ public class MsgViewModel : MyReactiveObject } } - //Enqueue - if (_queueMsg.Count > _numMaxMsg) - { - for (int k = 0; k < _queueMsg.Count - _numMaxMsg; k++) - { - _queueMsg.TryDequeue(out _); - } - } + while (_queueMsg.Count > _numMaxMsg) + _queueMsg.TryDequeue(out _); + _queueMsg.Enqueue(msg); if (!msg.EndsWith(Environment.NewLine)) - { _queueMsg.Enqueue(Environment.NewLine); - } + await Task.CompletedTask; } - public void ClearMsg() - { - _queueMsg.Clear(); - } + public void ClearMsg() => _queueMsg.Clear(); private void DoMsgFilter() { diff --git a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs index 7ce0ad07..9df2668e 100644 --- a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs @@ -5,12 +5,15 @@ using Avalonia.ReactiveUI; using Avalonia.Threading; using ReactiveUI; using v2rayN.Desktop.Common; +using System.Text; namespace v2rayN.Desktop.Views; public partial class MsgView : ReactiveUserControl { private readonly ScrollViewer _scrollViewer; + private const int _maxLines = 320; // 实际保留的行数 + private const int _trimLines = 350; // 超过此阈值时裁剪 private int _lastShownLength = 0; public MsgView() @@ -32,8 +35,7 @@ public partial class MsgView : ReactiveUserControl switch (action) { case EViewAction.DispatcherShowMsg: - if (obj is null) - return false; + if (obj is null) return false; Dispatcher.UIThread.Post(() => ShowMsg(obj), @@ -46,16 +48,20 @@ public partial class MsgView : ReactiveUserControl private void ShowMsg(object msg) { var newText = msg?.ToString() ?? string.Empty; + txtMsg.Text += newText; - if (txtMsg.Text is { } old && newText.Length >= _lastShownLength && newText.AsSpan(0, _lastShownLength).SequenceEqual(old)) + var lines = txtMsg.Text.Split(Environment.NewLine); + if (lines.Length > _trimLines) { - var delta = newText.AsSpan(_lastShownLength); - if (!delta.IsEmpty) - txtMsg.Text += delta.ToString(); - } - else - { - txtMsg.Text = newText; + var sb = new StringBuilder(); + int start = lines.Length - _maxLines; + for (int i = start; i < lines.Length; i++) + { + sb.Append(lines[i]); + if (i < lines.Length - 1) + sb.Append(Environment.NewLine); + } + txtMsg.Text = sb.ToString(); } _lastShownLength = txtMsg.Text.Length; @@ -70,6 +76,7 @@ public partial class MsgView : ReactiveUserControl { ViewModel?.ClearMsg(); txtMsg.Text = ""; + _lastShownLength = 0; } private void menuMsgViewSelectAll_Click(object? sender, RoutedEventArgs e) diff --git a/v2rayN/v2rayN/Views/MsgView.xaml.cs b/v2rayN/v2rayN/Views/MsgView.xaml.cs index 3cc1fdfa..a905c729 100644 --- a/v2rayN/v2rayN/Views/MsgView.xaml.cs +++ b/v2rayN/v2rayN/Views/MsgView.xaml.cs @@ -1,4 +1,5 @@ using System.Reactive.Disposables; +using System.Text; using System.Windows; using System.Windows.Threading; using ReactiveUI; @@ -7,6 +8,9 @@ namespace v2rayN.Views; public partial class MsgView { + private const int _maxLines = 320; + private const int _trimLines = 350; + public MsgView() { InitializeComponent(); @@ -47,12 +51,52 @@ public partial class MsgView private void ShowMsg(object msg) { + var incoming = msg?.ToString() ?? string.Empty; + var old = txtMsg.Text ?? string.Empty; + txtMsg.BeginChange(); - txtMsg.Text = msg.ToString(); + + if (incoming.Length >= old.Length && incoming.AsSpan(0, old.Length).SequenceEqual(old)) + { + var delta = incoming.AsSpan(old.Length); + if (!delta.IsEmpty) + txtMsg.AppendText(delta.ToString()); + } + else + { + // 兼容增量:如果不是全量覆盖场景,直接把 incoming 当作增量追加 + if (old.Length == 0) + { + txtMsg.Text = incoming; + } + else + { + txtMsg.AppendText(incoming); + } + } + + // 行数超过阈值才裁剪到 _maxLines + var lines = txtMsg.Text.Split(Environment.NewLine); + if (lines.Length > _trimLines) + { + var start = lines.Length - _maxLines; + if (start < 0) start = 0; + + var sb = new StringBuilder(); + for (int i = start; i < lines.Length; i++) + { + sb.Append(lines[i]); + if (i < lines.Length - 1) + sb.Append(Environment.NewLine); + } + txtMsg.Text = sb.ToString(); + } + if (togScrollToEnd.IsChecked ?? true) { txtMsg.ScrollToEnd(); } + txtMsg.EndChange(); }