I'd like to include console window functionality in my WPF application for several purposes, including:
- On-screen logging
- Display results of console commands executed by the application
- Diagnostics
Two techniques I've used are:
- Create a
TextBox
and set itsText
property by assignment or data binding. This nicely integrates the view into the application, but has performance issues as the text "buffer" gets large. - Make an interop call to
AllocConsole
to create a console window. Has no performance issues for relatively large text "buffers", but is created as a top-level window (visually separate from the application with its own title bar, etc.).
Ideally, I'd like to create a console window (like AllocConsole) as a child of my WPF application window, analogous to the way a Forms window can be hosted as a child window of a WPF application, so that it can live in a tab, etc..
So far as I can tell, There is no way to change a window's parent once it is created, the only way to create a new console window is AllocConsole, and it takes no parameters, so a parent window (e.g. a Forms host) cannot be specified. Am I missing something?
The point of my question is not to approximate/emulate console-like behavior using TextBox, ListBox, etc., but to host an actual console window as a GUI element of a WPF application.
EDIT
Where I am at so far:
Snippet of main window XAML:
<TabControl>
<TabItem Header="Log">
<Grid>
<WindowsFormsHost x:Name="FormsHost">
</WindowsFormsHost>
</Grid>
</TabItem>
</TabControl>
Main Window code-behind:
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Forms;
public partial class MainWindow : Window
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool AllocConsole();
[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll")]
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
public MainWindow()
{
InitializeComponent();
AllocConsole();
var newOut = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true };
Console.SetOut(newOut);
Console.SetError(newOut);
var ptr = GetConsoleWindow();
FormsHost.Child = new Control();
SetWindowLong(ptr, -16, GetWindowLong(ptr, -16) | 0x40000000);
SetParent(ptr, FormsHost.Child.Handle);
SetWindowPos(ptr, IntPtr.Zero, 0, 0, 300, 300, 0);
Console.WriteLine("Hello, World");
}
}
The above does bring the console window into the WPF tab, but it still has the appearance of a top-level window and it is still sizeable and moveable within its host. Also, while I can right-click in the console area to get the context menu for text selection, I can't use the mouse to select text.
UPDATE
I solved the problem with window style by changing
SetWindowLong(ptr, -16, GetWindowLong(ptr, -16) | 0x40000000)
to
SetWindowLong(ptr, -16, 0x50000000)
and sorted out window resizing, but I still have a problem with mouse interaction - right-mouse works to activate the context menu, but left mouse to make a selection does not.