加速写入wpf中的文本框并滚动到结束?
speed up write to texbox in wpf with scroll to end?
我一直在寻找一种在 C#/WPF 中尽可能快地更新多行文本框的方法。所以我想我会和你们核实一下,看看你们是否可以给我一些 pointers/hints 或帮助。
我想要的是能够尽可能快地将尽可能多的数据输出到多行文本框,并在更新时滚动到结束,数据来自串行连接。我已经在 C#/Forms 中完成了这个,没有任何问题。但是在 WPF 中就没那么容易了。
所以如果我只写一行文本框,那么 5000 项需要 940 毫秒。
但对于多行,它需要 143 秒。
如果我不滚动到文本框的末尾,那么相同的数据需要 112 秒。
代码创建一个带有队列的 class 实例,用 5000 个项目填充队列,然后尝试尽快将数据输出到 UI->texbox。
我在输出中添加了秒表刻度,这样我就可以看到每个函数的时间。
显示的输出格式
ItemValue DQ-> 从开始添加到队列标记 Ev-> 从开始移除事件标记 TB- > 文本框从开始输出时间(ms),
我整理了一个小测试程序,下面的代码与可以在这里下载的项目相同-> Link to project
测试用例 if 语句在 MainWindow:TxTextAdd()
我的想法是,有一种方法可以更改事件处理,以便它可以在每次 ui/event 处理程序触发时输出尽可能多的项目。所以我们可以一次打印多个项目而不是每个事件一个?
但我不知道如何实施该解决方案。
有什么想法吗?
enter
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
using System.Windows.Threading;
namespace BlockingCollectionAsFIFO
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
/// Create FIFO class
FIFO_Class FIFOqueue = new FIFO_Class();
// Display stopwatch
Stopwatch DispStop = Stopwatch.StartNew();
// Holds texbox string in one of the tests
string TextBox_Text = "";
public MainWindow()
{
InitializeComponent();
// register UI update event to populate textbox from queue dequeue
FIFOqueue.UI_Update += new FIFO_Class.UI_EventHandler(TxTextAdd);
string ID = "A";
/// Create new add thread in class
for (int i = 0; i < 5; i++)
{
if(i == 1)
{
ID = "B";
}
else if(i == 2)
{
ID = "C";
}
else if (i == 3)
{
ID = "D";
}
else if (i == 4)
{
ID = "E";
}
// create thread
FIFOqueue.CreateAddThread(ID);
}
/// Create new read thread in class
FIFOqueue.CreateReadThread();
}
public void TxTextAdd(string Data)
{
Dispatcher.BeginInvoke(DispatcherPriority.Input, new ThreadStart(() =>
{
try
{
/// Put togheter the string that we are going to display
Data += " TB->" + DispStop.ElapsedMilliseconds.ToString() + "ms";
/// true => only update the one row texbox with the latest string
if(true)
{
textbox2.Text = Data;
}
else
{
/// true => add string to TextBox_Text variable and display that (scroll to end)
/// false => add string to TextBox control and scroll to end
if (true)
{
TextBox_Text += Data + System.Environment.NewLine;
textbox.Text = TextBox_Text;
}
else
{
textbox.Text += Data + System.Environment.NewLine;
textbox.ScrollToEnd();
}
}
}
catch
{}
}));
}
private void btn_RUN_Click(object sender, RoutedEventArgs e)
{
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
/// Dispose alive dequeue thread
FIFOqueue.Dispose();
}
}
class FIFO_Class
{
// Main Queue
private BlockingCollection<Package> queue = new BlockingCollection<Package>(new ConcurrentQueue<Package>());
// Events
public delegate void UI_EventHandler(string message);
public event UI_EventHandler UI_Update;
private Thread DeQueueThread;
Stopwatch stop = Stopwatch.StartNew();
public void CreateAddThread(string ID)
{
Thread t = new Thread(() => Add(ID));
t.Start();
}
private void Add(string ID)
{
/// Just add some data
for (int i = 0; i < 1000; i++)
{
Package pack = new Package();
pack.ID = ID;
pack.Data = i;
/// Add pack into queue
queue.Add(pack);
}
}
public void CreateReadThread()
{
DeQueueThread = new Thread(ProcessPackets);
DeQueueThread.Start();
}
private void ProcessPackets()
{
string Output;
/// the try function exist so I can use abort thread
try
{
while (true)
{
/// Retrive a package from queue
Package e = queue.Take();
// check if there is any data in package
if (e != null)
{
/// Convert data from package to string, stopwatch puts number of ticks from the time the thread was created
Output = e.ID + e.Data.ToString() + " DQ-> " + stop.ElapsedTicks.ToString() + " ticks";
/// check if we have any subscribers on UIupdate event
UI_Update_Process(Output);
}
}
}
catch (ThreadAbortException)
{
}
}
public void Dispose()
{
// Abort Thread.
DeQueueThread.Abort();
// Wait for the thread to terminate.
DeQueueThread.Join();
}
// UI update Handler Event process
protected void UI_Update_Process(string Data)
{
if (UI_Update != null)
{
//UI_Update(Data);
UI_Update(Data + " Ev->" + stop.ElapsedTicks.ToString() + "ticks");
}
}
}
class Package
{
public string ID;
public int Data;
}
}
对于日志:
- Store/save文件中的日志。
- 在文本框旁边放置一个滚动条,并将其最大值设置为日志中的行数
- 每次滚动条更改时,都会从日志中获取正确的行,这些行将在文本框中可见(仅此而已)。
每次日志增长都会更改滚动条的最大值。
- 这样你就可以尽可能多地虚拟化文本框
- 您可以在 textbox/scrollbar 和日志文件之间放置一个缓冲区,以减少对日志的读取操作次数。
- 您不需要向文本框添加 500 行;大多数文本框不会同时显示那么多行。
我一直在寻找一种在 C#/WPF 中尽可能快地更新多行文本框的方法。所以我想我会和你们核实一下,看看你们是否可以给我一些 pointers/hints 或帮助。
我想要的是能够尽可能快地将尽可能多的数据输出到多行文本框,并在更新时滚动到结束,数据来自串行连接。我已经在 C#/Forms 中完成了这个,没有任何问题。但是在 WPF 中就没那么容易了。
所以如果我只写一行文本框,那么 5000 项需要 940 毫秒。 但对于多行,它需要 143 秒。 如果我不滚动到文本框的末尾,那么相同的数据需要 112 秒。
代码创建一个带有队列的 class 实例,用 5000 个项目填充队列,然后尝试尽快将数据输出到 UI->texbox。 我在输出中添加了秒表刻度,这样我就可以看到每个函数的时间。
显示的输出格式
ItemValue DQ-> 从开始添加到队列标记 Ev-> 从开始移除事件标记 TB- > 文本框从开始输出时间(ms),
我整理了一个小测试程序,下面的代码与可以在这里下载的项目相同-> Link to project
测试用例 if 语句在 MainWindow:TxTextAdd()
我的想法是,有一种方法可以更改事件处理,以便它可以在每次 ui/event 处理程序触发时输出尽可能多的项目。所以我们可以一次打印多个项目而不是每个事件一个? 但我不知道如何实施该解决方案。
有什么想法吗?
enter
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
using System.Windows.Threading;
namespace BlockingCollectionAsFIFO
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
/// Create FIFO class
FIFO_Class FIFOqueue = new FIFO_Class();
// Display stopwatch
Stopwatch DispStop = Stopwatch.StartNew();
// Holds texbox string in one of the tests
string TextBox_Text = "";
public MainWindow()
{
InitializeComponent();
// register UI update event to populate textbox from queue dequeue
FIFOqueue.UI_Update += new FIFO_Class.UI_EventHandler(TxTextAdd);
string ID = "A";
/// Create new add thread in class
for (int i = 0; i < 5; i++)
{
if(i == 1)
{
ID = "B";
}
else if(i == 2)
{
ID = "C";
}
else if (i == 3)
{
ID = "D";
}
else if (i == 4)
{
ID = "E";
}
// create thread
FIFOqueue.CreateAddThread(ID);
}
/// Create new read thread in class
FIFOqueue.CreateReadThread();
}
public void TxTextAdd(string Data)
{
Dispatcher.BeginInvoke(DispatcherPriority.Input, new ThreadStart(() =>
{
try
{
/// Put togheter the string that we are going to display
Data += " TB->" + DispStop.ElapsedMilliseconds.ToString() + "ms";
/// true => only update the one row texbox with the latest string
if(true)
{
textbox2.Text = Data;
}
else
{
/// true => add string to TextBox_Text variable and display that (scroll to end)
/// false => add string to TextBox control and scroll to end
if (true)
{
TextBox_Text += Data + System.Environment.NewLine;
textbox.Text = TextBox_Text;
}
else
{
textbox.Text += Data + System.Environment.NewLine;
textbox.ScrollToEnd();
}
}
}
catch
{}
}));
}
private void btn_RUN_Click(object sender, RoutedEventArgs e)
{
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
/// Dispose alive dequeue thread
FIFOqueue.Dispose();
}
}
class FIFO_Class
{
// Main Queue
private BlockingCollection<Package> queue = new BlockingCollection<Package>(new ConcurrentQueue<Package>());
// Events
public delegate void UI_EventHandler(string message);
public event UI_EventHandler UI_Update;
private Thread DeQueueThread;
Stopwatch stop = Stopwatch.StartNew();
public void CreateAddThread(string ID)
{
Thread t = new Thread(() => Add(ID));
t.Start();
}
private void Add(string ID)
{
/// Just add some data
for (int i = 0; i < 1000; i++)
{
Package pack = new Package();
pack.ID = ID;
pack.Data = i;
/// Add pack into queue
queue.Add(pack);
}
}
public void CreateReadThread()
{
DeQueueThread = new Thread(ProcessPackets);
DeQueueThread.Start();
}
private void ProcessPackets()
{
string Output;
/// the try function exist so I can use abort thread
try
{
while (true)
{
/// Retrive a package from queue
Package e = queue.Take();
// check if there is any data in package
if (e != null)
{
/// Convert data from package to string, stopwatch puts number of ticks from the time the thread was created
Output = e.ID + e.Data.ToString() + " DQ-> " + stop.ElapsedTicks.ToString() + " ticks";
/// check if we have any subscribers on UIupdate event
UI_Update_Process(Output);
}
}
}
catch (ThreadAbortException)
{
}
}
public void Dispose()
{
// Abort Thread.
DeQueueThread.Abort();
// Wait for the thread to terminate.
DeQueueThread.Join();
}
// UI update Handler Event process
protected void UI_Update_Process(string Data)
{
if (UI_Update != null)
{
//UI_Update(Data);
UI_Update(Data + " Ev->" + stop.ElapsedTicks.ToString() + "ticks");
}
}
}
class Package
{
public string ID;
public int Data;
}
}
对于日志:
- Store/save文件中的日志。
- 在文本框旁边放置一个滚动条,并将其最大值设置为日志中的行数
- 每次滚动条更改时,都会从日志中获取正确的行,这些行将在文本框中可见(仅此而已)。
每次日志增长都会更改滚动条的最大值。
- 这样你就可以尽可能多地虚拟化文本框
- 您可以在 textbox/scrollbar 和日志文件之间放置一个缓冲区,以减少对日志的读取操作次数。
- 您不需要向文本框添加 500 行;大多数文本框不会同时显示那么多行。