Como mostro uma saída / janela do console em um aplicativo de formulários?

131

Para ficar preso imediatamente, um exemplo muito básico:

using System;
using System.Windows.Forms;

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

Se eu compilar isso com opções padrão (usando csc na linha de comando), conforme o esperado, ele será compilado em um aplicativo de console. Além disso, como eu importei System.Windows.Forms, ele também mostrará uma caixa de mensagem.

Agora, se eu usar a opção /target:winexe, que eu acho que é o mesmo que escolher Windows Applicationentre as opções do projeto, conforme o esperado, só verei a Caixa de Mensagens e nenhuma saída do console.

(De fato, no momento em que é iniciado a partir da linha de comando, posso emitir o próximo comando antes que o aplicativo seja concluído).

Então, minha pergunta é - eu sei que você pode ter "janelas" / formulários de um aplicativo de console, mas existe uma maneira de mostrar o console de um aplicativo do Windows?

Wil
fonte
2
o que você vê como a diferença entre os dois? Por que não apenas compilar como console e mostrar um formulário.
Doggett
7
@Doggett, simples - estou aprendendo e quero entender por que / como fazê-lo, mesmo que nunca acabe usando-o em um aplicativo real .... No momento, estou pensando em uma opção que fornece comandos extras / saída como no VLC, no entanto, TBH, eu não preciso disso - novamente, apenas aprendendo e quero entender!
Wil
Eu consegui isso usando este tutorial: saezndaree.wordpress.com/2009/03/29/…
vivanov

Respostas:

153

este deve funcionar.

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();
wizzardz
fonte
8
Impressionante, esta pergunta parece ter sido muito solicitada, esta é a única resposta real para a pergunta que consegui encontrar, +1
RobJohnson
5
Problema principal: quando você o fecha, todos os aplicativos são fechados.
Mark
4
Eu testei no Windows 8 e Windows 10: - AttachConsole funciona em uma caixa de cmd - AllocConsole funciona no Visual Studio. Quando a alocação é necessária, AttachConsole retorna false. Você também deve chamar o FreeConsole () antes de finalizar o aplicativo no modo do console. No meu programa, usei o código de Matthew Strawbridge (veja abaixo), com a linha AttachConsole () modificada para: if (! AttachConsole (-1)) AllocConsole ();
Berend Engelbrecht
Isso funcionará em um controle de usuário? Estou trabalhando para fazer um controle SSH como um componente winforms usando Granados (por exemplo), e é apenas um componente em segundo plano. Gostaria de adicionar um bom wrapper para exibir e usar o console em um componente também.
Kraang Prime
2
Isso não é ótimo, quando executado a partir da linha de comando, ele abre uma janela de console separada e, ao executar a partir da linha de comando e tentando usar >para redirecionar a saída, recebo uma janela de console separada e zero resultado no meu arquivo.
precisa saber é o seguinte
139

Talvez isso seja simplista demais ...

Crie um projeto Windows Form ...

Em seguida: Propriedades do projeto -> Aplicativo -> Tipo de saída -> Aplicativo do console

Então pode ter Console e Forms rodando juntos, funciona pra mim

Chaz
fonte
2
Parece mais simples, corrigido meu problema também.
precisa saber é o seguinte
2
Esta é definitivamente a melhor solução! Outros são smarts, mas maneira complicada
LM.Croisez
3
Simples e funcionou bem. Essa deve ser a resposta aceita.
madu
7
Embora, sim, tecnicamente isso possa ser usado para permitir o que o pôster está pedindo - não é uma ótima solução. Ao fazer isso, se você iniciar o aplicativo winforms com a GUI - você também abrirá uma janela do console. Nesse caso, você precisaria de algo mais como a resposta de Mike de Klerk.
perfil completo de Justin Greywolf
2
Essa é a única solução em que consegui que meu aplicativo Winforms grave a saída no console quando executado na linha de comando ou grava no arquivo quando redirecionado na linha de comando >. No entanto, eu esperava uma solução que explicasse como executar um "Aplicativo de console" apenas algumas vezes (ou seja, para ativar programaticamente o que quer que seja que a alteração dessa misteriosa configuração do Visual Studio). Alguém sabe como isso funciona sob o capô?
precisa saber é o seguinte
63

