如何在窗体应用程序中显示控制台输出/窗口?

192

为了立即开始,这里有一个非常基本的例子:

using System;
using System.Windows.Forms;

class test
{ 
    static void Main()
    { 
        Console.WriteLine("test");
        MessageBox.Show("test");
    }
}
如果我使用默认选项编译(使用命令行中的csc),预期将编译为控制台应用程序。此外,因为我导入了System.Windows.Forms,它还将显示一个消息框。
现在,如果我使用选项 /target:winexe,我认为这与在项目选项中选择“Windows应用程序”相同,那么预期只会看到消息框而没有控制台输出。
(实际上,在从命令行启动它的瞬间,甚至在应用程序完成之前,我可以发出下一条命令)。
那么,我的问题是 - 我知道您可以从控制台应用程序中获得“窗口”/表单输出,但是否有任何方法可以从Windows应用程序中显示控制台?

2
你认为这两者有什么区别?为什么不直接编译为控制台并显示一个窗体呢? - Doggett
8
@Doggett,简单来说——我正在学习并希望了解为什么以及如何去做它,即使我最终不会在真实应用中使用它……目前,我在考虑一个给出额外命令/输出的选项,例如在VLC中,但说实话,我并不需要它——我只是在学习并希望理解它! - Wil
1
我使用这个教程完成了这个任务:https://saezndaree.wordpress.com/2009/03/29/how-to-redirect-the-consoles-output-to-a-textbox-in-c/ - vivanov
15个回答

213
这个应该可以工作。
using System.Runtime.InteropServices;

private void Form1_Load(object sender, EventArgs e)
{
    AllocConsole();
}

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool AllocConsole();

8
很棒,这个问题似乎被问得很多,这是我能找到的唯一实际答案,点赞+1。 - RobJohnson
6
主要问题:关闭它时,所有应用程序都会关闭。 - Mark
7
我在Windows 8和Windows 10上进行了测试:
  • AttachConsole可以从命令提示符界面中运行。
  • AllocConsole可以从Visual Studio中运行。如果需要alloc,AttachConsole将返回false。在控制台模式下终止应用程序之前,还应调用FreeConsole()。
在我的程序中,我使用了Matthew Strawbridge的代码(参见下文),其中AttachConsole()这一行被修改为: if (!AttachConsole(-1)) AllocConsole();
- Berend Engelbrecht
6
当从命令行运行时,这并不好,它会弹出一个“独立”的控制台窗口。当从命令行运行并尝试使用“>”将输出重定向到文件时,我得到了一个单独的控制台窗口和没有任何输出的文件,这很糟糕。 - uglycoyote
6
如何在控制台中打印消息?Console.Writeline() 在控制台中没有输出任何内容。 - Sid133
显示剩余4条评论

205

或许这个过于简单化了...

创建一个Windows窗体项目...

然后:项目属性-> 应用程序 -> 输出类型-> 控制台应用程序

然后可以同时运行控制台和窗体,对我有用


2
似乎最简单,也解决了我的问题。 - dadude999
2
这绝对是最佳解决方案!其他的也不错,但过于复杂。 - LM.Croisez
3
简单易懂,功能正常。这应该被接受作为答案。 - madu
12
是的,从技术上讲,这可以用来允许海报所要求的内容,但这不是一个很好的解决方案。如果你使用GUI启动WinForms应用程序,那么控制台窗口也会打开。在这种情况下,你需要像Mike de Klerk的答案一样做些改进。 - Justin Greywolf
4
这是我唯一找到的解决方案,通过它我能够在命令行运行时使我的Winforms应用程序将输出写入控制台,或者在使用 > 命令重定向时将其写入文件。然而,我希望有一个解决方案,能够解释如何仅在某些时候以“控制台应用程序”的形式运行(即通过编程方式启用更改此神秘的Visual Studio设置所做的任何操作)。有人知道这是如何在内部实现的吗? - uglycoyote
显示剩余5条评论

85
如果您不担心在命令上打开控制台,您可以进入项目的属性并将其更改为控制台应用程序。这将仍然显示您的窗体以及弹出一个控制台窗口。您无法关闭控制台窗口,但它作为临时调试记录器非常有效。
请记得在部署程序之前将其关闭。 更改项目类型的屏幕截图

