Merge pull request #2 from xujie86/master

Update
This commit is contained in:
JieXu 2025-09-25 07:22:01 +08:00 committed by GitHub
commit 406dc3a297
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 88 additions and 63 deletions

View file

@ -1,5 +1,6 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Fody.Helpers; using ReactiveUI.Fody.Helpers;
@ -9,7 +10,7 @@ namespace ServiceLib.ViewModels;
public class MsgViewModel : MyReactiveObject public class MsgViewModel : MyReactiveObject
{ {
private readonly ConcurrentQueue<string> _queueMsg = new(); private readonly ConcurrentQueue<string> _queueMsg = new();
private readonly int _numMaxMsg = 500; private readonly int _numMaxMsg = 500; // 仅用于限制队列,不做显示裁剪
private bool _lastMsgFilterNotAvailable; private bool _lastMsgFilterNotAvailable;
private bool _blLockShow = false; private bool _blLockShow = false;
@ -26,63 +27,45 @@ public class MsgViewModel : MyReactiveObject
MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty; MsgFilter = _config.MsgUIItem.MainMsgFilter ?? string.Empty;
AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true; AutoRefresh = _config.MsgUIItem.AutoRefresh ?? true;
this.WhenAnyValue( this.WhenAnyValue(x => x.MsgFilter).Subscribe(c => DoMsgFilter());
x => x.MsgFilter) this.WhenAnyValue(x => x.AutoRefresh, y => y == true)
.Subscribe(c => DoMsgFilter()); .Subscribe(c => { _config.MsgUIItem.AutoRefresh = AutoRefresh; });
this.WhenAnyValue(
x => x.AutoRefresh,
y => y == true)
.Subscribe(c => { _config.MsgUIItem.AutoRefresh = AutoRefresh; });
AppEvents.SendMsgViewRequested AppEvents.SendMsgViewRequested
.AsObservable() .AsObservable()
//.ObserveOn(RxApp.MainThreadScheduler) .Subscribe(async content => await AppendQueueMsg(content));
.Subscribe(async content => await AppendQueueMsg(content));
} }
private async Task AppendQueueMsg(string msg) private async Task AppendQueueMsg(string msg)
{ {
//if (msg == Global.CommandClearMsg) if (AutoRefresh == false) return;
//{ await EnqueueQueueMsg(msg);
// ClearMsg();
// return;
//}
if (AutoRefresh == false)
{
return;
}
_ = EnqueueQueueMsg(msg);
if (_blLockShow)
{
return;
}
if (!_config.UiItem.ShowInTaskbar)
{
return;
}
if (_blLockShow || !_config.UiItem.ShowInTaskbar) return;
_blLockShow = true; _blLockShow = true;
await Task.Delay(500); 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; _blLockShow = false;
} }
private async Task EnqueueQueueMsg(string msg) private async Task EnqueueQueueMsg(string msg)
{ {
//filter msg
if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable) if (MsgFilter.IsNotEmpty() && !_lastMsgFilterNotAvailable)
{ {
try try
{ {
if (!Regex.IsMatch(msg, MsgFilter)) if (!Regex.IsMatch(msg, MsgFilter)) return;
{
return;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -91,26 +74,17 @@ public class MsgViewModel : MyReactiveObject
} }
} }
//Enqueue while (_queueMsg.Count > _numMaxMsg)
if (_queueMsg.Count > _numMaxMsg) _queueMsg.TryDequeue(out _);
{
for (int k = 0; k < _queueMsg.Count - _numMaxMsg; k++)
{
_queueMsg.TryDequeue(out _);
}
}
_queueMsg.Enqueue(msg); _queueMsg.Enqueue(msg);
if (!msg.EndsWith(Environment.NewLine)) if (!msg.EndsWith(Environment.NewLine))
{
_queueMsg.Enqueue(Environment.NewLine); _queueMsg.Enqueue(Environment.NewLine);
}
await Task.CompletedTask; await Task.CompletedTask;
} }
public void ClearMsg() public void ClearMsg() => _queueMsg.Clear();
{
_queueMsg.Clear();
}
private void DoMsgFilter() private void DoMsgFilter()
{ {

View file

@ -5,12 +5,15 @@ using Avalonia.ReactiveUI;
using Avalonia.Threading; using Avalonia.Threading;
using ReactiveUI; using ReactiveUI;
using v2rayN.Desktop.Common; using v2rayN.Desktop.Common;
using System.Text;
namespace v2rayN.Desktop.Views; namespace v2rayN.Desktop.Views;
public partial class MsgView : ReactiveUserControl<MsgViewModel> public partial class MsgView : ReactiveUserControl<MsgViewModel>
{ {
private readonly ScrollViewer _scrollViewer; private readonly ScrollViewer _scrollViewer;
private const int _maxLines = 320; // 实际保留的行数
private const int _trimLines = 350; // 超过此阈值时裁剪
private int _lastShownLength = 0; private int _lastShownLength = 0;
public MsgView() public MsgView()
@ -32,8 +35,7 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
switch (action) switch (action)
{ {
case EViewAction.DispatcherShowMsg: case EViewAction.DispatcherShowMsg:
if (obj is null) if (obj is null) return false;
return false;
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
ShowMsg(obj), ShowMsg(obj),
@ -46,16 +48,20 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
private void ShowMsg(object msg) private void ShowMsg(object msg)
{ {
var newText = msg?.ToString() ?? string.Empty; 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); var sb = new StringBuilder();
if (!delta.IsEmpty) int start = lines.Length - _maxLines;
txtMsg.Text += delta.ToString(); for (int i = start; i < lines.Length; i++)
} {
else sb.Append(lines[i]);
{ if (i < lines.Length - 1)
txtMsg.Text = newText; sb.Append(Environment.NewLine);
}
txtMsg.Text = sb.ToString();
} }
_lastShownLength = txtMsg.Text.Length; _lastShownLength = txtMsg.Text.Length;
@ -70,6 +76,7 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
{ {
ViewModel?.ClearMsg(); ViewModel?.ClearMsg();
txtMsg.Text = ""; txtMsg.Text = "";
_lastShownLength = 0;
} }
private void menuMsgViewSelectAll_Click(object? sender, RoutedEventArgs e) private void menuMsgViewSelectAll_Click(object? sender, RoutedEventArgs e)

View file

@ -1,4 +1,5 @@
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Threading; using System.Windows.Threading;
using ReactiveUI; using ReactiveUI;
@ -7,6 +8,9 @@ namespace v2rayN.Views;
public partial class MsgView public partial class MsgView
{ {
private const int _maxLines = 320;
private const int _trimLines = 350;
public MsgView() public MsgView()
{ {
InitializeComponent(); InitializeComponent();
@ -47,12 +51,52 @@ public partial class MsgView
private void ShowMsg(object msg) private void ShowMsg(object msg)
{ {
var incoming = msg?.ToString() ?? string.Empty;
var old = txtMsg.Text ?? string.Empty;
txtMsg.BeginChange(); 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) if (togScrollToEnd.IsChecked ?? true)
{ {
txtMsg.ScrollToEnd(); txtMsg.ScrollToEnd();
} }
txtMsg.EndChange(); txtMsg.EndChange();
} }