16

Is it possible to embed a console window inside a WPF window?

As a little background, at first, I tried to implement a console window from scratch in WPF, which was successful except for one huge problem -- it is extremely slow. See the question here:
VT100 Terminal Emulation in Windows WPF or Silverlight

Since that does not seem to be an option I am instead looking at hosting an actual console window in my WPF application, which I've learned how to do as described here:

No output to console from a WPF application?

And that's great, but ideally, I'd like to have that console window look like it is a part of the rest of the WPF application. I know it is possible with a WinForms app as I've seen it done, involving using the SetParent Win32 API. You can see an example .NET project that does it with this CommandBar project that embeds a console window into the shell:

http://www.codeproject.com/KB/cs/commandbar.aspx

So I am hopeful it can be done with WPF as well, but I have no idea how you'd do that. Help is much appreciated (also, if you have any brilliant solutions to my original problem of creating a terminal window from scratch in WPF since that would solve my needs too).

UPDATE:

With help Reed Copsey's help I was able to get the Console Window embedded. However, of course it needed to be styled and moved or else it just looked like a regular console window inside a WPF window. I need the title bar and large borders removed. Doing research I figured out how to use the Win32 APIs to do that like this:

uint style = GetWindowLong(ConsoleManager.ConsoleWindowHandle, GWL_STYLE);
style &= ~(uint)WindowStyles.WS_CAPTION;
style &= ~(uint)WindowStyles.WS_THICKFRAME;
style &= ~(uint)WindowStyles.WS_DLGFRAME;
style &= ~(uint)WindowStyles.WS_POPUP;
SetWindowLong(ConsoleManager.ConsoleWindowHandle, GWL_STYLE, style);
MoveWindow(ConsoleManager.ConsoleWindowHandle, 0, 0, (int)WindowsFormsHost.ActualWidth, (int)WindowsFormsHost.ActualHeight, true);

However, there's one big problem. For some reason, the console window has a rendering artifact. It's as if it is not repainting itself on the bottom left and top right sides. The width of the artifact is similar to the width of the title bar and the thick border, and in fact, if I leave the thick border in the size of the artifact goes down. But simply repainting it won't help since it reappears. I can, for example, move the window off the screen and back again to fix it, but it soon reappears on its own:

rendering artifact http://img837.imageshack.us/img837/6241/renderissue.png

UPDATE 2: The effect happens even if I don't parent it into the WindowsFormsHost control. All I need to do to reproduce it is launch the console (using AllocConsole()) and then remove its title bar with SetWindowLong. This is a win7 machine.

UPDATE 3: It seems 'messing' with other windows like this isn't supported. The console window calculates its textarea assuming there is a caption, so there's no way around this. I think my only option to get console-like behavior in WPF is going to be to write a custom WinForms control and then embed that into WPF.

Metomorphose
  • 434
  • 3
  • 14
