Wie zeige ich eine Konsolenausgabe / ein Konsolenfenster in einer Formularanwendung?

130

Ein sehr einfaches Beispiel, um sofort festzuhalten:

using System;
using System.Windows.Forms;

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

Wenn ich dies wie erwartet mit Standardoptionen kompiliere (mit csc in der Befehlszeile), wird es zu einer Konsolenanwendung kompiliert. Da ich importiert habe System.Windows.Forms, wird auch ein Meldungsfeld angezeigt.

Wenn ich nun die Option verwende /target:winexe, die meiner Meinung nach der Auswahl Windows Applicationaus den Projektoptionen entspricht, wird erwartungsgemäß nur das Meldungsfeld und keine Konsolenausgabe angezeigt.

(Sobald es über die Befehlszeile gestartet wird, kann ich den nächsten Befehl ausgeben, bevor die Anwendung überhaupt abgeschlossen ist.)

Meine Frage lautet also: Ich weiß, dass Sie "Windows" / Formulare von einer Konsolenanwendung ausgeben können, aber gibt es trotzdem eine Möglichkeit, die Konsole von einer Windows-Anwendung anzuzeigen?

Wil
quelle
2
Was sehen Sie als den Unterschied zwischen den beiden? Warum nicht einfach als Konsole kompilieren und ein Formular anzeigen?
Doggett
7
@ Doggett, einfach - Ich lerne und möchte verstehen, warum / wie es geht, auch wenn ich es nie in einer realen Anwendung verwende. Im Moment denke ich an eine Option, die zusätzliche Befehle gibt. Ausgabe wie in VLC, jedoch TBH, ich brauche es nicht - wieder nur lernen und es verstehen wollen!
Wil
Ich habe das mit diesem Tutorial erreicht: saezndaree.wordpress.com/2009/03/29/…
vivanov

Antworten:

152

Dieser sollte funktionieren.

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
quelle
8
Genial, diese Frage scheint viel gestellt worden zu sein, dies ist die einzige tatsächliche Antwort auf die Frage, die ich finden konnte, +1
RobJohnson
5
Hauptproblem: Wenn Sie es schließen, wird die gesamte Anwendung geschlossen.
Mark
4
Ich habe unter Windows 8 und Windows 10 getestet: - AttachConsole funktioniert über eine Cmd-Box - AllocConsole funktioniert über Visual Studio. Wenn eine Zuordnung erforderlich ist, gibt AttachConsole false zurück. Sie sollten auch FreeConsole () aufrufen, bevor Sie die Anwendung im Konsolenmodus beenden. In meinem Programm habe ich den Code von Matthew Strawbridge verwendet (siehe unten), wobei die Zeile AttachConsole () geändert wurde in: if (! AttachConsole (-1)) AllocConsole ();
Berend Engelbrecht
Funktioniert dies in einer Benutzersteuerung? Ich arbeite daran, ein SSH-Steuerelement als Winforms-Komponente mit Granados (zum Beispiel) zu erstellen, und es ist nur eine Hintergrundkomponente. Ich möchte einen schönen Wrapper hinzufügen, damit er die Konsole auch in einer Komponente anzeigt und verwendet.
Kraang Prime
2
Dies ist nicht besonders gut. Wenn Sie über die Befehlszeile ausgeführt werden, wird ein separates Konsolenfenster geöffnet. Wenn Sie über die Befehlszeile ausgeführt werden und versuchen, >die Ausgabe umzuleiten, wird in meiner Datei ein separates Konsolenfenster und keine Ausgabe angezeigt.
hässlicher Kojote
137

Vielleicht ist das zu simpel ...

Erstellen Sie ein Windows Form-Projekt ...

Dann: Projekteigenschaften -> Anwendung -> Ausgabetyp -> Konsolenanwendung

Dann können Konsole und Formulare zusammen laufen, funktioniert für mich