Se você não estiver preocupado em abrir um console sob comando, poderá acessar as propriedades do seu projeto e alterá-lo para Console Application

captura de tela da alteração do tipo de projeto.

Isso ainda mostrará seu formulário, além de aparecer uma janela do console. Você não pode fechar a janela do console, mas funciona como um excelente criador de logs temporário para depuração.

Lembre-se de desativá-lo antes de implantar o programa.

gunr2171
fonte
1
Agradável. Isso resolve o problema que tenho com meu aplicativo de formulários, que preciso ser capaz de gerar para uma janela do console enquanto suporta o redirecionamento da saída para um arquivo. E eu não precisa anexar qualquer consola manualmente ...
Kai Hartmann
2
@JasonHarrison Se você fechar a janela do console, o programa será fechado. Além disso, a janela está sempre aberta enquanto o programa é executado.
gunr2171
2
@ gun2171: Obrigado. As desvantagens para esta abordagem é observado na resposta: a janela do console aparecerá se o aplicativo é iniciado com duplo clique, menu Iniciar, etc.
Jason Harrison
17

Você pode chamar AttachConsoleusando pinvoke para obter uma janela do console anexada a um projeto WinForms: http://www.csharp411.com/console-output-from-winforms-application/

Você também pode considerar o Log4net ( http://logging.apache.org/log4net/index.html ) para configurar a saída do log em diferentes configurações.

Adam Vandenberg
fonte
+1 - Uau, eu estava esperando um console.show ou similar! muito mais complicado do que eu pensava! Deixarei em aberto por enquanto, caso haja uma resposta melhor / mais fácil.
Wil
Isso funcionou para mim, o AllocConsole () não funcionou porque gerou uma nova janela do console (não foi mais longe no AllocConsole, talvez eu tenha perdido alguma coisa lá).
DerFunk
14

Isso funcionou para mim, canalizar a saída para um arquivo. Ligue para o console com

cmd / c "C: \ caminho \ para \ seu \ aplicativo.exe"> meuarquivo.txt

Adicione esse código ao seu aplicativo.

    [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;
            }
        }
    }

Encontrei este código aqui: http://www.csharp411.com/console-output-from-winforms-application/ Eu pensei que era digno de publicá-lo aqui também.

Mike de Klerk
fonte
5
Isso funciona muito bem, EXCETO agora falha no Windows 8 e no Windows 10. Por falhas, quero dizer que não há saída, exceto um prompt extra (se isso é uma pista). Alguém sugeriu o AllocConsole, mas apenas exibiu uma janela do cmd.
Simon Heffer
Também tentei a resposta de Chaz acima, mas isso fornece um novo console no Windows 7 (não em 8 ou 10). Eu só preciso da opção de executar o redirecionamento na linha de comando ou executar como uma GUI, se não houver argumentos.
Simon Heffer
Eu tentei isso, mas não funcionou. Com apenas AttachConsole(ATTACH_PARENT_PROCESS)eu recebo a saída do console, mas redirecioná-lo na linha de comando com >não funciona. Quando tento esta resposta, não consigo obter nenhuma saída, seja no console ou em um arquivo.
uglycoyote
12

Existem basicamente duas coisas que podem acontecer aqui.

Saída do console É possível que um programa winforms se conecte à janela do console que o criou (ou a uma janela diferente do console, ou mesmo a uma nova janela do console, se desejar). Uma vez anexado à janela do console, Console.WriteLine () etc funciona conforme o esperado. Uma dica para essa abordagem é que o programa retorna o controle para a janela do console imediatamente e depois continua gravando, para que o usuário também possa digitar na janela do console. Você pode usar start com o parâmetro / wait para lidar com isso, eu acho.

Link para iniciar a sintaxe do comando

Saída de console redirecionada É quando alguém canaliza a saída do seu programa em outro lugar, por exemplo.

yourapp> file.txt

A conexão a uma janela do console nesse caso ignora efetivamente a tubulação. Para fazer isso funcionar, você pode chamar Console.OpenStandardOutput () para obter um identificador para o fluxo no qual a saída deve ser canalizada. Isso funciona apenas se a saída for canalizada, portanto, se você quiser lidar com os dois cenários, precisará abrir a saída padrão e gravá-la e anexá-la à janela do console. Isso significa que a saída é enviada para a janela do console e para o canal, mas é a melhor solução que eu poderia encontrar. Abaixo do código que eu uso para fazer isso.

