Jak wyświetlić wyjście / okno konsoli w aplikacji formularzy?

139

Aby od razu utknąć, oto bardzo podstawowy przykład:

using System;
using System.Windows.Forms;

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

Jeśli skompiluję to z domyślnymi opcjami (używając csc w wierszu poleceń), zgodnie z oczekiwaniami, skompiluje się do aplikacji konsoli. Ponadto, ponieważ zaimportowałem System.Windows.Forms, wyświetli się również okno komunikatu.

Teraz, jeśli użyję opcji /target:winexe, która moim zdaniem jest taka sama, jak wybranie Windows Applicationopcji w ramach projektu, zgodnie z oczekiwaniami zobaczę tylko okno komunikatów i nie będę widzieć wyjścia konsoli.

(W rzeczywistości w momencie uruchomienia z wiersza poleceń mogę wydać następne polecenie, zanim aplikacja zostanie zakończona).

Więc moje pytanie brzmi - wiem, że możesz mieć "okna" / formularze wyjściowe z aplikacji konsoli, ale czy w ogóle istnieje możliwość wyświetlenia konsoli z aplikacji Windows?

Wil
źródło
2
Jaka jest różnica między nimi? Dlaczego nie skompilować po prostu jako konsoli i wyświetlić formularza.
Doggett
7
@Doggett, proste - uczę się i chcę zrozumieć, dlaczego / jak to zrobić, nawet jeśli nigdy nie skończę używać tego w prawdziwej aplikacji .... W tej chwili myślę o opcji, która daje dodatkowe polecenia / wyjście takie jak w VLC, jednak TBH, nie potrzebuję tego - znowu tylko się uczę i chcę to zrozumieć!
Wil
Dokonałem
vivanov

Odpowiedzi:

157

ten powinien działać.

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
źródło
8
Niesamowite, wydaje się, że to pytanie było często zadawane, to jedyna rzeczywista odpowiedź na pytanie, które udało mi się znaleźć, +1
RobJohnson
6
Główny problem: po zamknięciu wszystkie aplikacje zamykają się.
Mark
4
Testowałem na Windows 8 i Windows 10: - AttachConsole działa z pola cmd - AllocConsole działa z Visual Studio. Gdy wymagana jest alokacja, AttachConsole zwraca wartość false. Powinieneś także wywołać FreeConsole () przed zamknięciem aplikacji w trybie konsoli. W moim programie użyłem kodu Matthew Strawbridge'a (patrz poniżej), ze zmodyfikowaną linią AttachConsole () na: if (! AttachConsole (-1)) AllocConsole ();
Berend Engelbrecht
Czy to zadziała w kontrolce użytkownika? Pracuję nad utworzeniem formantu SSH jako komponentu winforms przy użyciu Granados (na przykład) i jest to tylko składnik tła. Chciałbym dodać ładne opakowanie, aby wyświetlać i używać konsoli również w komponencie.
Kraang Prime
2
To nie jest świetne, po uruchomieniu z wiersza poleceń otwiera się osobne okno konsoli, a podczas uruchamiania z wiersza poleceń i próby >przekierowania danych wyjściowych otrzymuję osobne okno konsoli i zero danych wyjściowych w moim pliku.
uglycoyote
147

Być może jest to zbyt uproszczone ...

Utwórz projekt Windows Form ...

Następnie: Właściwości projektu -> Aplikacja -> Typ wyniku -> Aplikacja konsoli

Wtedy konsola i Formularze działają razem, działa dla mnie

Chaz
źródło
2
Wydaje się najprostsze, naprawiłem również mój problem.
dadude999
2
To zdecydowanie najlepsze rozwiązanie! Inni są sprytni, ale dość skomplikowani
LM.Croisez
3
Prosty i działał dobrze. To powinna być akceptowana odpowiedź.
madu
7
Chociaż tak, technicznie można to wykorzystać, aby zezwolić na to, o co prosi plakat - nie jest to świetne rozwiązanie. W ten sposób, jeśli następnie uruchomisz aplikację winforms za pomocą GUI - otworzy się również okno konsoli. W takim przypadku potrzebowałbyś czegoś bardziej podobnego do odpowiedzi Mike'a de Klerka.
Justin Greywolf
2
Jest to jedyne rozwiązanie, w którym udało mi się sprawić, że moja aplikacja Winforms zapisuje dane wyjściowe do konsoli po uruchomieniu z wiersza poleceń lub zapisuje do pliku po przekierowaniu w wierszu poleceń z >. Liczyłem jednak na rozwiązanie, które wyjaśniłoby, jak działać jako „aplikacja konsolowa” tylko przez pewien czas (tj. Programowo włącza cokolwiek, co robi zmiana tego tajemniczego ustawienia programu Visual Studio). Czy ktoś wie, jak to działa pod maską?
uglycoyote
68

Jeśli nie martwisz się otwieraniem konsoli na polecenie, możesz przejść do właściwości swojego projektu i zmienić go na aplikację konsolową

zrzut ekranu przedstawiający zmianę typu projektu.