Chaz
quelle
2
Scheint am einfachsten, hat auch mein Problem behoben.
dadude999
2
Dies ist definitiv die beste Lösung! Andere sind klug, aber sehr kompliziert
LM.Croisez
3
Einfach und gut funktioniert. Dies sollte die akzeptierte Antwort sein.
Madu
7
Ja, technisch gesehen kann dies verwendet werden, um zuzulassen, was das Poster verlangt - es ist keine großartige Lösung. Wenn Sie dann Ihre Winforms-Anwendung über die GUI starten, wird auf diese Weise auch ein Konsolenfenster geöffnet. In diesem Fall benötigen Sie eher die Antwort von Mike de Klerk.
Justin Greywolf
2
Dies ist die einzige Lösung, bei der ich meine Winforms-App dazu bringen konnte, beim Ausführen über die Befehlszeile eine Ausgabe in die Konsole zu schreiben oder bei Umleitung in die Befehlszeile mit in eine Datei zu schreiben >. Ich hatte jedoch auf eine Lösung gehofft, die erklärt, wie man nur teilweise als "Konsolenanwendung" ausgeführt wird (dh programmgesteuert zu aktivieren, was auch immer das Ändern dieser mysteriösen Visual Studio-Einstellung bewirkt). Weiß jemand, wie das unter der Haube funktioniert?
hässlicher Kojote
62

Wenn Sie sich keine Gedanken über das Öffnen einer Konsole auf Befehl machen möchten, können Sie die Eigenschaften Ihres Projekts aufrufen und in Konsolenanwendung ändern

Screenshot zum Ändern des Projekttyps.

Dies zeigt weiterhin Ihr Formular an und öffnet ein Konsolenfenster. Sie können das Konsolenfenster nicht schließen, aber es funktioniert als hervorragender temporärer Logger für das Debuggen.

Denken Sie daran, es wieder auszuschalten, bevor Sie das Programm bereitstellen.

gunr2171
quelle
1
Nett. Dies behebt das Problem mit meiner Formularanwendung, dass ich in der Lage sein muss, in ein Konsolenfenster auszugeben, während die Umleitung der Ausgabe in eine Datei unterstützt wird. Und ich muss keine Konsole manuell anbringen ...
Kai Hartmann
2
@JasonHarrison Wenn Sie das Konsolenfenster schließen, wird das Programm geschlossen. Auch das Fenster ist immer geöffnet, während das Programm läuft.
gunr2171
2
@ gun2171: Danke. Die Nachteile dieses Ansatzes werden in der Antwort vermerkt: Das Konsolenfenster wird angezeigt, wenn die Anwendung mit Doppelklick, Startmenü usw. gestartet wird.
Jason Harrison
17

Sie können AttachConsolemit pinvoke aufrufen, um ein Konsolenfenster an ein WinForms-Projekt anzuhängen: http://www.csharp411.com/console-output-from-winforms-application/

