¿Cómo muestro una salida / ventana de consola en una aplicación de formularios?

131

Para quedar atrapado de inmediato, un ejemplo muy básico:

using System;
using System.Windows.Forms;

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

Si compilo esto con las opciones predeterminadas (usando csc en la línea de comando), como se esperaba, se compilará en una aplicación de consola. Además, porque importé System.Windows.Forms, también mostrará un cuadro de mensaje.

Ahora, si uso la opción /target:winexe, que creo que es lo mismo que elegir Windows Applicationdentro de las opciones del proyecto, como se esperaba, solo veré el cuadro de mensaje y no habrá salida de la consola.

(De hecho, en el momento en que se inicia desde la línea de comandos, puedo emitir el siguiente comando incluso antes de que la aplicación se haya completado).

Entonces, mi pregunta es: sé que puede obtener resultados de "ventanas" / formularios desde una aplicación de consola, pero ¿hay alguna forma de mostrar la consola desde una aplicación de Windows?

Wil
fuente
2
¿Qué ves como la diferencia entre los dos? ¿Por qué no simplemente compilar como consola y mostrar un formulario?
Doggett
77
@Doggett, simple: estoy aprendiendo y quiero entender por qué / cómo hacerlo, incluso si nunca termino usándolo en una aplicación real ... En este momento, estoy pensando en una opción que dé comandos adicionales / salida como en VLC, sin embargo TBH, no lo necesito, nuevamente, ¡solo estoy aprendiendo y quiero entenderlo!
Wil
Lo logré usando este tutorial: saezndaree.wordpress.com/2009/03/29/…
vivanov

Respuestas:

153

Este debería 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
fuente
8
Impresionante, esta pregunta parece haberse hecho mucho, esta es la única respuesta real a la pregunta que he podido encontrar, +1
RobJohnson
55
Problema principal: cuando lo cierra, se cierra toda la aplicación.
Mark
44
Probé en Windows 8 y Windows 10: - AttachConsole funciona desde un cuadro de cmd - AllocConsole funciona desde Visual Studio. Cuando se necesita alloc, AttachConsole devuelve false. También debe llamar a FreeConsole () antes de finalizar la aplicación en modo consola. En mi programa utilicé el código de Matthew Strawbridge (ver más abajo), con la línea AttachConsole () modificada a: if (! AttachConsole (-1)) AllocConsole ();
Berend Engelbrecht
¿Funcionará esto en un control de usuario? Estoy trabajando en hacer un control SSH como componente winforms usando Granados (por ejemplo), y es solo un componente de fondo. Me gustaría agregar un buen contenedor para que muestre y use la consola en un componente también.
Kraang Prime
2
Esto no es genial, cuando se ejecuta desde la línea de comando, se abre una ventana de consola separada , y cuando se ejecuta desde la línea de comando e intento usar >para redirigir la salida, obtengo una ventana de consola separada y cero salida en mi archivo.
uglycoyote
139

Quizás esto sea demasiado simplista ...

Crear un proyecto de Windows Form ...

Luego: Propiedades del proyecto -> Aplicación -> Tipo de salida -> Aplicación de consola

Entonces puede tener la consola y los formularios funcionando juntos, funciona para mí

Chaz
fuente
2
Parece más simple, solucionó mi problema también.
dadude999
2
¡Esta es definitivamente la mejor solución! Otros son inteligentes pero muy complicados
LM.Croisez
3
Simple y funcionó bien. Esta debería ser la respuesta aceptada.
madu
77
Si bien, sí, técnicamente esto se puede usar para permitir lo que pide el póster, no es una gran solución. Al hacer esto, si luego inicia su aplicación winforms con la GUI, también se abrirá una ventana de consola. En este caso, necesitaría algo más como la respuesta de Mike de Klerk.
Justin Greywolf
2
Esta es la única solución en la que he podido hacer que mi aplicación Winforms escriba la salida en la consola cuando se ejecuta desde la línea de comandos, o que escriba en el archivo cuando se redirige en la línea de comandos con >. Sin embargo, esperaba una solución que explicara cómo ejecutarse como una "Aplicación de consola" solo algunas veces (es decir, habilitar programáticamente lo que sea que cambie esta configuración misteriosa de Visual Studio). ¿Alguien sabe cómo funciona esto bajo el capó?
uglycoyote
63

