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


130

要立即陷入困境,这是一个非常基本的示例:

using System;
using System.Windows.Forms;

class test
{ 
    static void Main()
    { 
        Console.WriteLine("test");
        MessageBox.Show("test");
    }
}

如果按预期使用默认选项(在命令行中使用csc)进行编译,它将编译为控制台应用程序。另外,由于我已导入System.Windows.Forms,因此它还会显示一个消息框。

现在,如果我使用option /target:winexe,我认为它与Windows Application从项目选项中进行选择相同,那么按预期,我只会看到消息框,而没有控制台输出。

(实际上,从命令行启动它之后,我可以在应用程序完成之前发出下一条命令)。

所以,我的问题是-我知道您可以从控制台应用程序中获取“ windows” /窗体输出,但是无论如何,还是可以从Windows应用程序中显示控制台吗?


2
您认为两者之间的区别是什么?为什么不只是编译为控制台并显示表单。
Doggett 2010年

7
@Doggett,简单-我正在学习,并且想了解为什么/如何做,即使我从未在真正的应用程序中最终使用过它。...目前,我正在考虑提供额外命令/的选项。 VLC中的输出,但是TBH中,我不需要它-再一次,只是学习并想了解它!
威尔,2010年

我使用本教程实现了这一目标:saezndaree.wordpress.com/2009/03/29/…–
vivanov

Answers:


152

这应该工作。

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

5
主要问题:关闭它时,所有应用程序都关闭。
标记

4
我在Windows 8和Windows 10上进行了测试:-AttachConsole可通过cmd框运行-AllocConsole可通过Visual Studio运行。需要分配时,AttachConsole返回false。您还应该在控制台模式下终止应用程序之前调用FreeConsole()。在我的程序中,我使用了Matthew Strawbridge的代码(请参见下文),并将AttachConsole()行修改为:if(!AttachConsole(-1))AllocConsole();。
Berend Engelbrecht

这可以在用户控件中起作用吗?我正在使用Granados(例如)将SSH控件作为winforms组件进行制作,并且它仅是后台组件。我想为其添加一个不错的包装,以便在组件中显示和使用控制台。
Kraang Prime

2
这不是很好,当从命令行运行时,它将弹出打开一个单独的控制台窗口,而当从命令行运行并尝试>用于重定向输出时,我在文件中得到了一个单独的控制台窗口和零输出。
uglycoyote18年

138

也许这太过简单了...

创建一个Windows Form项目...

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

然后可以让控制台和表单一起运行,为我工作


2
似乎最简单,也解决了我的问题。
dadude999 2015年

2
这绝对是最好的解决方案!其他人很聪明,但方式
却很

3
简单,工作正常。这应该是公认的答案。
madu

7
虽然是的,但从技术上讲,这可以用于允许发布者的要求-这不是一个很好的解决方案。这样,如果您随后使用GUI启动winforms应用程序-您还将打开一个控制台窗口。在这种情况下,您将需要更像Mike de Klerk的答案。
贾斯汀·格雷沃尔夫

2
这是唯一能够使我的Winforms应用程序从命令行运行时将输出写入控制台,或在通过命令行重定向到Windows时写入文件的唯一解决方案>。但是,我希望找到一种解决方案,该解决方案仅在某些时间解释如何作为“控制台应用程序”运行(即,以编程方式启用更改此神秘的Visual Studio设置所做的一切)。有谁知道它是如何工作的?
uglycoyote18年

63

如果您不担心按命令打开控制台,则可以进入项目的属性并将其更改为“控制台应用程序”

更改项目类型的屏幕截图

这仍然会显示您的表单并弹出控制台窗口。您无法关闭控制台窗口,但它可作为调试的出色临时记录器。

只是记得在部署程序之前将其关闭。


1
真好 这解决了我的表单应用程序所存在的问题,即我需要能够输出到控制台窗口,同时支持将输出重定向到文件。而且我不需要手动连接任何控制台...
Kai Hartmann 2014年

2
@JasonHarrison如果关闭控制台窗口,则程序将关闭。此外,程序运行时窗口始终处于打开状态。
gunr2171

2
@ gun2171:谢谢。这种方法的缺点在回答注意:如果在启动应用程序使用双击,开始菜单等都会出现在控制台窗口
贾森·哈里森

17

您可以AttachConsole使用pinvoke进行调用,以获取附加到WinForms项目的控制台窗口:http : //www.csharp411.com/console-output-from-winforms-application/

您可能还需要考虑使用Log4net(http://logging.apache.org/log4net/index.html)在不同的配置中配置日志输出。


+1-哇,我希望有一个console.show或类似的东西!比我想象的要复杂得多!我暂时不公开,以防万一有更好/更容易的答案。
威尔,2010年

这对我有用,AllocConsole()并不是因为它产生了一个新的控制台窗口(没有深入研究AllocConsole,但是也许我错过了那里的东西)。
derFunk

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)我就可以获取控制台输出,但是在命令行上使用重定向则>无法正常工作。当我尝试此答案时,无论在控制台还是在文件中,都无法获得任何输出。
uglycoyote18年