1
很好。这解决了我在表单应用程序中遇到的问题,我需要能够输出到控制台窗口,同时支持将输出重定向到文件。而且我不需要手动附加任何控制台... - Kai Hartmann
3
如果关闭控制台窗口,程序就会关闭。此外,在程序运行时该窗口始终保持打开状态。 - gunr2171
3
谢谢。答案中已经指出了这种方法的缺点:如果应用程序通过双击、开始菜单等方式启动,控制台窗口将会出现。 - Jason Harrison
2
这在 .Net Core 5 上不起作用。我只测试了单个版本的 .Net Core。除了不显示控制台窗口外,甚至在我保存并尝试打开表单设计选项卡后,它还导致 Visual Studio 2019 完全冻结了一次。 - C Perkins
@c-perkins 我也和你一样得到了相同的结果(.NET Core 5 Windows Form应用程序作为控制台应用程序不显示控制台,运行exe在VS 2019中不会输出到控制台),但是我尝试了VS 2022,在调试和运行exe时都可以在cmd终端中正常工作。 - Gordon Glas
显示剩余2条评论

20

+1 - 哇,我希望有一个console.show或类似的东西!比我想象的要复杂得多!我会暂时保持开放状态,以防有更好/更容易的答案。 - Wil
这对我有用,AllocConsole()没有用,因为它生成了一个新的控制台窗口(虽然我没有深入研究AllocConsole,也许我在那里错过了什么)。 - derFunk

19
创建一个Windows Forms应用程序,并将输出类型更改为控制台。
这将导致控制台和表单同时打开。 enter image description here

1
这正是我所寻找的。简单而且不使用WINAPI。 - Michael Coxon
2
我尝试了许多例子,但它们都没有达到我期望的结果。然而,这个解决方案正是我想要的,而且是最简单的解决方案。 - inexcitus

14

基本上有两件事情可能会发生在这里。

控制台输出 一个winforms程序可以附加到创建它的控制台窗口(或不同的控制台窗口,或者甚至是一个新的控制台窗口,如果需要的话)。一旦附加到控制台窗口,Console.WriteLine()等就像预期的那样工作。这种方法的一个问题是程序立即返回控制台窗口,然后继续写入它,所以用户也可以在控制台窗口中输入。我认为你可以使用带有/wait参数的start来处理这个问题。

链接到启动命令语法

重定向的控制台输出 这是当有人将你的程序的输出管道传输到其他地方时发生的情况,例如:

yourapp > file.txt

在这种情况下,连接到控制台窗口实际上会忽略管道。要使其工作,您可以调用Console.OpenStandardOutput()以获取应将输出传送到的流的句柄。这仅在输出被传输时才有效,因此如果要处理这两种情况,则需要打开标准输出并将其写入,并连接到控制台窗口。这意味着输出被发送到控制台窗口和管道,但这是我能找到的最佳解决方案。以下是我用来执行此操作的代码。
// This always writes to the parent console window and also to a redirected stdout if there is one.
// It would be better to do the relevant thing (eg write to the redirected file if there is one, otherwise
// write to the console) but it doesn't seem possible.
public class GUIConsoleWriter : IConsoleWriter
{
    [System.Runtime.InteropServices.DllImport("kernel32.dll")]
    private static extern bool AttachConsole(int dwProcessId);

    private const int ATTACH_PARENT_PROCESS = -1;

    StreamWriter _stdOutWriter;

    // this must be called early in the program
    public GUIConsoleWriter()
    {
        // this needs to happen before attachconsole.
        // If the output is not redirected we still get a valid stream but it doesn't appear to write anywhere
        // I guess it probably does write somewhere, but nowhere I can find out about
        var stdout = Console.OpenStandardOutput();
        _stdOutWriter = new StreamWriter(stdout);
        _stdOutWriter.AutoFlush = true;

        AttachConsole(ATTACH_PARENT_PROCESS);
    }

    public void WriteLine(string line)
    {
        _stdOutWriter.WriteLine(line);
        Console.WriteLine(line);
    }
}

