2014/4/21

[WinForm] 如何在 WinForm 應用程式中開啟 Console 視窗

時常寫 Windows Form 程式的朋友一定知道, 我們在 Windows Form 程式中一樣可以使用 Console.Write() 方法。這些訊息可以在 Visual Studio 中的「輸出視窗」裡看見。然而, Visual Studio 本身就有許多訊息顯示在輸出視窗裡, 所以我們時常有可能漏掉某些訊息沒看到, 或者需要不停地往回捲動, 才能找到。此外, 如果你希望這些訊息是能夠讓使用者看到的, 該怎麼辦?

當然, 你可以在 WinForm 裡放一個 Label 或者 TextBox 控制項來顯示這些訊息。但是這些方式有時候就是遠遠不如讓你的 Console 輸出導到一個真正的 Console Window 去。因為 Console Window 的一個重要特色就是它永遠會把焦點停留最下面(也是最新)的訊息上, 同時保留往上回捲的能力(雖然有長度限制), 而且因為它沒有 Windows Form 的 Paint 事件, 所以它不會有 UI Lock 的問題(請參考「[入門] .Net 非同步處理與同步機制全解析 (二)」這篇文章裡所描述的情境)。

那麼, 我們要怎麼做, 才能把 Console 的輸出轉移到一個真正的 Console Window? 方法如下:

public Form1()
{
    InitializeComponent();
    AllocConsole();
    Console.Beep();
    ConsoleColor oriColor = Console.ForegroundColor;
    Console.ForegroundColor = ConsoleColor.Yellow; 
    Console.WriteLine("* Don't close this console window or the application will also close.");
    Console.WriteLine();
    Console.ForegroundColor = oriColor;
}

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool AllocConsole();

[DllImport("Kernel32")]
public static extern void FreeConsole();

在上述程式中, 呼叫 AllocConsole() 方法可以開啟一個 Console Window, 然後你的 Console 輸出就會出現在那個 Window 裡。你甚至可以使用 Console.Beep(); 讓它叫一聲。呼叫 FreeConsole() 方法則可以把那個 Console Window 關閉。

呼叫 FreeConsole() 方法以關閉 Console Window 不會出現任何問題。但是如果使用者自己把那個 Console Window 關掉(包括按下右上角的關閉按鈕、按下 Ctrl-C 等等), 那麼整個應用程式(包括你的 WinForm)都一併關掉。這個關閉的動作是強制的, 你甚至無法在 FormClosing 事件中攔截。所以為保險起見, 我在一開始就秀了一段警告訊息。

不過, 我們倒是有辦法防止使用者按下 Ctrl-C 而將應用程式關閉。方法是攔截 Console.CancelKeyPress 事件:

Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);
static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
    e.Cancel = true;
}

當然, 或許你也可以選擇讓使用者按下 Ctrl-C 以做些其它事情, 例如中斷目前的作業之類的。在這種情況下, 把上面這個 Console_CancelKeyPress 事件處理函數改寫即可。

如果你在程式中呼叫 FreeConsole() 把 Console 關掉, 你可以再次呼叫 AllocConsole() 把它重新叫出來, 但是先前的訊息並不會保留。這應該是少數必須稍作留意的地方。

以下是完整的程式列表:

public Form1()
{
    InitializeComponent();
    AllocConsole();
    Console.CancelKeyPress += new 
        ConsoleCancelEventHandler(Console_CancelKeyPress);
    Console.Beep();
    ConsoleColor oriColor = Console.ForegroundColor;
    Console.ForegroundColor = ConsoleColor.Yellow; 
    Console.WriteLine(
        "* Don't close this console window or the application will also close.");
    Console.WriteLine();
    Console.ForegroundColor = oriColor;
}
 
static void Console_CancelKeyPress(object sender, 
        ConsoleCancelEventArgs e)
{
    e.Cancel = true;
}

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool AllocConsole();
 
[DllImport("Kernel32")]
public static extern void FreeConsole();

話說回來, WinForm 的文字方塊其實也可以讓焦點隨時保持在最下面 (最新) 的地方。以 RichText 控制項為例, 方法如下:

foreach (Bala Bala in Bala) {
 rt.Text = 'Bala Bala...';
 rt.SelectionStart = rt.Text.Length;
 rt.ScrollToCaret();
}

不過你仍然可能遇到前面提過的 UI Lock 的問題, 所以你必須把你的程式改成非同步作業, 才能看到效果。有興趣的朋友可以參考[入門] .Net 非同步處理與同步機制全解析 (二)」這篇文章裡介紹的做法。

沒有留言:

張貼留言