Möglicherweise möchten Sie auch Log4net ( http://logging.apache.org/log4net/index.html ) in Betracht ziehen, um die Protokollausgabe in verschiedenen Konfigurationen zu konfigurieren.

Adam Vandenberg
quelle
+1 - Wow, ich hatte auf eine Konsole gehofft. Show oder ähnliches! viel komplizierter als ich dachte! Ich werde für den Moment offen lassen, falls es eine bessere / einfachere Antwort gibt.
Wil
Dies funktionierte für mich, AllocConsole () tat es nicht, weil es ein neues Konsolenfenster hervorbrachte (ging nicht weiter in AllocConsole ein, vielleicht habe ich dort etwas verpasst).
derFunk
14

Dies funktionierte für mich, um die Ausgabe in eine Datei zu leiten. Rufen Sie die Konsole mit an

cmd / c "C: \ Pfad \ zu \ Ihrer \ application.exe"> myfile.txt

Fügen Sie diesen Code Ihrer Anwendung hinzu.

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

Ich habe diesen Code hier gefunden: http://www.csharp411.com/console-output-from-winforms-application/ Ich dachte, es wäre wert, ihn auch hier zu posten.

Mike de Klerk
quelle
5
Dies funktioniert hervorragend, AUSSER es schlägt jetzt in Windows 8 und Windows 10 fehl. Mit "fehlgeschlagen" meine ich, dass es keine Ausgabe außer und eine zusätzliche Eingabeaufforderung gibt (wenn dies ein Hinweis ist). Jemand hat AllocConsole vorgeschlagen, aber das hat nur ein cmd-Fenster geflasht.
Simon Heffer
Ich habe auch Chaz 'Antwort oben ausprobiert, aber das gibt eine neue Konsole in Windows 7 (obwohl nicht in 8 oder 10). Ich brauche nur die Option, um mit Umleitung in der Befehlszeile oder als GUI auszuführen, wenn es keine Argumente gibt.
Simon Heffer
Ich habe es versucht, aber es hat nicht funktioniert. Mit nur AttachConsole(ATTACH_PARENT_PROCESS)bekomme ich die Konsolenausgabe, aber das Umleiten auf die Kommandozeile mit >funktioniert nicht. Wenn ich diese Antwort versuche, kann ich weder in der Konsole noch in einer Datei eine Ausgabe erhalten.
hässlicher Kojote
12

Grundsätzlich können hier zwei Dinge passieren.

Konsolenausgabe Ein Winforms-Programm kann sich an das Konsolenfenster anhängen, das es erstellt hat (oder an ein anderes Konsolenfenster oder an ein neues Konsolenfenster, falls gewünscht). Einmal an das Konsolenfenster angehängt, funktioniert Console.WriteLine () usw. wie erwartet. Ein Problem bei diesem Ansatz ist, dass das Programm die Steuerung sofort an das Konsolenfenster zurückgibt und dann weiter darauf schreibt, sodass der Benutzer auch das Konsolenfenster eingeben kann. Sie können start mit dem Parameter / wait verwenden, um dies zu handhaben, denke ich.

Link zum Starten der Befehlssyntax

Umgeleitete Konsolenausgabe Dies ist der Fall, wenn jemand die Ausgabe Ihres Programms an eine andere Stelle weiterleitet, z.

yourapp> file.txt

Beim Anschließen an ein Konsolenfenster werden in diesem Fall die Rohrleitungen effektiv ignoriert. Damit dies funktioniert, können Sie Console.OpenStandardOutput () aufrufen, um ein Handle für den Stream abzurufen, an den die Ausgabe weitergeleitet werden soll. Dies funktioniert nur, wenn die Ausgabe weitergeleitet wird. Wenn Sie also beide Szenarien behandeln möchten, müssen Sie die Standardausgabe öffnen, darauf schreiben und an das Konsolenfenster anhängen. Dies bedeutet, dass die Ausgabe an das Konsolenfenster und an die Pipe gesendet wird, aber es ist die beste Lösung, die ich finden konnte. Unter dem Code, den ich dazu benutze.

// 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
quelle
Ich konnte nicht auf die Konsole schreiben. Das Anhängen des übergeordneten Prozesses hat zuerst den Trick ausgeführt. Danke dir.
Pupper
Es scheint, dass Sie für diese Antwort alle Anrufe neu schreiben müssen, Console.WriteLineum stattdessen die WriteLineoben definierte neue aufzurufen . Obwohl ich versucht habe, mit diesem Code nichts in eine Datei umzuleiten, wenn die Anwendung in der Befehlszeile ausgeführt und mit >in eine Datei umgeleitet wird .
hässlicher Kojote
@uglycoyote, stellen Sie sicher, dass Sie den GUIConsoleWriter so früh wie möglich in Ihrer Anwendung erstellen, da er sonst aus mysteriösen Windows-Gründen nicht funktioniert. Ich würde argumentieren, dass das Einkapseln von Anrufen Console.WriteLinenur eine gute Vorgehensweise ist, da Sie damit die Orte, an denen Sie sich anmelden, testen und einfach ändern können (z. B. möchten Sie möglicherweise mit der Protokollierung bei einem Cloud-basierten Protokollierungsdienst wie PaperTrail oder was auch immer beginnen )
Cedd
Dies funktionierte gut für mich in Win10 ohne geradeStreamWriter _stdOutWriter;
TS
Piping ist die Antwort, aber anstelle einer Datei verwenden Sie einfach MEHR, wie z. B.: Yourapp | Mehr ; Weitere Informationen finden
Roland
9

Erstellen Sie eine Windows Forms-Anwendung und ändern Sie den Ausgabetyp in Konsole.

Es wird sowohl eine Konsole als auch das Formular geöffnet.

Geben Sie hier die Bildbeschreibung ein

Pedro Rodrigues
quelle
Genau das suche ich. Einfach und ohne WINAPI.
Michael Coxon
Ich habe viele Beispiele ausprobiert, aber keines davon hat zu Ergebnissen geführt, die meine Erwartungen erfüllt haben. Diese Lösung ist jedoch genau das, was ich wollte und bei weitem die einfachste Lösung.
Unentschlossenheit
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
quelle
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));
    }
}
Lappen
quelle
1
Funktioniert einwandfrei über eine Eingabeaufforderung, jedoch nicht über Start> Ausführen oder in Visual Studio. Ersetzen Sie die AttachConsole-Zeile durch: if (! AttachConsole (-1)) AllocConsole (); Wenn AllocConsole () aufgerufen wird, sollte auch FreeConsole () aufgerufen werden, andernfalls wird der Konsolenhost nach Beendigung des Programms weiter ausgeführt.
Berend Engelbrecht
2
Was ist die beabsichtigte Verwendung von initialiseOut und initialiseError, da sie nicht verwendet werden?
Edwin
StandardHandle : uintist hier falsch ... sollte IntPtr sein, um sowohl auf x86 als auch auf x64 zu arbeiten
Dmitry Gusarov
1

