Ответ 1
У RichTextBox есть метод AppendText, который выполняется быстро.
И он может обрабатывать большой текст хорошо.
Я считаю, что это лучшее, что вам нужно.
У меня есть программа, которая контролирует отладочные сообщения, и я попытался использовать TextBox и приложил к ней сообщения, но он не масштабируется очень хорошо и замедляется, когда число сообщений становится большим. Затем я попробовал ListBox, но прокрутка была привязана к вершине при добавлении новых сообщений. Он также не позволяет вырезать и вставлять, как это делает текстовое поле.
Что такое лучший способ реализовать консольный элемент, встроенный в окно winforms.
Изменить: Я все равно хотел бы добавить окно вывода, такое как визуальная студия, но поскольку я не могу найти простой способ, вот два решения, которые я использую. В дополнение к использованию RichTextBox, который работает, но вы должны его очищать время от времени. Я использую консоль, которую я вызываю. Вот небольшой класс оболочки, который я написал, чтобы справиться с этим.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace Con
{
class Ext_Console
{
static bool console_on = false;
public static void Show(bool on,string title)
{
console_on = on;
if (console_on)
{
AllocConsole();
Console.Title = title;
// use to change color
Console.BackgroundColor = System.ConsoleColor.White;
Console.ForegroundColor = System.ConsoleColor.Black;
}
else
{
FreeConsole();
}
}
public static void Write(string output)
{
if (console_on)
{
Console.Write(output);
}
}
public static void WriteLine(string output)
{
if (console_on)
{
Console.WriteLine(output);
}
}
[DllImport("kernel32.dll")]
public static extern Boolean AllocConsole();
[DllImport("kernel32.dll")]
public static extern Boolean FreeConsole();
}
}
// example calls
Ext_Console.Write("console output ");
Ext_Console.WriteLine("console output");
Ext_Console.Show(true,"Title of console");
У RichTextBox есть метод AppendText, который выполняется быстро.
И он может обрабатывать большой текст хорошо.
Я считаю, что это лучшее, что вам нужно.
Вы не можете просто добавлять элементы ведения журнала в элемент управления WinForms (ListBox или RichTextBox) - он в конечном итоге будет засорен и начнет замену на диск.
У меня была эта точная ошибка в какой-то момент. Решение, которое у меня было, состояло в том, чтобы периодически кликать список отображаемых сообщений. В псевдокоде это что-то вроде:
void AddLogMessage(String message)
{
list.Items.Add(message);
// DO: Append message to file as needed
// Clip the list
if (list.count > ListMaxSize)
{
list.Items.RemoveRange(0, list.Count - listMinSize);
}
// DO: Focus the last item on the list
}
ListMaxSize должен быть значительно больше, чем ListMinSize, поэтому обрезка не происходит слишком часто. ListMinSize - это количество последних сообщений, которые вам обычно нужно просматривать в списке ведения журнала.
Это просто псевдокод, на самом деле нет элемента RemoveRange в коллекции элементов ListBox (но есть в списке). Вы можете определить точный код.
Я делаю это в своих оконных программах на С# (WInforms или WPF) с помощью окна консоли Win32. У меня есть небольшой класс, который обертывает некоторые базовые API Win32, тонкий, я создаю консоль при запуске программы. Это просто пример: в "реальной жизни" вы использовали настройку или какую-то другую вещь, чтобы включать консоль только тогда, когда вам это нужно.
using System;
using System.Windows.Forms;
using Microsoft.Win32.SafeHandles;
using System.Diagnostics;
using MWin32Api;
namespace WFConsole
{
static class Program
{
static private SafeFileHandle ConsoleHandle;
/// <summary>
/// Initialize the Win32 console for this process.
/// </summary>
static private void InitWin32Console()
{
if ( !K32.AllocConsole() ) {
MessageBox.Show( "Cannot allocate console",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error );
return;
}
IntPtr handle = K32.CreateFile(
"CONOUT$", // name
K32.GENERIC_WRITE | K32.GENERIC_READ, // desired access
K32.FILE_SHARE_WRITE | K32.FILE_SHARE_READ, // share access
null, // no security attributes
K32.OPEN_EXISTING, // device already exists
0, // no flags or attributes
IntPtr.Zero ); // no template file.
ConsoleHandle = new SafeFileHandle( handle, true );
if ( ConsoleHandle.IsInvalid ) {
MessageBox.Show( "Cannot create diagnostic console",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error );
return;
}
//
// Set the console screen buffer and window to a reasonable size
// 1) set the screen buffer sizse
// 2) Get the maximum window size (in terms of characters)
// 3) set the window to be this size
//
const UInt16 conWidth = 256;
const UInt16 conHeight = 5000;
K32.Coord dwSize = new K32.Coord( conWidth, conHeight );
if ( !K32.SetConsoleScreenBufferSize( ConsoleHandle.DangerousGetHandle(), dwSize ) ) {
MessageBox.Show( "Can't get console screen buffer information.",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error );
return;
}
K32.Console_Screen_Buffer_Info SBInfo = new K32.Console_Screen_Buffer_Info();
if ( !K32.GetConsoleScreenBufferInfo( ConsoleHandle.DangerousGetHandle(), out SBInfo ) ) {
MessageBox.Show( "Can't get console screen buffer information.",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
return;
}
K32.Small_Rect sr; ;
sr.Left = 0;
sr.Top = 0;
sr.Right = 132 - 1;
sr.Bottom = 51 - 1;
if ( !K32.SetConsoleWindowInfo( ConsoleHandle.DangerousGetHandle(), true, ref sr ) ) {
MessageBox.Show( "Can't set console screen buffer information.",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error );
return;
}
IntPtr conHWND = K32.GetConsoleWindow();
if ( conHWND == IntPtr.Zero ) {
MessageBox.Show( "Can't get console window handle.",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error );
return;
}
if ( !U32.SetForegroundWindow( conHWND ) ) {
MessageBox.Show( "Can't set console window as foreground.",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error );
return;
}
K32.SetConsoleTitle( "Test - Console" );
Trace.Listeners.Add( new ConsoleTraceListener() );
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
InitWin32Console();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault( false );
Application.Run( new Main() );
}
}
}
using System;
using System.Runtime.InteropServices;
namespace MWin32Api
{
#region Kernel32 Functions
//--------------------------------------------------------------------------
/// <summary>
/// Functions in Kernel32.dll
/// </summary>
public sealed class K32
{
#region Data Structures, Types and Constants
//----------------------------------------------------------------------
// Data Structures, Types and Constants
//
[StructLayout( LayoutKind.Sequential )]
public class SecurityAttributes
{
public UInt32 nLength;
public UIntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[StructLayout( LayoutKind.Sequential, Pack = 1, Size = 4 )]
public struct Coord
{
public Coord( UInt16 tx, UInt16 ty )
{
x = tx;
y = ty;
}
public UInt16 x;
public UInt16 y;
}
[StructLayout( LayoutKind.Sequential, Pack = 1, Size = 8 )]
public struct Small_Rect
{
public Int16 Left;
public Int16 Top;
public Int16 Right;
public Int16 Bottom;
public Small_Rect( short tLeft, short tTop, short tRight, short tBottom )
{
Left = tLeft;
Top = tTop;
Right = tRight;
Bottom = tBottom;
}
}
[StructLayout( LayoutKind.Sequential, Pack = 1, Size = 24 )]
public struct Console_Screen_Buffer_Info
{
public Coord dwSize;
public Coord dwCursorPosition;
public UInt32 wAttributes;
public Small_Rect srWindow;
public Coord dwMaximumWindowSize;
}
public const int ZERO_HANDLE_VALUE = 0;
public const int INVALID_HANDLE_VALUE = -1;
#endregion
#region Console Functions
//----------------------------------------------------------------------
// Console Functions
//
[DllImport( "kernel32.dll", SetLastError = true )]
public static extern bool AllocConsole();
[DllImport( "kernel32.dll", SetLastError = true )]
public static extern bool SetConsoleScreenBufferSize(
IntPtr hConsoleOutput,
Coord dwSize );
[DllImport( "kernel32.dll", SetLastError = true )]
public static extern bool GetConsoleScreenBufferInfo(
IntPtr hConsoleOutput,
out Console_Screen_Buffer_Info lpConsoleScreenBufferInfo );
[DllImport( "kernel32.dll", SetLastError = true )]
public static extern bool SetConsoleWindowInfo(
IntPtr hConsoleOutput,
bool bAbsolute,
ref Small_Rect lpConsoleWindow );
[DllImport( "kernel32.dll", SetLastError = true )]
public static extern IntPtr GetConsoleWindow();
[DllImport( "kernel32.dll", SetLastError = true )]
public static extern bool SetConsoleTitle(
string Filename );
#endregion
#region Create File
//----------------------------------------------------------------------
// Create File
//
public const UInt32 CREATE_NEW = 1;
public const UInt32 CREATE_ALWAYS = 2;
public const UInt32 OPEN_EXISTING = 3;
public const UInt32 OPEN_ALWAYS = 4;
public const UInt32 TRUNCATE_EXISTING = 5;
public const UInt32 FILE_SHARE_READ = 1;
public const UInt32 FILE_SHARE_WRITE = 2;
public const UInt32 GENERIC_WRITE = 0x40000000;
public const UInt32 GENERIC_READ = 0x80000000;
[DllImport( "kernel32.dll", SetLastError = true )]
public static extern IntPtr CreateFile(
string Filename,
UInt32 DesiredAccess,
UInt32 ShareMode,
SecurityAttributes SecAttr,
UInt32 CreationDisposition,
UInt32 FlagsAndAttributes,
IntPtr TemplateFile );
#endregion
#region Win32 Miscelaneous
//----------------------------------------------------------------------
// Miscelaneous
//
[DllImport( "kernel32.dll" )]
public static extern bool CloseHandle( UIntPtr handle );
#endregion
//----------------------------------------------------------------------
private K32()
{
}
}
#endregion
//--------------------------------------------------------------------------
/// <summary>
/// Functions in User32.dll
/// </summary>
#region User32 Functions
public sealed class U32
{
[StructLayout( LayoutKind.Sequential )]
public struct Rect
{
public Int32 Left;
public Int32 Top;
public Int32 Right;
public Int32 Bottom;
public Rect( short tLeft, short tTop, short tRight, short tBottom )
{
Left = tLeft;
Top = tTop;
Right = tRight;
Bottom = tBottom;
}
}
[DllImport( "user32.dll" )]
public static extern bool GetWindowRect(
IntPtr hWnd,
[In][MarshalAs( UnmanagedType.LPStruct )]Rect lpRect );
[DllImport( "user32.dll", SetLastError = true )]
public static extern bool SetForegroundWindow(
IntPtr hWnd );
//----------------------------------------------------------------------
private U32()
{
}
} // U32 class
#endregion
} // MWin32Api namespace
У меня была эта сложная задача. Я решил это двумя разными способами: работать и выполнять будет при большой нагрузке. Один из способов - с помощью ListView. Добавление строки текста выглядит следующим образом:
ListViewItem itm = new ListViewItem();
itm.Text = txt;
this.listView1.Items.Add(itm);
this.listView1.EnsureVisible(listView1.Items.Count - 1);
Другим способом является использование DataGridView в виртуальном режиме. У меня нет такого кода, как удобно. Виртуальный режим - ваш друг.
EDIT: перечитайте, я вижу, что вы хотите, чтобы копирование/вставка работали. Может быть, элемент управления RichText работает нормально - не знаю, но если вы используете ListView или DataGrid, вам нужно будет сделать больше кода, чтобы заставить Copy/Paste работать.
установите выбранный индекс в списке для последнего элемента, чтобы он прокручивался до нижней части
также ограничьте количество элементов в списке на что-то разумное (удалите сверху, сохраните более поздние элементы), чтобы вы не пережевали всю вашу память
Ранее я использовал текстовое поле. Добавьте его в свою форму, установите для свойства Multipline значение true, полосы прокрутки - по вертикали. И, наконец, добавьте следующий код:
private void AddConsoleComment(string comment)
{
textBoxConsole.Text += comment + System.Environment.NewLine;
textBoxConsole.Select(textBoxConsole.Text.Length,0);
textBoxConsole.ScrollToCaret();
}
По существу, это добавление вашего комментария к существующему тексту, а также добавление перевода строки. И, наконец, выбирая последний бит текста длины = 0. ScrollToCaret заставляет текстовое поле прокручиваться вниз до места расположения курсора (в последней строке)
Надеюсь, что это поможет.
public class ConsoleTextBox: TextBox
{
private List<string> contents = new List<string>();
private const int MAX = 50;
public void WriteLine(string input)
{
if (contents.Count == MAX)
contents.RemoveAt(MAX-1);
contents.Insert(0, input);
Rewrite();
}
private void Rewrite()
{
var sb = new StringBuilder();
foreach (var s in contents)
{
sb.Append(s);
sb.Append(Environment.NewLine);
}
this.Text = sb.ToString();
}
}