Spowoduje to nadal wyświetlenie formularza, a także wyskakujące okno konsoli. Nie możesz zamknąć okna konsoli, ale działa jako doskonały tymczasowy rejestrator do debugowania.

Pamiętaj tylko, aby go wyłączyć przed wdrożeniem programu.

gunr2171
źródło
1
Ładny. Rozwiązuje to problem, który mam z moją aplikacją formularzy, który muszę mieć możliwość wyświetlania danych wyjściowych w oknie konsoli, jednocześnie obsługując przekierowywanie danych wyjściowych do pliku. I nie muszę ręcznie podłączać żadnej konsoli ...
Kai Hartmann
3
@JasonHarrison Jeśli zamkniesz okno konsoli, program zostanie zamknięty. Również okno jest zawsze otwarte podczas działania programu.
gunr2171
2
@ gun2171: Dzięki. Wady tego podejścia są odnotowane w odpowiedzi: okno konsoli pojawi się, jeśli aplikacja zostanie uruchomiona przez podwójne kliknięcie, menu Start itp.
Jason Harrison
Czy jest jakiś sposób na śledzenie tego zamknięcia konsoli?
Elshan
17

Możesz zadzwonić AttachConsoleza pomocą pinvoke, aby uzyskać okno konsoli dołączone do projektu WinForms: http://www.csharp411.com/console-output-from-winforms-application/

Możesz również rozważyć Log4net ( http://logging.apache.org/log4net/index.html ) w celu skonfigurowania wyjścia dziennika w różnych konfiguracjach.

Adam Vandenberg
źródło
+1 - Wow, liczyłem na console.show lub coś podobnego! o wiele bardziej skomplikowane niż myślałem! Na razie zostawię otwarte, na wypadek gdyby była lepsza / łatwiejsza odpowiedź.
Wil
To zadziałało dla mnie, AllocConsole () nie zadziałało, ponieważ pojawiło się nowe okno konsoli (nie zagłębiałem się w AllocConsole, może jednak coś tam przeoczyłem).
derFunk
14

To zadziałało dla mnie, aby przesłać dane wyjściowe do pliku. Zadzwoń do konsoli za pomocą

cmd / c "C: \ ścieżka \ do \ twoja \ aplikacja.exe"> mojplik.txt

Dodaj ten kod do swojej aplikacji.

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

Znalazłem ten kod tutaj: http://www.csharp411.com/console-output-from-winforms-application/ Pomyślałem, że warto go również tutaj zamieścić.

Mike de Klerk
źródło
5
Działa to świetnie, Z WYJĄTKIEM, że teraz nie działa w Windows 8 i Windows 10. Przez niepowodzenie mam na myśli, że nie ma wyjścia poza dodatkowym monitem (jeśli to jest wskazówka). Ktoś zasugerował AllocConsole, ale właśnie mignął okno cmd.
Simon Heffer
Wypróbowałem również odpowiedź Chaza powyżej, ale to daje nową konsolę w Windows 7 (chociaż nie w 8 lub 10). Potrzebuję tylko opcji uruchomienia z przekierowaniem w wierszu poleceń lub uruchomienia jako GUI, jeśli nie ma żadnych argumentów.
Simon Heffer,
Próbowałem tego, ale nie zadziałało. Po prostu AttachConsole(ATTACH_PARENT_PROCESS)otrzymuję wyjście konsoli, ale przekierowanie go w wierszu poleceń z >nie działa. Kiedy spróbuję tej odpowiedzi, nie mogę uzyskać żadnego wyniku ani w konsoli, ani w pliku.
uglycoyote
12

Zasadniczo mogą się tu zdarzyć dwie rzeczy.

Wyjście konsoli Program winForms może dołączyć się do okna konsoli, które go utworzyło (lub do innego okna konsoli, lub też do nowego okna konsoli, jeśli jest to pożądane). Po podłączeniu do okna konsoli Console.WriteLine () etc działa zgodnie z oczekiwaniami. Jedynym mankamentem tego podejścia jest to, że program natychmiast zwraca kontrolę do okna konsoli, a następnie kontynuuje do niego pisanie, więc użytkownik może również pisać w oknie konsoli. Myślę, że możesz użyć start z parametrem / wait, aby sobie z tym poradzić.

Link do uruchomienia składni polecenia

Przekierowane wyjście konsoli Dzieje się tak, gdy ktoś przesyła dane wyjściowe z twojego programu w inne miejsce, np.

twojaaplikacja> plik.txt

W tym przypadku podłączenie do okna konsoli skutecznie ignoruje orurowanie. Aby to zadziałało, możesz wywołać Console.OpenStandardOutput (), aby uzyskać uchwyt do strumienia, do którego dane wyjściowe powinny być przesłane potokiem. Działa to tylko wtedy, gdy dane wyjściowe są przesyłane potokiem, więc jeśli chcesz obsłużyć oba scenariusze, musisz otworzyć standardowe wyjście, zapisać do niego i dołączyć do okna konsoli. Oznacza to, że dane wyjściowe są wysyłane do okna konsoli i do potoku, ale jest to najlepsze rozwiązanie, jakie mogłem znaleźć. Poniżej kodu, którego używam do tego.

// 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
źródło
Nie mogłem pisać do konsoli; dołączenie procesu nadrzędnego najpierw załatwiło sprawę. Dziękuję Ci.
Pupper
Wydawałoby się, że ta odpowiedź wymaga przepisania wszystkich wywołań, Console.WriteLineaby zamiast tego wywoływać nowe WriteLinezdefiniowane powyżej. Mimo że próbowałem, że nie byłem w stanie za pomocą tego kodu uzyskać przekierowania do pliku podczas uruchamiania aplikacji w wierszu poleceń i przekierowywania z >do pliku.
uglycoyote
@uglycoyote, upewnij się, że tworzysz GUIConsoleWriter tak wcześnie, jak to możliwe w swojej aplikacji, w przeciwnym razie nie zadziała z powodu tajemniczych typów okien. Twierdziłbym, że hermetyzowanie wywołań Console.WriteLinejest po prostu dobrą praktyką, ponieważ pozwala testować i łatwo zmieniać miejsca, w których się logujesz (na przykład możesz chcieć rozpocząć logowanie do usługi rejestrowania w chmurze, takiej jak PaperTrail, lub cokolwiek innego )
cedd
to działało dobrze dla mnie w Win10 bez nawetStreamWriter _stdOutWriter;
TS
Odpowiedzią jest potokowanie, ale zamiast do pliku, po prostu użyj WIĘCEJ, na przykład: twojaaplikacja | jeszcze ; proszę odnieść się do stackoverflow.com/a/13010823/1845672
Roland
12

Utwórz aplikację Windows Forms i zmień typ danych wyjściowych na Console.

Spowoduje to otwarcie zarówno konsoli, jak i formularza .

wprowadź opis obrazu tutaj

Pedro Rodrigues
źródło
1
Właśnie tego szukam. Proste i nie używające WINAPI.
Michael Coxon
1
Wypróbowałem wiele przykładów, ale żaden z nich nie przyniósł rezultatów spełniających moje oczekiwania. To rozwiązanie jest jednak dokładnie tym, czego chciałem i zdecydowanie najłatwiejszym rozwiązaniem.
inexcitus
1
Świetne rozwiązanie, dzięki
Moti Hamo
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
źródło
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));
    }
}
szmata
źródło
1
Działa dobrze z wiersza polecenia, ale nie z Start> Uruchom lub w programie Visual Studio. Aby to działało we wszystkich przypadkach, zamień wiersz AttachConsole na: if (! AttachConsole (-1)) AllocConsole (); Jeśli wywoływana jest AllocConsole (), należy również wywołać FreeConsole (), w przeciwnym razie host konsoli będzie kontynuował działanie po zakończeniu programu.
Berend Engelbrecht
2
Jakie jest zamierzone zastosowanie initialiseOut i initialiseError, skoro nie są używane?
Edwin
StandardHandle : uintjest źle tutaj ... powinien być IntPtr, aby działał zarówno na x86, jak i x64
Dmitry Gusarov
1

