8

Related to my earlier question:

I came up with the following approach, in which I created this WinForms control:

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class ConsoleWindow : Control
{
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool AllocConsole();

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool FreeConsole();

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

    private static ConsoleWindow _theWindow;

    public ConsoleWindow()
    {
        if (!DesignMode)
        {
            if (_theWindow != null)
            {
                throw new Exception("An application can have only one ConsoleWindow");
            }
            _theWindow = this;
            AllocConsole();
            var newOut = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true };
            Console.SetOut(newOut);
            Console.SetError(newOut);

            var consoleHwnd = GetConsoleWindow();

            SizeChanged += (sender, args) =>
            {
                SetWindowPos(consoleHwnd, IntPtr.Zero, 0, 0, Width, Height, 0);
            };

            SetWindowLong(consoleHwnd, -16 /*GWL_STYLE*/, 0x50000000 /* WS_CHILD|WS_VISIBLE*/);
            SetParent(consoleHwnd, Handle);
            SetWindowPos(consoleHwnd, IntPtr.Zero, 0, 0, 0, 0, 0);
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing && _theWindow != null)
        {
            FreeConsole();
            _theWindow = null;
        }

        base.Dispose(disposing);
    }
}

... which I can then use in my WPF application via XAML such as this:

    <WindowsFormsHost>
        <WindowsFormsHost.Child>
            <controls:ConsoleWindow></controls:ConsoleWindow>
        </WindowsFormsHost.Child>
    </WindowsFormsHost>

It mostly works, except that mouse interaction seems impaired. When you create a console window (normally operates as a top-level window) you can use the mouse to click/drag an arbitrary selection, but this no longer works after parenting it as a child control as I have done. I can right-click to invoke the console window's context menu to select/copy all text, but I can't do a click/drag selection.

Is there a way to fix this (missing/incorrect styles or message routing perhaps?) so that I can interact with the console window as expected, or is there a fundamental problem with parenting the console window in this manner?

C Robinson
  • 411
  • 4
  • 18
  • WinForms and WPF interop is a bit of pain, especially the focus handing. Even Microsoft was unable to solve some issues, so there will always be some issues in those WPF apps that use hosted WinForms controls. You're introducing yet another level - a console inside of a WinForms control. I would just recommend to not go this way. – dymanoid Feb 26 '19 at 10:20
  • @CRobinson If you found the answer useful please mark it as an answer. – Pvria Ansari Mar 05 '19 at 07:59
  • I had did some tests myself. It appreas that if console window worked as a top level window, mouse selection was available. But if console window worked as a chile window, no matter what kind of parent it had, mouse selection was disabled and context menu became available. So, this is more like a specification of console window to me. – Alex.Wei Mar 05 '19 at 10:03
  • @PouriaAnsari While your answer presents a way to display output, it doesn't address my actual question - parenting the console window (as when you use AllocConsole) – C Robinson Mar 05 '19 at 14:41
  • @Alex.Wei It appears to me that the context menu is always available, works consistently regardless of whether the console window is parented or not; it's left-mouse interaction that appears to be broken. – C Robinson Mar 05 '19 at 14:44
  • @CRobinson Forgot to said that I was using win10. The default right click action for top level console window is paste. And only child console window will show context menu. – Alex.Wei Mar 06 '19 at 06:54

1 Answers1

1

There is a control written by Dave Kerr which Solves Your problem.

Use this link: https://www.codeproject.com/articles/335909/embedding-a-console-in-a-c-application

this should be added that it uses a class named ProcessInterface which is included in the link.

and at last this part of code is the solution:

/// <summary>
        /// Handles the KeyDown event of the richTextBoxConsole control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.KeyEventArgs"/> instance containing the event data.</param>
        void richTextBoxConsole_KeyDown(object sender, KeyEventArgs e)
        {
            bool inReadOnlyZone = richTextBoxConsole.Selection.Start.CompareTo(inputStart) < 0;

            //  If we're at the input point and it's backspace, bail.
            if (inReadOnlyZone && e.Key == Key.Back)
                e.Handled = true;;

            //  Are we in the read-only zone?
            if (inReadOnlyZone)
            {
                //  Allow arrows and Ctrl-C.
                if (!(e.Key == Key.Left ||
                    e.Key == Key.Right ||
                    e.Key == Key.Up ||
                    e.Key == Key.Down ||
                    (e.Key == Key.C && Keyboard.Modifiers.HasFlag(ModifierKeys.Control))))
                {
                    e.Handled = true;
                }
            }

            //  Is it the return key?
            if (e.Key == Key.Return)
            {
                //  Get the input.
                //todostring input = richTextBoxConsole.Text.Substring(inputStart, (richTextBoxConsole.SelectionStart) - inputStart);

                //  Write the input (without echoing).
                //todoWriteInput(input, Colors.White, false);
            }
        }
Pvria Ansari
  • 406
  • 4
  • 20