// 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);
    }
}
cedd
fonte
Não consegui escrever no console; anexar o processo pai primeiro fez o truque. Obrigado.
Pupper
Parece que esta resposta exige que você reescreva todas as chamadas para, Console.WriteLineem vez disso, chamar o novo WriteLinedefinido acima. Mesmo que eu tenha tentado não conseguir, com esse código, redirecionar qualquer coisa para um arquivo ao executar o aplicativo na linha de comando e redirecioná-lo >para um arquivo.
precisa saber é o seguinte
@uglycoyote, verifique se você está construindo o GUIConsoleWriter o mais cedo possível em seu aplicativo, caso contrário, ele não funcionará por razões misteriosas do tipo janelas. Eu diria que encapsular chamadas para Console.WriteLineé apenas uma boa prática, pois permite testar e alterar facilmente os locais nos quais você faz logon (por exemplo, você pode começar a fazer logon em um serviço de log baseado em nuvem como o PaperTrail ou qualquer outra coisa )
cedd 15/08/18
isso funcionou bem para mim no Win10, mesmo semStreamWriter _stdOutWriter;
TS
Tubulação é a resposta, mas em vez de para um arquivo, basta usar MAIS, como: yourapp | Mais ; consulte stackoverflow.com/a/13010823/1845672
Roland
9

Crie um aplicativo Windows Forms e altere o tipo de saída para console.

Isso resultará em um console e no formulário para abrir.

insira a descrição da imagem aqui

Pedro Rodrigues
fonte
É exatamente isso que estou procurando. Simples e sem usar WINAPI's.
Michael Coxon
Eu tentei muitos exemplos, mas nenhum deles produziu resultados que atendiam às minhas expectativas. Esta solução, porém, é exatamente o que eu queria e, de longe, a solução mais fácil.
inexcitus 17/09/19
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;
        }
    }
}
Kamil Kh
fonte
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));
    }
}
trapo
fonte
1
Funciona bem em um prompt de comando, mas não em Iniciar> Executar ou no Visual Studio. Para fazê-lo funcionar em todos os casos, substitua a linha AttachConsole por: if (! AttachConsole (-1)) AllocConsole (); Se AllocConsole () for chamado, FreeConsole () também deve ser chamado, caso contrário, o host do console continuará em execução após o encerramento do programa.
Berend Engelbrecht
2
Qual é o uso pretendido de initialiseOut e initialiseError, porque eles não são usados?
Edwin
StandardHandle : uintestá errado aqui ... deve ser IntPtr para trabalhar tanto em x86 e x64
Dmitry Gusarov
1

Você pode alternar a qualquer momento entre os tipos de aplicativos, para o console ou o Windows. Portanto, você não escreverá uma lógica especial para ver o stdout. Além disso, ao executar o aplicativo no depurador, você verá todo o stdout na janela de saída. Você também pode adicionar um ponto de interrupção e, nas propriedades do ponto de interrupção, alterar "When Hit ...", pode enviar mensagens e variáveis. Além disso, você pode marcar / desmarcar "Continuar execução", e seu ponto de interrupção se tornará quadrado. Portanto, as mensagens do ponto de interrupção sem alterar nada no aplicativo na janela de saída de depuração.

armagedescu
fonte
0

Por que não deixá-lo como um aplicativo do Windows Forms e criar um formulário simples para imitar o console. O formulário pode ser criado para se parecer com o console com tela preta e responder diretamente ao pressionamento de tecla. Em seguida, no arquivo program.cs, você decide se precisa executar o formulário principal ou o ConsoleForm. Por exemplo, eu uso essa abordagem para capturar os argumentos da linha de comando no arquivo program.cs. Crio o ConsoleForm, inicialmente o oculto e transmito as seqüências de caracteres da linha de comando para uma função AddCommand, que exibe os comandos permitidos. Finalmente, se o usuário forneceu -h ou -? , chamo o .Show no ConsoleForm e, quando o usuário pressiona qualquer tecla, encerro o programa. Se o usuário não der o -? comando, fecho o ConsoleForm oculto e execute o formulário principal.

gverge
fonte
2
Olá e bem-vindo ao StackOverflow, evite postar perguntas como respostas, use a seção de comentários.
Pedro Rodrigues