12

基本上可以在这里发生两件事。

控制台输出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);
    }
}

我无法写到控制台。首先附加父进程就可以了。谢谢。
2013年

似乎此答案要求您重写所有的调用Console.WriteLine,而不是调用WriteLine上面定义的新调用。即使我尝试使用此代码无法在命令行上运行应用程序并将其重定向到文件时,也无法将任何内容重定向到>文件。
uglycoyote18年

@uglycoyote,请确保您在应用程序中尽早构造GUIConsoleWriter,否则由于神秘的Windows类型原因,它将无法正常工作。我认为,封装对to的调用Console.WriteLine只是一种好习惯,因为它可以测试并轻松更改您登录的位置(例如,您可能希望开始登录到基于纸张的日志服务(如PaperTrail) )
08/15

这在Win10中甚至连我都没有用StreamWriter _stdOutWriter;
TS

管道是解决问题的方法,但不是使用文件,而是使用MORE,例如:yourapp | 更多 ; 请访问stackoverflow.com/a/13010823/1845672
罗兰

9

创建一个Windows Forms应用程序,并将输出类型更改为Console。

这将同时打开控制台和表单

在此处输入图片说明


这正是我想要的。简单且不使用WINAPI。
Michael Coxon

我尝试了许多示例,但是没有一个示例能够达到我的期望。但是,此解决方案正是我想要的,也是迄今为止最简单的解决方案。
inexcitus

4
//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;
        }
    }
}

3
using System;
using System.Runtime.InteropServices;

namespace SomeProject
{
    class GuiRedirect
    {
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool AttachConsole(int dwProcessId);
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr GetStdHandle(StandardHandle nStdHandle);
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool SetStdHandle(StandardHandle nStdHandle, IntPtr handle);
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern FileType GetFileType(IntPtr handle);

    private enum StandardHandle : uint
    {
        Input = unchecked((uint)-10),
        Output = unchecked((uint)-11),
        Error = unchecked((uint)-12)
    }

    private enum FileType : uint
    {
        Unknown = 0x0000,
        Disk = 0x0001,
        Char = 0x0002,
        Pipe = 0x0003
    }

    private static bool IsRedirected(IntPtr handle)
    {
        FileType fileType = GetFileType(handle);

        return (fileType == FileType.Disk) || (fileType == FileType.Pipe);
    }

    public static void Redirect()
    {
        if (IsRedirected(GetStdHandle(StandardHandle.Output)))
        {
            var initialiseOut = Console.Out;
        }

        bool errorRedirected = IsRedirected(GetStdHandle(StandardHandle.Error));
        if (errorRedirected)
        {
            var initialiseError = Console.Error;
        }

        AttachConsole(-1);

        if (!errorRedirected)
            SetStdHandle(StandardHandle.Error, GetStdHandle(StandardHandle.Output));
    }
}

1
在命令提示符下工作正常,但不能从“开始”>“运行”或在Visual Studio中正常工作。要使其在所有情况下均可用,请替换为AttachConsole行:if(!AttachConsole(-1))AllocConsole(); 如果调用了AllocConsole(),则还应该调用FreeConsole(),否则控制台主机在终止程序后继续运行。
Berend Engelbrecht

2
由于未使用initialiseOut和initialiseError的预期用途是什么?
艾德温

StandardHandle : uint在这里是错误的...应该是IntPtr才能在x86和x64上工作
Dmitry Gusarov

1

您可以随时在应用程序类型,控制台或Windows之间切换。因此,您将不会编写特殊的逻辑来查看标准输出。另外,在调试器中运行应用程序时,您将在输出窗口中看到所有标准输出。您可能还只是添加一个断点,并且在断点属性中更改“ When Hit ...”时,您可以输出任何消息和变量。您也可以选中/取消选中“继续执行”,断点将变为正方形。因此,在调试输出窗口中无需更改应用程序中任何内容的断点消息。


0

为什么不将其保留为Window Forms应用程序,并创建一个简单的表单来模仿控制台。可以使该表格看起来像黑屏的控制台,并使其直接响应按键。然后,在program.cs文件中,确定是需要运行主窗体还是ConsoleForm。例如,我使用这种方法来捕获program.cs文件中的命令行参数。我创建了ConsoleForm,首先将其隐藏,然后将命令行字符串传递给其中的AddCommand函数,该函数显示允许的命令。最后,如果用户给出-h或-?命令,我在ConsoleForm上调用.Show,当用户按下任何键时,我关闭程序。如果用户不输入-?命令,我关闭了隐藏的ConsoleForm并运行主窗体。


2
您好,欢迎使用StackOverflow,避免发布问题作为答案,请使用评论部分。
Pedro Rodrigues
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.