C# 如何在 WPF 中模拟控制台?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14948171/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 13:30:58  来源:igfitidea点击:

How to emulate a console in WPF?

c#wpfsshconsoleemulation

提问by FrankyTheDumb

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 :-)

我正在尝试在我的应用程序中实现 SSH。SSH 后端工作正常等等,但我被困在前端。什么 WPF-Combination 会为我提供模拟控制台的适当解决方案?抛开一个完整的终端模拟,我很乐意简单地将 readline/writeline 变成一个看起来像控制台的东西:-)

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

我最好的方法是一个 80x50 的单个字符网格,导致 4000 个单个单元格,这感觉完全是矫枉过正。

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

另一个想法是制作一个控制台应用程序。绑定到另一个项目中的 wpf 窗口。但是......这甚至可能吗?如何?

采纳答案by ZombieSheep

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

鉴于您想模拟控制台,我会这样做。请注意,您必须自己处理命令并输出结果。

page.xaml

页面.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

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

回答by animaonline

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

您是否知道可以使用 AllocConsole 从应用程序显示控制台窗口?

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

这是创建“双模式”应用程序的一种简单方法,可以是控制台或 Windows 窗体应用程序。

[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.

为了获得更好的外观,请将 TextBlock 替换为 ListBox 并相应地设置 ItemTemplate 的样式。

回答by Robetto

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

我自己没有做过,但这是我的“如果有时间我会做”的项目之一。因此,我仍在寻找现有的实现:-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.

使用视觉效果(即椭圆、文本块)的方法可能不是一个好主意。想想如果你想要 200x100 个字符,会发生什么。甚至可能是一个后台缓冲区。将其全部保存在内存中 + 绘制它......这将非常缓慢。

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.

因此,更好(甚至正确)的方法是“画自己”。由于 WPF 是后台缓冲的并且您不想显示任意位图,因此最可能的方法是创建一个新的 UserControl 并覆盖它的 Paint-Method。您可能更喜欢从 Control 派生,但 UserControl 可能有 Content,因此您可以在其中显示类似连接指示器图标的内容。

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 \ncharacters 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).

在体系结构方面,我建议创建一个依赖属性Buffer( ConsoleBuffer) 来保存控制台缓冲区模型。另一个 DP 将持有左上角的位置Location( long)。它决定了从哪里开始显示(当你看看后面的时候)。控制台模型我会创建一个包含 achar[]和 a Color[](一维)的类。使用换行符和\n字符来创建行(因为这是控制台的字符)。然后,如果您调整控件的大小,它将重新流动,而无需重新分配缓冲区。您可以使用不同大小的 **ConsoleBuffer**s(用于不同数量的字符后面)。

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

ConsoleBuffer.Write(string s)是你做事的方法。

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

也许建议使用数组数组char[][]来表示行……但这取决于在编程时找出来。