C# 作为WPF中日志的只读文本框的有效替代方案

C# 作为WPF中日志的只读文本框的有效替代方案,c#,wpf,logging,C#,Wpf,Logging,我的应用程序以数据绑定的只读文本框为特色,以捕获和显示其“记录”的活动。每次记录某个内容时,都会将其连接到绑定字符串。这对于有限数量的日志文本来说已经足够好了,但随着文本数量的增加,它(可以理解)会陷入停滞。我在前面的问题中看到了这个建议:以及使用列表框的建议。我可以这样做,但我会失去一个很好的功能——允许用户选择和复制任意文本块。是否有其他解决方案?您可以使用列表框,并允许用户使用一些项目模板复制部分日志: <ListBox Name="viewList"> <Lis

我的应用程序以数据绑定的只读
文本框
为特色,以捕获和显示其“记录”的活动。每次记录某个内容时,都会将其连接到绑定字符串。这对于有限数量的日志文本来说已经足够好了,但随着文本数量的增加,它(可以理解)会陷入停滞。我在前面的问题中看到了这个建议:以及使用
列表框的建议。我可以这样做,但我会失去一个很好的功能——允许用户选择和复制任意文本块。是否有其他解决方案?

您可以使用
列表框
,并允许用户使用一些
项目模板
复制部分日志:

<ListBox Name="viewList">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBox Text="{Binding Mode=OneWay}" IsReadOnly="True"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

然后用一些可观察的集合填充它:

ObservableCollection<string> mvList = new ObservableCollection<string>();
viewList.ItemsSource = mvList;
ObservableCollection mvList=新的ObservableCollection();
viewList.ItemsSource=mvList;

好消息:列表框
会自动实现一些虚拟化,以确保非常长的列表具有良好的性能

您可以使用
列表框
,并允许用户使用一些
项目模板
复制部分日志:

<ListBox Name="viewList">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBox Text="{Binding Mode=OneWay}" IsReadOnly="True"/>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

然后用一些可观察的集合填充它:

ObservableCollection<string> mvList = new ObservableCollection<string>();
viewList.ItemsSource = mvList;
ObservableCollection mvList=新的ObservableCollection();
viewList.ItemsSource=mvList;

好消息:列表框会自动实现一些虚拟化,以确保非常长的列表具有良好的性能

问题简单地解决了

我的新用户控件的XAML:

<UserControl x:Class="DbHelper.Controls.LogControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <TextBox x:Name="TextBox" FontFamily="Consolas" FontSize="12" IsReadOnly="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" TextWrapping="Wrap"></TextBox>
</UserControl>

。。。及其背后的代码:

public partial class LogControl : UserControl
{
    private readonly Queue<string> _logQueue = new Queue<string>();
    private Timer _timer;
    private bool _synced;

    public int MaxLines { get; set; } = 1000;

    public LogControl()
    {
        _timer = new Timer(state => ((LogControl)state).Refresh(), this, 1000, 1000);
        InitializeComponent();
    }

    private void Refresh()
    {
        lock (_logQueue)
        {
            if (!_synced)
            {
                var sb = new StringBuilder();
                foreach (var line in _logQueue)
                {
                    sb.AppendLine(line);
                }

                Dispatcher.Invoke(() =>
                {
                    TextBox.Text = sb.ToString();
                    TextBox.ScrollToEnd();
                });
                _synced = true;
            }
        }
    }

    public void Log(string str)
    {
        lock (_logQueue)
        {
            _logQueue.Enqueue(str);
            while (_logQueue.Count > MaxLines)
            {
                _logQueue.Dequeue();
            }
            _synced = false;
        }
    }

    public void Clear()
    {
        lock (_logQueue)
        {
            _logQueue.Clear();
            _synced = false;
        }
    }
}
公共部分类LogControl:UserControl
{
私有只读队列_logQueue=新队列();
私人定时器;
已同步私人布尔;
公共整数MaxLines{get;set;}=1000;
公共后勤管制()
{
_timer=newtimer(state=>((LogControl)state).Refresh(),this,10001000);
初始化组件();
}
私有无效刷新()
{
锁定(_logQueue)
{
如果(!\u已同步)
{
var sb=新的StringBuilder();
foreach(日志队列中的var行)
{
给某人加上一行(一行);
}
Dispatcher.Invoke(()=>
{
TextBox.Text=sb.ToString();
TextBox.ScrollToEnd();
});
_synced=true;
}
}
}
公共无效日志(字符串str)
{
锁定(_logQueue)
{
_logQueue.Enqueue(str);
而(_logQueue.Count>MaxLines)
{
_logQueue.Dequeue();
}
_同步=假;
}
}
公共空间清除()
{
锁定(_logQueue)
{
_logQueue.Clear();
_同步=假;
}
}
}