Sie können jederzeit zwischen Anwendungstypen, Konsolen oder Fenstern wechseln. Sie werden also keine spezielle Logik schreiben, um den Standard zu sehen. Wenn Sie eine Anwendung im Debugger ausführen, wird im Ausgabefenster der gesamte Standard angezeigt. Sie können auch einfach einen Haltepunkt hinzufügen und in den Haltepunkteigenschaften "Wenn getroffen ..." ändern, können Sie beliebige Nachrichten und Variablen ausgeben. Sie können auch "Ausführung fortsetzen" aktivieren / deaktivieren, und Ihr Haltepunkt wird quadratisch. Also die Haltepunktmeldungen, ohne etwas in der Anwendung im Debug-Ausgabefenster zu ändern.

armagedescu
quelle
0

Lassen Sie es einfach als Window Forms-App und erstellen Sie ein einfaches Formular, um die Konsole nachzuahmen. Das Formular kann so gestaltet werden, dass es genau wie die Konsole mit dem schwarzen Bildschirm aussieht und direkt auf Tastendruck reagiert. Anschließend entscheiden Sie in der Datei program.cs, ob Sie das Hauptformular oder die ConsoleForm ausführen müssen. Zum Beispiel verwende ich diesen Ansatz, um die Befehlszeilenargumente in der Datei program.cs zu erfassen. Ich erstelle die ConsoleForm, verstecke sie zunächst und übergebe dann die Befehlszeilenzeichenfolgen an eine darin enthaltene AddCommand-Funktion, die die zulässigen Befehle anzeigt. Schließlich, wenn der Benutzer das -h oder -? Befehl rufe ich die .Show in der ConsoleForm auf und wenn der Benutzer eine Taste darauf drückt, schalte ich das Programm aus. Wenn der Benutzer das - nicht gibt? Befehl schließe ich die versteckte ConsoleForm und führe das Hauptformular aus.

gverge
quelle
2
Hallo und willkommen bei StackOverflow. Vermeiden Sie es, Fragen als Antworten zu veröffentlichen. Verwenden Sie den Kommentarbereich.
Pedro Rodrigues