InfinitiesLoop
  • 14,349
  • 3
  • 31
  • 34
  • Did you accomplish this successfully? – Shawn Mclean Jun 24 '12 at 03:24
  • 1
    @ShawnMclean Nope... the updates represent the whole story :( – InfinitiesLoop Jun 24 '12 at 04:22
  • You might look at the Console2 (later forked as ConsoleZ) programs. They successfully host a range of command prompts in a tab control without captions/titles. – Drew Noakes Jun 14 '16 at 08:16
  • How did you embed it into the WPF? Took Reed Copsey's advice and tried using HwndHost but i'm failing miserably. – Darksody Sep 26 '20 at 06:02
  • @InfinitiesLoop Hey, Using your code from the UPDATE my cmd console gets resezed correctly but after calling `WIN32API.SetParent(cmdProcess.MainWindowHandle, panel.Handle)` and then assigning the panel into the WindowsFormsHost.Child = panel` my console does not appear in the WindowsFormsHost's panel. Do I need to do something more? – XardasLord Mar 09 '21 at 11:33

2 Answers2

10

In addition to Reed Copsey's excellent advice on embedding a console window in a WPF application, an alternative strategy which is ridiculously easy to implement would be to simply issue the command via the Process class and redirect the two streams into native WPF TextBlocks. Here's a screenshot...

enter image description here

This WPF app (wired to the Windows Explorer context menu for 'exe' files) executes the program and pipes the results into the appropriate window.

It's designed to help when you want to run a console utility and when you click it, the utility goes whizzing by in a console window and you never get to see what happened. It is also wired to 'csproj' files to run MSBuild on them from Explorer.

The point being that sometimes it's easier and more scalable to do it yourself rather than try to host a console window...

The internals of this app use this class...

public class ProcessPiper
{
    public string StdOut { get; private set; }
    public string StdErr { get; private set; }
    public string ExMessage { get; set; }
    public void Start(FileInfo exe, string args, Action<ProcessPiper>onComplete)
    {
        ProcessStartInfo psi = new ProcessStartInfo(exe.FullName, args);
        psi.RedirectStandardError = true;
        psi.RedirectStandardOutput = true;
        psi.UseShellExecute = false;
        psi.WorkingDirectory = Path.GetDirectoryName(exe.FullName);
        Task.Factory.StartNew(() =>
            {
                try
                {
                    ExMessage = string.Empty;
                    Process process = new Process();
                    process.StartInfo = psi;
                    process.Start();
                    process.WaitForExit();
                    StdOut = process.StandardOutput.ReadToEnd();
                    StdErr = process.StandardError.ReadToEnd();
                    onComplete(this);
                }
                catch (Exception ex)
                {
                    ExMessage = ex.Message;
                }
            });
    }
}

This class executes the named 'exe' file and captures the output and then calls the View Model. The whole coding drill should take about an hour or so...

Docs on the Process class are here: http://msdn.microsoft.com/en-us/library/system.diagnostics.process.aspx

Community
  • 1
  • 1
Gayot Fow
  • 8,710
  • 1
  • 35
  • 48
  • 1
    This approach does not work for interactive console applications or for showing live output from a program. – Dai Jan 08 '20 at 19:14
8

You should be able to use the same technique as the Windows Forms application you showed by reparenting into an HwndHost. You could even just adapt the Windows Forms code, and put this directly into WindowsFormsHost control.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • It couldn't be that easy, could it? :) I did this: SetParent(ConsoleManager.ConsoleWindowHandle, WindowsFormsHost.Handle); But the console window doesn't show up.. as soon as it is reparented it just vanishes. Any ideas? – InfinitiesLoop Jul 19 '10 at 21:47
  • Try putting a (Windows Forms) Panel within your WindowsFormsHost control, and setting the parent to the panel's handle (instead of the control itself). – Reed Copsey Jul 19 '10 at 22:17
  • You know I think it was working, it's just that the window is sometimes out of view within the parent. I will need to figure out next how to bind the size of the two together and to hide the title bar, etc. You helped a lot, thanks :) – InfinitiesLoop Jul 19 '10 at 22:20
  • Reed -- see my update to the question above. The console window is having rendering issues using this technique :| – InfinitiesLoop Jul 20 '10 at 02:03
  • @InfinitiesLoop: Try setting owner drawn to true on your Windows Forms control, as well. That may help it receive the proper messages... – Reed Copsey Jul 20 '10 at 02:23
  • Thanks Reed but (1) I cannot find any such property, and (2) interestingly enough, the rendering issues happen even if I don't parent the console window with the WindowsFormsHost. All I do is use SetWindowLong() to remove the title bar, and it happens. It's like the console window thinks its text area is still lessened by the title bar even though it's gone, resulting in unpainted areas. I tried calling SetWindowsPos afterwards as MS seems to recommend it to clear 'cached values'... no effect :( – InfinitiesLoop Jul 20 '10 at 02:37
  • Well looks like I'm out of luck. Found out this simply isn't supported. The only other thing I could do is position the console so just the textarea shows up within a clipping parent, but that's hacky. I'm going to look at writing a WinForms control that acts like a console and then embedding that. Still, you answered my question even though it didn't work out very well, so re-accepting the answer. – InfinitiesLoop Jul 20 '10 at 16:13
  • @InfinitiesLoop: Sorry to hear this doesn't work - it's not completely surpising to me, though. I'd recommend looking at using WPF text runs to handle this. They'll perform MUCH better than Windows Forms controls, especially as you add a lot of text. I would avoid RTF based controls, unless you really need that extra formatting functionality. – Reed Copsey Jul 20 '10 at 16:17
  • I tried making it so each row was a single textblock with 80 runs in it. Since each character can be a different color, etc, I don't see any other way other than a complicated approach of trying to merge similar styled consecutive characters (and then even that would still degrade if the screen had complicated characters on it). The performance didn't seem any different. Still pretty darn slow. – InfinitiesLoop Jul 20 '10 at 19:25
  • @ReedCopsey Hey, using InfinitiesLoop's code from the UPDATE my cmd console gets resezed correctly but after calling `WIN32API.SetParent(cmdProcess.MainWindowHandle, panel.Handle)` and then assigning the panel into the WindowsFormsHost.Child = panel` my console does not appear in the WindowsFormsHost's panel. Do I need to do something more? – XardasLord Mar 09 '21 at 11:34