如何向 ac# winforms 程序添加类似控制台的元素
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/252323/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I add a console like element to a c# winforms program
提问by Rex Logan
I have a program that monitors debug messages and I have tried using a TextBox and appended the messages to it but it doesn't scale very well and slows way down when the number of messages gets large. I then tried a ListBox but the scrolling was snapping to the top when appending new messages. It also doesn't allow for cut and paste like the text box does.
我有一个监视调试消息的程序,我尝试使用 TextBox 并将消息附加到它,但它不能很好地扩展并且在消息数量变大时减慢速度。然后我尝试了一个 ListBox,但是在附加新消息时滚动会捕捉到顶部。它也不允许像文本框那样进行剪切和粘贴。
What is a better way to implement a console like element embedded in a winforms window.
实现嵌入在 winforms 窗口中的类似控制台的元素的更好方法是什么。
Edit: I would still like to be able to embed a output window like visual studio but since I can't figure out an easy way here are the two solutions I use. In addition to using the RichTextBox which works but you have to clear it every now and then. I use a console that I pinvoke. Here is a little wrapper class that I wrote to handle this.
编辑:我仍然希望能够嵌入像visual studio这样的输出窗口,但由于我无法找到一种简单的方法,这是我使用的两种解决方案。除了使用有效的 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");
采纳答案by Ovidiu Pacurar
RichTextBox has an AppendText method that is fast.
And it can handle large text well.
I believe it is the best for what you need.
RichTextBox 有一个快速的 AppendText 方法。它可以很好地处理大文本。
我相信它最适合您的需求。
回答by Steven A. Lowe
set the selectedindex of the listbox to the last element to make it scroll to the bottom
将列表框的 selectedindex 设置为最后一个元素以使其滚动到底部
also, limit the number of items in the listbox to something reasonable (delete from the top, keep the later items) so you don't chew up all of your memory
此外,将列表框中的项目数量限制在合理范围内(从顶部删除,保留后面的项目),这样您就不会占用所有内存
回答by Ovidiu Pacurar
Ive previously used a textbox. Add it to your form, set Multipline property to true, Scrollbars to Vertical. And finally add this following code:
我以前使用过文本框。将其添加到您的表单中,将 Multipline 属性设置为 true,将 Scrollbars 设置为 Vertical。最后添加以下代码:
private void AddConsoleComment(string comment)
{
textBoxConsole.Text += comment + System.Environment.NewLine;
textBoxConsole.Select(textBoxConsole.Text.Length,0);
textBoxConsole.ScrollToCaret();
}
Essentially its adding your comment to the existing text, also appending a linefeed. And finally selecting last bit of text of length = 0. ScrollToCaret forces the textbox to scroll down to where the cursor is positioned (at the last line)
本质上是将您的评论添加到现有文本中,还附加了换行符。最后选择长度为 0 的文本的最后一位。 ScrollToCaret 强制文本框向下滚动到光标所在的位置(在最后一行)
Hope this helps.
希望这可以帮助。
回答by Corey Trager
I've had this exact challenge. I've solved it two different ways, both work and perform will under heavy load. One way is with a ListView. Adding a line of text is like this:
我遇到了这个确切的挑战。我已经解决了它两种不同的方法,工作和执行都将在重负载下进行。一种方法是使用 ListView。添加一行文字是这样的:
ListViewItem itm = new ListViewItem();
itm.Text = txt;
this.listView1.Items.Add(itm);
this.listView1.EnsureVisible(listView1.Items.Count - 1);
The other way is with a DataGridView in virtual mode. I don't have that code as handy. Virtual mode is your friend.
另一种方法是在虚拟模式下使用 DataGridView。我没有那个代码那么方便。虚拟模式是您的朋友。
EDIT: re-reading, I see you want copy/paste to work. Maybe the RichText control performs ok - don't know, but if you use the ListView or DataGrid, you'd have to do more coding to get Copy/Paste to work.
编辑:重新阅读,我看到您希望复制/粘贴工作。也许 RichText 控件执行正常 - 不知道,但如果您使用 ListView 或 DataGrid,则必须执行更多编码才能使复制/粘贴工作。
回答by Foredecker
I do this in my C# window programs (WInforms or WPF) using a Win32 console window. I have a small class that wraps some basic Win32 APIs, thin I create a console when the program begins. This is just an example: in 'real life' you'd use a setting or some other thing to only enable the console when you needed it.
我使用 Win32 控制台窗口在我的 C# 窗口程序(WInforms 或 WPF)中执行此操作。我有一个包含一些基本 Win32 API 的小类,我在程序开始时创建了一个控制台。这只是一个例子:在“现实生活”中,您会使用设置或其他东西来仅在需要时启用控制台。
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
回答by dbkk
You can't just keep adding logging items to a WinForms control (ListBox or RichTextBox) -- it will eventually get clogged and start swapping to disk.
您不能一直将日志项添加到 WinForms 控件(ListBox 或 RichTextBox)——它最终会被阻塞并开始交换到磁盘。
I had this exact bug at one point. The solution I had was to clip the list of displayed messages occasionally. In pseudocode, this is something like:
我曾经有过这个确切的错误。我的解决方案是偶尔剪辑显示的消息列表。在伪代码中,这类似于:
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 should be substantially bigger than ListMinSize, so clipping does not happen too often. ListMinSize is the number of recent messages that you'd normally need to look through in your logging list.
ListMaxSize 应该比 ListMinSize 大很多,所以剪裁不会经常发生。ListMinSize 是您通常需要在日志记录列表中查看的最近消息的数量。
This is just pseudocode, there is actually no RemoveRange on ListBox item collection (but there is on List). You can figure out the exact code.
这只是伪代码,实际上 ListBox 项集合上没有 RemoveRange(但 List 上有)。你可以找出确切的代码。
回答by psamwel
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();
}
}