From e9daf26bcf4eb8706b3467631d23bb37915f5076 Mon Sep 17 00:00:00 2001 From: xujie86 <167618598+xujie86@users.noreply.github.com> Date: Thu, 25 Sep 2025 06:11:03 +0800 Subject: [PATCH 1/7] Update MsgViewModel.cs --- v2rayN/ServiceLib/ViewModels/MsgViewModel.cs | 74 ++++++-------------- 1 file changed, 23 insertions(+), 51 deletions(-) diff --git a/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs b/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs index 8fc62dfe..b71c45d2 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; @@ -13,11 +14,8 @@ public class MsgViewModel : MyReactiveObject private bool _lastMsgFilterNotAvailable; private bool _blLockShow = false; - [Reactive] - public string MsgFilter { get; set; } - - [Reactive] - public bool AutoRefresh { get; set; } + [Reactive] public string MsgFilter { get; set; } + [Reactive] public bool AutoRefresh { get; set; } public MsgViewModel(Func>? updateView) { @@ -26,63 +24,43 @@ 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) 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 sb = new StringBuilder(); + while (_queueMsg.TryDequeue(out var line)) + { + sb.Append(line); + } + if (sb.Length > 0) + await _updateView?.Invoke(EViewAction.DispatcherShowMsg, sb.ToString()); _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 +69,20 @@ public class MsgViewModel : MyReactiveObject } } - //Enqueue if (_queueMsg.Count > _numMaxMsg) { for (int k = 0; k < _queueMsg.Count - _numMaxMsg; k++) - { _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() { From d4d47b0d269a8f3efe19f50e806e18de006cb72e Mon Sep 17 00:00:00 2001 From: xujie86 <167618598+xujie86@users.noreply.github.com> Date: Thu, 25 Sep 2025 06:16:24 +0800 Subject: [PATCH 2/7] Update MsgView.axaml.cs --- v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs index 7ce0ad07..19c75685 100644 --- a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs @@ -47,7 +47,9 @@ public partial class MsgView : ReactiveUserControl { var newText = msg?.ToString() ?? string.Empty; - if (txtMsg.Text is { } old && newText.Length >= _lastShownLength && newText.AsSpan(0, _lastShownLength).SequenceEqual(old)) + if (txtMsg.Text is { } old && + newText.Length >= _lastShownLength && + newText.AsSpan(0, _lastShownLength).SequenceEqual(old)) { var delta = newText.AsSpan(_lastShownLength); if (!delta.IsEmpty) @@ -61,15 +63,18 @@ public partial class MsgView : ReactiveUserControl _lastShownLength = txtMsg.Text.Length; if (togScrollToEnd.IsChecked ?? true) + { Avalonia.Threading.Dispatcher.UIThread.Post( () => _scrollViewer?.ScrollToEnd(), Avalonia.Threading.DispatcherPriority.Render); + } } public void ClearMsg() { ViewModel?.ClearMsg(); txtMsg.Text = ""; + _lastShownLength = 0; } private void menuMsgViewSelectAll_Click(object? sender, RoutedEventArgs e) From 1b9d7be5538832afecf4e0c7ead99f62e41c162a Mon Sep 17 00:00:00 2001 From: xujie86 <167618598+xujie86@users.noreply.github.com> Date: Thu, 25 Sep 2025 06:31:37 +0800 Subject: [PATCH 3/7] Update MsgView.axaml.cs --- v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs | 192 ++++++++++++------- 1 file changed, 123 insertions(+), 69 deletions(-) diff --git a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs index 19c75685..73e1c83b 100644 --- a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs @@ -1,102 +1,156 @@ -using System.Reactive.Disposables; -using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.ReactiveUI; -using Avalonia.Threading; +using System.Collections.Concurrent; +using System.Reactive.Linq; +using System.Text; +using System.Text.RegularExpressions; using ReactiveUI; -using v2rayN.Desktop.Common; +using ReactiveUI.Fody.Helpers; -namespace v2rayN.Desktop.Views; +namespace ServiceLib.ViewModels; -public partial class MsgView : ReactiveUserControl +public class MsgViewModel : MyReactiveObject { - private readonly ScrollViewer _scrollViewer; - private int _lastShownLength = 0; + private readonly ConcurrentQueue _queueMsg = new(); + private readonly int _numMaxMsg = 500; + private bool _lastMsgFilterNotAvailable; + private bool _blLockShow = false; - public MsgView() + private readonly Queue _window = new(); + private readonly int _maxLines = 350; + + [Reactive] + public string MsgFilter { get; set; } + + [Reactive] + public bool AutoRefresh { get; set; } + + public MsgViewModel(Func>? updateView) { - InitializeComponent(); - _scrollViewer = this.FindControl("msgScrollViewer"); + _config = AppManager.Instance.Config; + _updateView = updateView; + MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty; + AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true; - ViewModel = new MsgViewModel(UpdateViewHandler); + this.WhenAnyValue(x => x.MsgFilter) + .Subscribe(_ => DoMsgFilter()); - this.WhenActivated(disposables => - { - this.Bind(ViewModel, vm => vm.MsgFilter, v => v.cmbMsgFilter.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables); - }); + this.WhenAnyValue(x => x.AutoRefresh, y => y == true) + .Subscribe(_ => { _config.MsgUIItem.AutoRefresh = AutoRefresh; }); + + AppEvents.SendMsgViewRequested + .AsObservable() + //.ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(async content => await AppendQueueMsg(content)); } - private async Task UpdateViewHandler(EViewAction action, object? obj) + private async Task AppendQueueMsg(string msg) { - switch (action) + //if (msg == Global.CommandClearMsg) + //{ + // ClearMsg(); + // return; + //} + if (AutoRefresh == false) { - case EViewAction.DispatcherShowMsg: - if (obj is null) - return false; - - Dispatcher.UIThread.Post(() => - ShowMsg(obj), - DispatcherPriority.ApplicationIdle); - break; + return; } - return await Task.FromResult(true); - } - private void ShowMsg(object msg) - { - var newText = msg?.ToString() ?? string.Empty; + _ = EnqueueQueueMsg(msg); - if (txtMsg.Text is { } old && - newText.Length >= _lastShownLength && - newText.AsSpan(0, _lastShownLength).SequenceEqual(old)) + if (_blLockShow) { - var delta = newText.AsSpan(_lastShownLength); - if (!delta.IsEmpty) - txtMsg.Text += delta.ToString(); + return; + } + if (!_config.UiItem.ShowInTaskbar) + { + return; + } + + _blLockShow = true; + + await Task.Delay(500); + + var sb = new StringBuilder(); + var needRebuild = false; + + while (_queueMsg.TryDequeue(out var line)) + { + _window.Enqueue(line); + if (_window.Count > _maxLines) + { + _window.Dequeue(); + needRebuild = true; + } + if (!needRebuild) + { + sb.Append(line); + } + } + + if (needRebuild) + { + var sbAll = new StringBuilder(); + foreach (var s in _window) + { + sbAll.Append(s); + } + await _updateView?.Invoke(EViewAction.DispatcherShowMsg, sbAll.ToString()); } else { - txtMsg.Text = newText; + if (sb.Length > 0) + { + await _updateView?.Invoke(EViewAction.DispatcherShowMsg, sb.ToString()); + } } - _lastShownLength = txtMsg.Text.Length; + _blLockShow = false; + } - if (togScrollToEnd.IsChecked ?? true) + private async Task EnqueueQueueMsg(string msg) + { + //filter msg + if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable) { - Avalonia.Threading.Dispatcher.UIThread.Post( - () => _scrollViewer?.ScrollToEnd(), - Avalonia.Threading.DispatcherPriority.Render); + try + { + if (!Regex.IsMatch(msg, MsgFilter)) + { + return; + } + } + catch (Exception ex) + { + _queueMsg.Enqueue(ex.Message); + _lastMsgFilterNotAvailable = true; + } } + + // 限制待处理队列规模,避免短时洪峰占用 + if (_queueMsg.Count > _numMaxMsg) + { + for (int k = 0; k < _queueMsg.Count - _numMaxMsg; k++) + { + _queueMsg.TryDequeue(out _); + } + } + + _queueMsg.Enqueue(msg); + if (!msg.EndsWith(Environment.NewLine)) + { + _queueMsg.Enqueue(Environment.NewLine); + } + await Task.CompletedTask; } public void ClearMsg() { - ViewModel?.ClearMsg(); - txtMsg.Text = ""; - _lastShownLength = 0; + _queueMsg.Clear(); + _window.Clear(); } - private void menuMsgViewSelectAll_Click(object? sender, RoutedEventArgs e) + private void DoMsgFilter() { - txtMsg.Focus(); - txtMsg.SelectAll(); - } - - private async void menuMsgViewCopy_Click(object? sender, RoutedEventArgs e) - { - var data = txtMsg.SelectedText.TrimEx(); - await AvaUtils.SetClipboardData(this, data); - } - - private async void menuMsgViewCopyAll_Click(object? sender, RoutedEventArgs e) - { - var data = txtMsg.Text.TrimEx(); - await AvaUtils.SetClipboardData(this, data); - } - - private void menuMsgViewClear_Click(object? sender, RoutedEventArgs e) - { - ClearMsg(); + _config.MsgUIItem.MainMsgFilter = MsgFilter; + _lastMsgFilterNotAvailable = false; } } From f9f45d7c53269ccb84fd4f7cd58da80b1a52a76b Mon Sep 17 00:00:00 2001 From: xujie86 <167618598+xujie86@users.noreply.github.com> Date: Thu, 25 Sep 2025 06:36:59 +0800 Subject: [PATCH 4/7] Update MsgView.axaml.cs --- v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs | 191 +++++++------------ 1 file changed, 66 insertions(+), 125 deletions(-) diff --git a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs index 73e1c83b..7ce0ad07 100644 --- a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs @@ -1,156 +1,97 @@ -using System.Collections.Concurrent; -using System.Reactive.Linq; -using System.Text; -using System.Text.RegularExpressions; +using System.Reactive.Disposables; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.ReactiveUI; +using Avalonia.Threading; using ReactiveUI; -using ReactiveUI.Fody.Helpers; +using v2rayN.Desktop.Common; -namespace ServiceLib.ViewModels; +namespace v2rayN.Desktop.Views; -public class MsgViewModel : MyReactiveObject +public partial class MsgView : ReactiveUserControl { - private readonly ConcurrentQueue _queueMsg = new(); - private readonly int _numMaxMsg = 500; - private bool _lastMsgFilterNotAvailable; - private bool _blLockShow = false; + private readonly ScrollViewer _scrollViewer; + private int _lastShownLength = 0; - private readonly Queue _window = new(); - private readonly int _maxLines = 350; - - [Reactive] - public string MsgFilter { get; set; } - - [Reactive] - public bool AutoRefresh { get; set; } - - public MsgViewModel(Func>? updateView) + public MsgView() { - _config = AppManager.Instance.Config; - _updateView = updateView; - MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty; - AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true; + InitializeComponent(); + _scrollViewer = this.FindControl("msgScrollViewer"); - this.WhenAnyValue(x => x.MsgFilter) - .Subscribe(_ => DoMsgFilter()); + ViewModel = new MsgViewModel(UpdateViewHandler); - this.WhenAnyValue(x => x.AutoRefresh, y => y == true) - .Subscribe(_ => { _config.MsgUIItem.AutoRefresh = AutoRefresh; }); - - AppEvents.SendMsgViewRequested - .AsObservable() - //.ObserveOn(RxApp.MainThreadScheduler) - .Subscribe(async content => await AppendQueueMsg(content)); + this.WhenActivated(disposables => + { + this.Bind(ViewModel, vm => vm.MsgFilter, v => v.cmbMsgFilter.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables); + }); } - private async Task AppendQueueMsg(string msg) + private async Task UpdateViewHandler(EViewAction action, object? obj) { - //if (msg == Global.CommandClearMsg) - //{ - // ClearMsg(); - // return; - //} - if (AutoRefresh == false) + switch (action) { - return; + case EViewAction.DispatcherShowMsg: + if (obj is null) + return false; + + Dispatcher.UIThread.Post(() => + ShowMsg(obj), + DispatcherPriority.ApplicationIdle); + break; } + return await Task.FromResult(true); + } - _ = EnqueueQueueMsg(msg); + private void ShowMsg(object msg) + { + var newText = msg?.ToString() ?? string.Empty; - if (_blLockShow) + if (txtMsg.Text is { } old && newText.Length >= _lastShownLength && newText.AsSpan(0, _lastShownLength).SequenceEqual(old)) { - return; - } - if (!_config.UiItem.ShowInTaskbar) - { - return; - } - - _blLockShow = true; - - await Task.Delay(500); - - var sb = new StringBuilder(); - var needRebuild = false; - - while (_queueMsg.TryDequeue(out var line)) - { - _window.Enqueue(line); - if (_window.Count > _maxLines) - { - _window.Dequeue(); - needRebuild = true; - } - if (!needRebuild) - { - sb.Append(line); - } - } - - if (needRebuild) - { - var sbAll = new StringBuilder(); - foreach (var s in _window) - { - sbAll.Append(s); - } - await _updateView?.Invoke(EViewAction.DispatcherShowMsg, sbAll.ToString()); + var delta = newText.AsSpan(_lastShownLength); + if (!delta.IsEmpty) + txtMsg.Text += delta.ToString(); } else { - if (sb.Length > 0) - { - await _updateView?.Invoke(EViewAction.DispatcherShowMsg, sb.ToString()); - } + txtMsg.Text = newText; } - _blLockShow = false; - } + _lastShownLength = txtMsg.Text.Length; - private async Task EnqueueQueueMsg(string msg) - { - //filter msg - if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable) - { - try - { - if (!Regex.IsMatch(msg, MsgFilter)) - { - return; - } - } - catch (Exception ex) - { - _queueMsg.Enqueue(ex.Message); - _lastMsgFilterNotAvailable = true; - } - } - - // 限制待处理队列规模,避免短时洪峰占用 - if (_queueMsg.Count > _numMaxMsg) - { - for (int k = 0; k < _queueMsg.Count - _numMaxMsg; k++) - { - _queueMsg.TryDequeue(out _); - } - } - - _queueMsg.Enqueue(msg); - if (!msg.EndsWith(Environment.NewLine)) - { - _queueMsg.Enqueue(Environment.NewLine); - } - await Task.CompletedTask; + if (togScrollToEnd.IsChecked ?? true) + Avalonia.Threading.Dispatcher.UIThread.Post( + () => _scrollViewer?.ScrollToEnd(), + Avalonia.Threading.DispatcherPriority.Render); } public void ClearMsg() { - _queueMsg.Clear(); - _window.Clear(); + ViewModel?.ClearMsg(); + txtMsg.Text = ""; } - private void DoMsgFilter() + private void menuMsgViewSelectAll_Click(object? sender, RoutedEventArgs e) { - _config.MsgUIItem.MainMsgFilter = MsgFilter; - _lastMsgFilterNotAvailable = false; + txtMsg.Focus(); + txtMsg.SelectAll(); + } + + private async void menuMsgViewCopy_Click(object? sender, RoutedEventArgs e) + { + var data = txtMsg.SelectedText.TrimEx(); + await AvaUtils.SetClipboardData(this, data); + } + + private async void menuMsgViewCopyAll_Click(object? sender, RoutedEventArgs e) + { + var data = txtMsg.Text.TrimEx(); + await AvaUtils.SetClipboardData(this, data); + } + + private void menuMsgViewClear_Click(object? sender, RoutedEventArgs e) + { + ClearMsg(); } } From 6d45efc9e61ac4a35491c4ca93cacc667d0e1587 Mon Sep 17 00:00:00 2001 From: xujie86 <167618598+xujie86@users.noreply.github.com> Date: Thu, 25 Sep 2025 06:56:42 +0800 Subject: [PATCH 5/7] Update MsgViewModel.cs --- v2rayN/ServiceLib/ViewModels/MsgViewModel.cs | 28 +++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs b/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs index b71c45d2..d1a58ea9 100644 --- a/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs @@ -10,12 +10,15 @@ 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; - [Reactive] public string MsgFilter { get; set; } - [Reactive] public bool AutoRefresh { get; set; } + [Reactive] + public string MsgFilter { get; set; } + + [Reactive] + public bool AutoRefresh { get; set; } public MsgViewModel(Func>? updateView) { @@ -35,7 +38,7 @@ public class MsgViewModel : MyReactiveObject private async Task AppendQueueMsg(string msg) { - if (!AutoRefresh) return; + if (AutoRefresh == false) return; await EnqueueQueueMsg(msg); if (_blLockShow || !_config.UiItem.ShowInTaskbar) return; @@ -43,13 +46,15 @@ public class MsgViewModel : MyReactiveObject await Task.Delay(500); - var sb = new StringBuilder(); + var sbDelta = new StringBuilder(); while (_queueMsg.TryDequeue(out var line)) { - sb.Append(line); + sbDelta.Append(line); } - if (sb.Length > 0) - await _updateView?.Invoke(EViewAction.DispatcherShowMsg, sb.ToString()); + + var delta = sbDelta.ToString(); + if (delta.Length > 0) + await _updateView?.Invoke(EViewAction.DispatcherShowMsg, delta); _blLockShow = false; } @@ -69,11 +74,8 @@ public class MsgViewModel : MyReactiveObject } } - 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)) From fafdbb42caea225986a73b6776f55efb63a55395 Mon Sep 17 00:00:00 2001 From: xujie86 <167618598+xujie86@users.noreply.github.com> Date: Thu, 25 Sep 2025 06:57:58 +0800 Subject: [PATCH 6/7] Update MsgView.axaml.cs --- v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs | 27 ++++++++++++-------- 1 file changed, 17 insertions(+), 10 deletions(-) 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) From fff4c110ae020937c4027c1582156b64ccc07874 Mon Sep 17 00:00:00 2001 From: xujie86 <167618598+xujie86@users.noreply.github.com> Date: Thu, 25 Sep 2025 07:16:03 +0800 Subject: [PATCH 7/7] Update MsgView.xaml.cs --- v2rayN/v2rayN/Views/MsgView.xaml.cs | 46 ++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) 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(); }