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?
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));
}
}
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.
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.