W każdej chwili możesz przełączać się między typami aplikacji, na konsole lub okna. Więc nie będziesz pisać specjalnej logiki, aby zobaczyć standardowe wyjście. Ponadto, podczas uruchamiania aplikacji w debugerze, zobaczysz całe standardowe wyjście w oknie wyjściowym. Możesz także po prostu dodać punkt przerwania, a we właściwościach punktu przerwania zmienić „When Hit ...”, możesz wyprowadzać dowolne komunikaty i zmienne. Możesz także zaznaczyć / odznaczyć opcję „Kontynuuj wykonywanie”, a punkt przerwania będzie miał kształt kwadratu. Tak więc komunikaty punktu przerwania bez zmiany czegokolwiek w aplikacji w oknie wyjściowym debugowania.

armagedescu
źródło
0

Dlaczego nie zostawić go jako aplikacji Window Forms i utworzyć prosty formularz naśladujący konsolę. Formularz może wyglądać podobnie jak ekranowana na czarno konsola i odpowiadać bezpośrednio na naciśnięcie klawisza. Następnie w pliku program.cs decydujesz, czy chcesz uruchomić formularz główny, czy ConsoleForm. Na przykład używam tego podejścia do przechwytywania argumentów wiersza poleceń w pliku program.cs. Tworzę ConsoleForm, początkowo ukrywam go, a następnie przekazuję ciągi poleceń do funkcji AddCommand, która wyświetla dozwolone polecenia. Wreszcie, jeśli użytkownik podał -h lub -? polecenie, wywołuję .Show w ConsoleForm i kiedy użytkownik naciśnie dowolny klawisz, zamykam program. Jeśli użytkownik nie poda -? polecenie, zamykam ukryty ConsoleForm i uruchamiam główny formularz.

gverge
źródło
1
nie jestem pewien, czy to liczy się jako pytanie. Daje pełny opis krok po kroku, jak zrobić to, co sugeruje, nawet jeśli rzeczywisty kod byłby fajny.
John Lord