我无法写入控制台;首先附加父进程就解决了问题。谢谢。 - Pupper
似乎这个答案需要你将所有对 Console.WriteLine 的调用重写为调用上面定义的新的 WriteLine。尽管我尝试了,但是在使用命令行运行应用程序并使用 > 将输出重定向到文件时,我仍然无法得到任何重定向的内容。 - uglycoyote
@uglycoyote,请确保在应用程序的尽早阶段构建GUIConsoleWriter,否则由于神秘的窗口类型原因它将无法工作。我认为封装对Console.WriteLine的调用只是一个好的实践,因为它允许您进行测试,并轻松更改记录日志的位置(例如,您可能想要开始记录到像PaperTrail这样的基于云的日志服务,或者其他任何地方)。 - cedd
这在Win10上对我来说很好用,甚至不需要 StreamWriter _stdOutWriter; - T.S.
管道是答案,但不要将其导出到文件中,只需使用MORE,例如:yourapp | more;请参考https://dev59.com/I03Sa4cB1Zd3GeqPtC0a#13010823。 - Roland

14

对我来说这行代码很有效,可以将输出导入到文件中。使用以下命令调用控制台:

cmd /c "C:\path\to\your\application.exe" > myfile.txt

将此代码添加到您的应用程序中。

    [DllImport("kernel32.dll")]
    static extern bool AttachConsole(UInt32 dwProcessId);
    [DllImport("kernel32.dll")]
    private static extern bool GetFileInformationByHandle(
        SafeFileHandle hFile,
        out BY_HANDLE_FILE_INFORMATION lpFileInformation
        );
    [DllImport("kernel32.dll")]
    private static extern SafeFileHandle GetStdHandle(UInt32 nStdHandle);
    [DllImport("kernel32.dll")]
    private static extern bool SetStdHandle(UInt32 nStdHandle, SafeFileHandle hHandle);
    [DllImport("kernel32.dll")]
    private static extern bool DuplicateHandle(
        IntPtr hSourceProcessHandle,
        SafeFileHandle hSourceHandle,
        IntPtr hTargetProcessHandle,
        out SafeFileHandle lpTargetHandle,
        UInt32 dwDesiredAccess,
        Boolean bInheritHandle,
        UInt32 dwOptions
        );
    private const UInt32 ATTACH_PARENT_PROCESS = 0xFFFFFFFF;
    private const UInt32 STD_OUTPUT_HANDLE = 0xFFFFFFF5;
    private const UInt32 STD_ERROR_HANDLE = 0xFFFFFFF4;
    private const UInt32 DUPLICATE_SAME_ACCESS = 2;
    struct BY_HANDLE_FILE_INFORMATION
    {
        public UInt32 FileAttributes;
        public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime;
        public UInt32 VolumeSerialNumber;
        public UInt32 FileSizeHigh;
        public UInt32 FileSizeLow;
        public UInt32 NumberOfLinks;
        public UInt32 FileIndexHigh;
        public UInt32 FileIndexLow;
    }
    static void InitConsoleHandles()
    {
        SafeFileHandle hStdOut, hStdErr, hStdOutDup, hStdErrDup;
        BY_HANDLE_FILE_INFORMATION bhfi;
        hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
        hStdErr = GetStdHandle(STD_ERROR_HANDLE);
        // Get current process handle
        IntPtr hProcess = Process.GetCurrentProcess().Handle;
        // Duplicate Stdout handle to save initial value
        DuplicateHandle(hProcess, hStdOut, hProcess, out hStdOutDup,
        0, true, DUPLICATE_SAME_ACCESS);
        // Duplicate Stderr handle to save initial value
        DuplicateHandle(hProcess, hStdErr, hProcess, out hStdErrDup,
        0, true, DUPLICATE_SAME_ACCESS);
        // Attach to console window – this may modify the standard handles
        AttachConsole(ATTACH_PARENT_PROCESS);
        // Adjust the standard handles
        if (GetFileInformationByHandle(GetStdHandle(STD_OUTPUT_HANDLE), out bhfi))
        {
            SetStdHandle(STD_OUTPUT_HANDLE, hStdOutDup);
        }
        else
        {
            SetStdHandle(STD_OUTPUT_HANDLE, hStdOut);
        }
        if (GetFileInformationByHandle(GetStdHandle(STD_ERROR_HANDLE), out bhfi))
        {
            SetStdHandle(STD_ERROR_HANDLE, hStdErrDup);
        }
        else
        {
            SetStdHandle(STD_ERROR_HANDLE, hStdErr);
        }
    }

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
        // initialize console handles
        InitConsoleHandles();

        if (args.Length != 0)
        {

            if (args[0].Equals("waitfordebugger"))
            {
                MessageBox.Show("Attach the debugger now");
            }
            if (args[0].Equals("version"))
            {
#if DEBUG
                String typeOfBuild = "d";
#else
                String typeOfBuild = "r";
#endif
                String output = typeOfBuild + Assembly.GetExecutingAssembly()
                    .GetName().Version.ToString();
                //Just for the fun of it
                Console.Write(output);
                Console.Beep(4000, 100);
                Console.Beep(2000, 100);
                Console.Beep(1000, 100);
                Console.Beep(8000, 100);
                return;
            }
        }
    }

