3

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 its Text 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.

C Robinson
  • 411
  • 4
  • 18
  • Possible duplicate of [making a .NET TextBox work FIFO style](https://stackoverflow.com/questions/18988170/making-a-net-textbox-work-fifo-style) – eocron Feb 21 '19 at 15:58
  • @eocron I've been down the road of using TextBox and/or other GUI controls to mimic a console window; they all seem to have issues of one sort or another. The intent of this question is to explore the possibility of hosting an actual console window in a WPF parent. – C Robinson Feb 21 '19 at 16:14
  • I have seen plenty of controls used this way and have one myself. Attaching console to your WPF application is bad idead in many terms. – eocron Feb 22 '19 at 05:35
  • @eocron I'd like to understand the issues a little better. Please see my related question https://stackoverflow.com/questions/54830047/why-cant-i-interact-with-a-console-window-after-parenting-it-into-my-wpf-applic – C Robinson Feb 22 '19 at 15:18

1 Answers1

2

From what I've gathered your intent is only for output (not input), then something simple like Console.SetOut fits the bill.

That will redirect console output to a TextWriter of your choice. Which you can then display however you want.

Granted, you could also use one of the many logging libraries instead of writing to the console. Or use something like Debug.Write etc...

But this is a fast and simple way to read anything output to the console and display however you want to. Or redirect to a file if you wish.

Regarding performance, I'd have to see the code you're using. I personally would use ListBox over TextBox for large amounts of logging data.

Zer0
  • 7,191
  • 1
  • 20
  • 34