18

I'd like some tips-in-the-right-direction or even ready solutions to this problem and I'm pretty stuck (I'm just beginner/intermediate):

I'm trying to implement a SSH in my application. The SSH-backend works fine and such, but I'm stuck at the frontend. What WPF-Combination would present me with an adequate solution to emulate a console? Put aside a complete terminal-emulation, I'd be happy to simply readline/writeline into something that looks like a console :-)

My best approach yet was a 80x50 Grid of single characters resulting in 4000 single cells and that feels like a total overkill.

Another idea was to make a console-Appl. bound to a wpf-window in another project. But...is that even possible and how?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
FrankyTheDumb
  • 191
  • 1
  • 1
  • 4
  • 10
    Nothing looks more like a console window than... a console window. – Simon Whitehead Feb 19 '13 at 01:16
  • 10
    This could have been helpful if it would have been helpful. Please, be so kind to elaborate. – FrankyTheDumb Feb 19 '13 at 01:41
  • You could style a ListBox and use TextBoxes as the items and have tha last TextBox editable to type a command, on enter handle the text entered make the TextBox read only and add a new Textbox to the bottom – sa_ddam213 Feb 19 '13 at 01:45
  • 5
    @user2085300 My point was.. if you want a console.. why not just write a console application? – Simon Whitehead Feb 19 '13 at 03:11
  • Though this would have been the most obvious way, it's not what I intended, otherway I would have simply chose a console :-) Maybe I just didn't make myself very clear. My bad. – FrankyTheDumb Feb 19 '13 at 20:24

3 Answers3

39

Given that you want to emulate a console, I'd do it like this. Note that you'd have to handle the commands and outputting the results yourself.

page.xaml

<Window x:Class="ConsoleEmulation.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" MinHeight="350" MinWidth="525" Height="350" Width="525">
    <Grid>
        <ScrollViewer Name="Scroller" Margin="0" Background="Black">
            <StackPanel>
                <ItemsControl ItemsSource="{Binding ConsoleOutput, Mode=OneWay}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Path=.}" Foreground="White" FontFamily="Consolas"/>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
                <TextBox Text="{Binding ConsoleInput, Mode=TwoWay}" Background="Black" Foreground="White" FontFamily="Consolas" Name="InputBlock" BorderBrush="{x:Null}" SelectionBrush="{x:Null}" />
            </StackPanel>
        </ScrollViewer>
    </Grid>
</Window>

page.xaml.cs

public partial class MainWindow : Window
{
    ConsoleContent dc = new ConsoleContent();

    public MainWindow()
    {
        InitializeComponent();
        DataContext = dc;
        Loaded += MainWindow_Loaded;
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        InputBlock.KeyDown += InputBlock_KeyDown;
        InputBlock.Focus();
    }

    void InputBlock_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Enter)
        {
            dc.ConsoleInput = InputBlock.Text;
            dc.RunCommand();
            InputBlock.Focus();
            Scroller.ScrollToBottom();
        }
    }
}

public class ConsoleContent : INotifyPropertyChanged
{
    string consoleInput = string.Empty;
    ObservableCollection<string> consoleOutput = new ObservableCollection<string>() { "Console Emulation Sample..." };

    public string ConsoleInput
    {
        get
        {
            return consoleInput;
        }
        set
        {
            consoleInput = value;
            OnPropertyChanged("ConsoleInput");
        }
    }

    public ObservableCollection<string> ConsoleOutput
    {
        get
        {
            return consoleOutput;
        }
        set
        {
            consoleOutput = value;
            OnPropertyChanged("ConsoleOutput");
        }
    }

    public void RunCommand()
    {
        ConsoleOutput.Add(ConsoleInput);
        // do your stuff here.
        ConsoleInput = String.Empty;
    }


    public event PropertyChangedEventHandler PropertyChanged;
    void OnPropertyChanged(string propertyName)
    {
        if (null != PropertyChanged)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}
Hille
  • 2,123
  • 22
  • 39
ZombieSheep
  • 29,603
  • 12
  • 67
  • 114
  • This pretty nailed it! Excellent! That's the stuff I wasted hours upon and awefully failed :-) Thanks a lot! – FrankyTheDumb Feb 19 '13 at 20:21
  • I'm not able to get this answer to work in ms visual studio and would appreciate build/project instructions or if needed, any corrections. – user3461121 Feb 05 '18 at 02:48
  • @user3461121: Could you elaborate on 'not able to get this answer to work'- what's the error, or what's not happening that you expect? Without any symptoms of the problem, it's difficult to diagnose what it could be. – Flynn1179 Apr 10 '18 at 09:01
  • @Flynn1179: A project or including usings would have been easier on the uninitiated. No problem for me now, though, and an excellent answer. – user3461121 Apr 11 '18 at 18:08
7

Did you know that you can display a Console window from your application by using AllocConsole?

This is a simple way to create a "dual-mode" application can be a console or windows forms application.

[DllImport("kernel32")]
static extern bool AllocConsole();

Or you can use this:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="30"/>
    </Grid.RowDefinitions>
    <TextBlock Text="Console contents..." HorizontalAlignment="Stretch" VerticalAlignment="Stretch" x:Name="ConsoleTextBlock"/>
    <DockPanel Grid.Row="1">
        <TextBox/>
    </DockPanel>
</Grid>

For better looks, replace the TextBlock with a ListBox and style the ItemTemplate accordingly.

animaonline
  • 3,715
  • 5
  • 30
  • 57
  • 1
    Though this is pretty cute and simple it's not really what I was looking for. I'd like to either put a console into a wpf-window (not running seperately) OR emulate the console itself. But thanks a lot anyway! – FrankyTheDumb Feb 19 '13 at 20:19
  • 2
    Then, I suggest you take a look at this: http://www.codeproject.com/Articles/247280/WPF-Command-Prompt – animaonline Feb 19 '13 at 21:22
1

I haven't done it myself, however it is one of my "I'll do it if I have time"-projects. Thus I am still looking for an existing implementation :-P

Anyways some thoughts:

The applroach to use Visuals (i.e. Ellipses, Textblocks) is probably not a good Idea. Just think of what has to happen if you want like 200x100 characters. Maybe even a backbuffer. Holding it all in memory + drawing it....it will be incredibly slow.

Therefore the better (or even right) approach is to "draw yourself". Since WPF is backbuffered and you don't want to display an arbitrary bitmap the most likly approach would be to create a new UserControl and override it's Paint-Method. You ma prefer to derive from Control, but UserControl may have Content, so you can show something like a connection indicator icon inside.

Architecture-wise I'd suggest to create a dependecy property Buffer (ConsoleBuffer) that holds the console buffer-model. Another DP would hold the top-left positon Location (long). It determines where to start the display (while you have a look behind). The console model I would make a class that contains a char[] and a Color[] (one dimensional). Use line breaking and \n characters to make lines (because this is the character of a console). Then if you resize the control it will re-flow without the buffer needing to be re-allocated. You can work with **ConsoleBuffer**s of different sizes (for a different number of look behind characters).

ConsoleBuffer.Write(string s) is your method to do stuff.

Maybe it is advisable to hold arrays of arrays char[][] to represent lines.... but that is up to finding out while programming.

Robetto
  • 739
  • 7
  • 20