我在这里找到了这段代码:http://www.csharp411.com/console-output-from-winforms-application/ 我认为在这里也发帖是有意义的。


5
除了在Windows 8和Windows 10中出现问题外,这个东西很好用。失败的意思是除了一个额外的提示符之外,没有输出(如果这是一个线索的话)。有人建议使用AllocConsole,但那只会闪烁一个cmd窗口。 - Simon Heffer
也尝试了上面 Chaz 的回答,但在 Windows 7 中会打开一个新的控制台窗口(但不是在 8 或 10 中)。我只需要在命令行上使用重定向选项运行或在没有参数的情况下作为 GUI 运行的选项。 - Simon Heffer
我尝试过这个,但是没有成功。只使用 AttachConsole(ATTACH_PARENT_PROCESS) 可以获得控制台输出,但是使用 > 在命令行上重定向输出却不起作用。当我尝试使用这个答案时,无论是在控制台还是文件中都无法获得任何输出。 - uglycoyote
有效期为6-7网? - Kiquenet

7

基于 Chaz 的回答,在 .NET 5 中有一个破坏性的更改,因此在项目文件中需要进行两个修改,即更改 OutputType 并添加 DisableWinExeOutputInference。 例如:

<PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0-windows10.0.17763.0</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <DisableWinExeOutputInference>true</DisableWinExeOutputInference>
    <Platforms>AnyCPU;x64;x86</Platforms>
</PropertyGroup>

实际上,在.NET 6中(以及可能更早的版本),只需将OutputType更改为Exe即可创建控制台...现在似乎这是正确的答案,只需在csproj中进行更改(或者使用/target:exe而不是/target:winexe或其他)就可以了。所有那些“将输出类型设置为控制台”的操作基本上都是在幕后执行此操作。 - spamove

7
//From your application set the Console to write to your RichTextkBox 
//object:
Console.SetOut(new RichTextBoxWriter(yourRichTextBox));

//To ensure that your RichTextBox object is scrolled down when its text is 
//changed add this event:
private void yourRichTextBox_TextChanged(object sender, EventArgs e)
{
    yourRichTextBox.SelectionStart = yourRichTextBox.Text.Length;
    yourRichTextBox.ScrollToCaret();
}

public delegate void StringArgReturningVoidDelegate(string text);
public class RichTextBoxWriter : TextWriter
{
    private readonly RichTextBox _richTextBox;
    public RichTextBoxWriter(RichTextBox richTexttbox)
    {
        _richTextBox = richTexttbox;
    }

    public override void Write(char value)
    {
        SetText(value.ToString());
    }

    public override void Write(string value)
    {
        SetText(value);
    }

    public override void WriteLine(char value)
    {
        SetText(value + Environment.NewLine);
    }

    public override void WriteLine(string value)
    {
        SetText(value + Environment.NewLine);
    }

    public override Encoding Encoding => Encoding.ASCII;

    //Write to your UI object in thread safe way:
    private void SetText(string text)
    {
        // InvokeRequired required compares the thread ID of the  
        // calling thread to the thread ID of the creating thread.  
        // If these threads are different, it returns true.  
        if (_richTextBox.InvokeRequired)
        {
            var d = new StringArgReturningVoidDelegate(SetText);
            _richTextBox.Invoke(d, text);
        }
        else
        {
            _richTextBox.Text += text;
        }
    }
}

非常酷的解决方案。正是我想要的! - BenHero

3

在项目属性中将输出类型设置为控制台,将会给你一个包含所创建窗体的控制台应用程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接