Si no le preocupa abrir un comando de consola, puede acceder a las propiedades de su proyecto y cambiarlo a Aplicación de consola

captura de pantalla de cambiar el tipo de proyecto.

Esto seguirá mostrando su formulario, así como una ventana emergente de la consola. No puede cerrar la ventana de la consola, pero funciona como un excelente registrador temporal para la depuración.

Solo recuerde desactivarlo antes de implementar el programa.

gunr2171
fuente
1
Agradable. Esto resuelve el problema que tengo con mi aplicación de formularios, que necesito poder enviar a una ventana de la consola mientras soporto la redirección de la salida a un archivo. Y no necesito conectar ninguna consola manualmente ...
Kai Hartmann
2
@JasonHarrison Si cierra la ventana de la consola, el programa se cierra. Además, la ventana siempre está abierta mientras se ejecuta el programa.
gunr2171
2
@ gun2171: Gracias. Las desventajas de este enfoque se observan en la respuesta: la ventana de la consola aparecerá si la aplicación se inicia con doble clic, menú Inicio, etc.
Jason Harrison
17

Puede llamar AttachConsoleusando pinvoke para obtener una ventana de consola adjunta a un proyecto WinForms: http://www.csharp411.com/console-output-from-winforms-application/

También puede considerar Log4net ( http://logging.apache.org/log4net/index.html ) para configurar la salida del registro en diferentes configuraciones.

Adam Vandenberg
fuente
+1 - Wow, esperaba una consola. ¡Show o similar! mucho más complicado de lo que pensaba! Dejaré abierto por el momento en caso de que haya una respuesta mejor / más fácil.
Wil
Esto funcionó para mí, AllocConsole () no lo hizo porque generó una nueva ventana de consola (no profundizó en AllocConsole, aunque tal vez me perdí algo allí).
derFunk
14

Esto funcionó para mí, para canalizar la salida a un archivo. Llama a la consola con

cmd / c "C: \ ruta \ a \ su \ application.exe"> myfile.txt

Agregue este código a su aplicación.

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

Encontré este código aquí: http://www.csharp411.com/console-output-from-winforms-application/ Pensé que valía la pena publicarlo aquí también.

Mike de Klerk
fuente
55
Esto funciona muy bien EXCEPTO que ahora falla en Windows 8 y Windows 10. Por fallas quiero decir que no hay salida excepto y un mensaje adicional (si eso es una pista). Alguien sugirió AllocConsole pero eso solo mostró una ventana de cmd.
Simon Heffer
También probé la respuesta de Chaz anterior, pero eso da una nueva consola en Windows 7 (aunque no en 8 o 10). Solo necesito la opción de ejecutar con redireccionamiento en la línea de comando o ejecutar como una interfaz gráfica de usuario si no hay argumentos.
Simon Heffer
Intenté esto pero no funcionó. Con solo AttachConsole(ATTACH_PARENT_PROCESS)obtengo la salida de la consola, pero la redirección en la línea de comando >no funciona. Cuando intento esta respuesta, no puedo obtener ningún resultado, ya sea en la consola o en un archivo.
uglycoyote
12

Básicamente, hay dos cosas que pueden suceder aquí.

Salida de la consola Es posible que un programa winforms se una a la ventana de la consola que lo creó (o a una ventana de consola diferente, o incluso a una nueva ventana de consola si lo desea). Una vez conectado a la ventana de la consola, Console.WriteLine (), etc. funciona como se esperaba. Uno de los problemas con este enfoque es que el programa devuelve el control a la ventana de la consola inmediatamente y luego continúa escribiendo en él, de modo que el usuario también puede escribir en la ventana de la consola. Puede usar start con el parámetro / wait para manejar esto, creo.

Enlace para iniciar la sintaxis del comando

Salida de consola redirigida Esto es cuando alguien canaliza la salida de su programa en otro lugar, por ejemplo.

yourapp> file.txt

Adjuntar a una ventana de consola en este caso efectivamente ignora la tubería. Para que esto funcione, puede llamar a Console.OpenStandardOutput () para obtener un identificador de la secuencia a la que se debe canalizar la salida. Esto solo funciona si la salida se canaliza, por lo que si desea manejar ambos escenarios, debe abrir la salida estándar y escribir en ella y adjuntarla a la ventana de la consola. Esto significa que la salida se envía a la ventana de la consola y a la tubería, pero es la mejor solución que pude encontrar. Debajo del código que uso para hacer esto.

// 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
fuente
No pude escribir en la consola; adjuntar el proceso padre primero hizo el truco. Gracias.
Pupper
Parece que esta respuesta requiere que reescribas todas las llamadas a Console.WriteLinepara llamar a la nueva WriteLinedefinida anteriormente. Aunque probé que no podía con este código redirigir nada a un archivo al ejecutar la aplicación en la línea de comando y redirigir >a un archivo.
uglycoyote
@uglycoyote, asegúrese de construir el GUIConsoleWriter lo antes posible en su aplicación, de lo contrario no funcionará por misteriosas razones de tipo Windows. Yo diría que encapsular las llamadas a Console.WriteLinees solo una buena práctica, ya que le permite probar y cambiar fácilmente los lugares en los que inicia sesión (por ejemplo, es posible que desee comenzar a iniciar sesión en un servicio de registro basado en la nube como PaperTrail, o lo que sea )
cedd
esto funcionó bien para mí en Win10 sin siquieraStreamWriter _stdOutWriter;
TS
La tubería es la respuesta, pero en lugar de un archivo, simplemente use MÁS, como: yourapp | más ; consulte stackoverflow.com/a/13010823/1845672
Roland
9

Cree una aplicación de formularios Windows Forms y cambie el tipo de salida a Consola.

Resultará en una consola y el formulario para abrir.

ingrese la descripción de la imagen aquí

Pedro Rodrigues
fuente
Esto es exactamente lo que estoy buscando. Simple y sin usar WINAPI's.
Michael Coxon
He probado muchos ejemplos, pero ninguno de ellos produjo resultados que cumplieran mis expectativas. Sin embargo, esta solución es exactamente lo que quería y, con mucho, la solución más fácil.
inexcito
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
fuente
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
fuente
1
Funciona bien desde un símbolo del sistema, pero no desde Inicio> Ejecutar o en Visual Studio. Para que funcione en todos los casos, reemplace la línea AttachConsole por: if (! AttachConsole (-1)) AllocConsole (); Si se llama a AllocConsole (), también se debe llamar a FreeConsole (), de lo contrario, el host de la consola continúa ejecutándose después de finalizar el programa.
Berend Engelbrecht
2
¿Cuál es el uso previsto de initialiseOut e initialiseError, porque no se usan?
Edwin
StandardHandle : uintestá mal aquí ... debería ser IntPtr para trabajar tanto en x86 como en x64
Dmitry Gusarov
1

En cualquier momento puede cambiar entre tipos de aplicaciones, consolas o ventanas. Por lo tanto, no escribirá una lógica especial para ver el stdout. Además, al ejecutar la aplicación en el depurador, verá todas las stdout en la ventana de salida. También puede agregar un punto de interrupción y, en las propiedades del punto de interrupción, cambiar "Cuando se golpea ...", puede generar mensajes y variables. También puede marcar / desmarcar "Continuar ejecución", y su punto de interrupción tendrá forma cuadrada. Entonces, los mensajes de punto de interrupción sin cambiar nada en la aplicación en la ventana de salida de depuración.

armagedescu
fuente
0

¿Por qué no simplemente dejarlo como una aplicación de Windows Forms y crear un formulario simple para imitar la consola? Se puede hacer que el formulario se parezca a la consola con pantalla negra y que responda directamente al presionar una tecla. Luego, en el archivo program.cs, usted decide si necesita ejecutar el formulario principal o el ConsoleForm. Por ejemplo, uso este enfoque para capturar los argumentos de la línea de comandos en el archivo program.cs. Creo el ConsoleForm, inicialmente lo oculto, luego paso las cadenas de la línea de comandos a una función AddCommand, que muestra los comandos permitidos. Finalmente, si el usuario dio -h o -? comando, llamo al .Show en ConsoleForm y cuando el usuario toca cualquier tecla, apago el programa. Si el usuario no da el -? comando, cierro el ConsoleForm oculto y ejecuto el formulario principal.

gverge
fuente
2
Hola y bienvenido a StackOverflow, evite publicar preguntas como respuestas, use la sección de comentarios.
Pedro Rodrigues