问题已简单解决

我的新用户控件的XAML:

<UserControl x:Class="DbHelper.Controls.LogControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <TextBox x:Name="TextBox" FontFamily="Consolas" FontSize="12" IsReadOnly="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" TextWrapping="Wrap"></TextBox>
</UserControl>

。。。及其背后的代码:

public partial class LogControl : UserControl
{
    private readonly Queue<string> _logQueue = new Queue<string>();
    private Timer _timer;
    private bool _synced;

    public int MaxLines { get; set; } = 1000;

    public LogControl()
    {
        _timer = new Timer(state => ((LogControl)state).Refresh(), this, 1000, 1000);
        InitializeComponent();
    }

    private void Refresh()
    {
        lock (_logQueue)
        {
            if (!_synced)
            {
                var sb = new StringBuilder();
                foreach (var line in _logQueue)
                {
                    sb.AppendLine(line);
                }

                Dispatcher.Invoke(() =>
                {
                    TextBox.Text = sb.ToString();
                    TextBox.ScrollToEnd();
                });
                _synced = true;
            }
        }
    }

    public void Log(string str)
    {
        lock (_logQueue)
        {
            _logQueue.Enqueue(str);
            while (_logQueue.Count > MaxLines)
            {
                _logQueue.Dequeue();
            }
            _synced = false;
        }
    }

    public void Clear()
    {
        lock (_logQueue)
        {
            _logQueue.Clear();
            _synced = false;
        }
    }
}
公共部分类LogControl:UserControl
{
私有只读队列_logQueue=新队列();
私人定时器;
已同步私人布尔;
公共整数MaxLines{get;set;}=1000;
公共后勤管制()
{
_timer=newtimer(state=>((LogControl)state).Refresh(),this,10001000);
初始化组件();
}
私有无效刷新()
{
锁定(_logQueue)
{
如果(!\u已同步)
{
var sb=新的StringBuilder();
foreach(日志队列中的var行)
{
给某人加上一行(一行);
}
Dispatcher.Invoke(()=>
{
TextBox.Text=sb.ToString();
TextBox.ScrollToEnd();
});
_synced=true;
}
}
}
公共无效日志(字符串str)
{
锁定(_logQueue)
{
_logQueue.Enqueue(str);
而(_logQueue.Count>MaxLines)
{
_logQueue.Dequeue();
}
_同步=假;
}
}
公共空间清除()
{
锁定(_logQueue)
{
_logQueue.Clear();
_同步=假;
}
}
}

您应该设置日志中要保留多少文本的限制,并在应用程序运行时开始删除这些文本。所有日志历史记录都应该写入一个文本文件,以便用户复制它在什么时候停止(在多少行上)?要求是什么(要显示的最大行数)?@Evk有点主观,但随着字符串长度达到大约100k(不确定有多少行,可能只有1000行左右),它开始变得明显。如果它仍然可以使用几行megs-10000-20000行,那就太好了。您应该设置日志中要保留的文本数量的限制,并在应用程序运行时开始删除它们。所有日志历史记录都应该写入一个文本文件,以便用户复制它在什么时候停止(在多少行上)?要求是什么(要显示的最大行数)?@Evk有点主观,但随着字符串长度达到大约100k(不确定有多少行,可能只有1000行左右),它开始变得明显。如果它仍然可以处理几个MEG-10000-20000行,那就太好了。我在我引用的答案中看到了解决方案,但它没有做到我的目标是让日志可以任意选择,就像它是一个大文本块一样。当然,你可以进行多行选择,但这与我的目标是完全不同的用户体验。我在参考答案中看到了解决方案,但它没有做到我的目标是让日志可以任意选择,就像它是一个大文本块一样。当然,你可以进行多行选择,但这与我的目标是完全不同的用户体验。