2010-07-10 134 views
7

我在想像創建一個像終端窗口一樣工作的WPF或Silverlight應用程序。除了WPF/Silverlight,它將能夠'增強'終端體驗,圖像等。Windows中的VT100終端仿真WPF或Silverlight

我試圖找出模擬終端的最佳方法。就解析而言,我知道如何處理VT100仿真,但是如何顯示它?我考慮使用RichTextBox,並將VT100轉義代碼轉換爲RTF。

我看到的問題是性能。終端可能一次只能得到幾個字符,並且能夠將它們加載到文本框中,我們將不斷創建TextRanges並使用Load()加載RTF。此外,爲了使每個加載「會話」完成,它必須完全描述RTF。例如,如果當前顏色爲紅色,則每個加載到文本框中都需要使用RTF代碼使文本變爲紅色,或者我假定實時出價工具不會將其加載爲紅色。

這看起來非常冗餘 - 仿真所生成的RTF文檔將非常混亂。此外,插入符號的移動似乎不像RTB理想的那樣處理。我需要一些自定義的東西,但是那讓我害怕!

希望聽到聰明的想法或指向現有的解決方案。也許有一種方法可以在其上嵌入實際的終端和覆蓋物。我唯一發現的是一箇舊的WinForms控件。

更新:瞭解建議的解決方案由於在下面的答案中表現如何失敗。 :(
VT100 Terminal Emulation in Windows WPF or Silverlight

回答

16

如果您嘗試使用RichTextBo x和RTF,你將很快遇到許多限制,並發現自己花費更多時間解決差異問題,而不是自己實現功能。

事實上,使用WPF實現VT100終端仿真是相當容易的。我知道,因爲剛纔我在一個小時左右就實現了一個幾乎完整的VT100仿真器。準確地說,我implmented一切,除了:

  • 鍵盤輸入,
  • 備選字符集,
  • 一些深奧的VT100模式我從來沒有見過使用,

最有趣的部分分別是:

  • 我使用RenderTransform和RenderTransformOrigin的雙倍寬度/雙倍高度字符
  • 閃爍,爲此,我使用的共享對象上的動畫,以便所有的字符將閃爍一起
  • 下劃線,爲此,我使用的網格和矩形,以便它看起來更像一個VT100顯示
  • 的光標和選擇,爲此我在單元格本身上設置了一個標誌,並使用DataTriggers更改顯示器
  • 使用指向同一對象的一維數組和指向同一對象的嵌套數組,以便於滾動和選擇

這裏是XAML:

<Style TargetType="my:VT100Terminal"> 
    <Setter Property="Template"> 
    <Setter.Value> 
     <ControlTemplate TargetType="my:VT100Terminal"> 
     <DockPanel> 
      <!-- Add status bars, etc to the DockPanel at this point --> 
      <ContentPresenter Content="{Binding Display}" /> 
     </DockPanel> 
     </ControlTemplate> 
    </Setter.Value> 
    </Setter> 
</Style> 

<ItemsPanelTemplate x:Key="DockPanelLayout"> 
    <DockPanel /> 
</ItemsPanelTemplate> 

<DataTemplate DataType="{x:Type my:TerminalDisplay}"> 
    <ItemsControl ItemsSource="{Binding Lines}" TextElement.FontFamily="Courier New"> 
    <ItemsControl.ItemTemplate> 
     <DataTemplate> 
     <ItemsControl ItemsSource="{Binding}" ItemsPanel="{StaticResource DockPanelLayout}" /> 
     </DataTemplate> 
    </ItemsControl.ItemTemplate> 
    </ItemsControl> 
</DataTemplate> 

<DataTemplate DataType="{x:Type my:TerminalCell}"> 
    <Grid> 
    <TextBlock x:Name="tb" 
     Text="{Binding Character}" 
     Foreground="{Binding Foreground}" 
     Background="{Binding Background}" 
     FontWeight="{Binding FontWeight}" 
     RenderTransformOrigin="{Binding TranformOrigin}"> 
     <TextBlock.RenderTransform> 
      <ScaleTransform ScaleX="{Binding ScaleX}" ScaleY="{Binding ScaleY}" /> 
     </TextBlock.RenderTransform> 
    </TextBlock> 
    <Rectangle Visibility="{Binding UnderlineVisiblity}" Height="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="0 0 0 2" /> 
    </Grid> 
    <DataTemplate.Triggers> 
    <DataTrigger Binding="{Binding IsCursor}" Value="true"> 
     <Setter TargetName="tb" Property="Foreground" Value="{Binding Background}" /> 
     <Setter TargetName="tb" Property="Background" Value="{Binding Foreground}" /> 
    </DataTrigger> 
    <DataTrigger Binding="{Binding IsMouseSelected}" Value="true"> 
     <Setter TargetName="tb" Property="Foreground" Value="White" /> 
     <Setter TargetName="tb" Property="Background" Value="Blue" /> 
    </DataTrigger> 
    </DataTemplate.Triggers> 
</DataTemplate> 

這裏是代碼:

public class VT100Terminal : Control 
{ 
    bool _selecting; 

    static VT100Terminal() 
    { 
    DefaultStyleKeyProperty.OverrideMetadata(typeof(VT100Terminal), new FrameworkPropertyMetadata(typeof(VT100Terminal))); 
    } 

    // Display 
    public TerminalDisplay Display { get { return (TerminalDisplay)GetValue(DisplayProperty); } set { SetValue(DisplayProperty, value); } } 
    public static readonly DependencyProperty DisplayProperty = DependencyProperty.Register("Display", typeof(TerminalDisplay), typeof(VT100Terminal)); 

    public VT100Terminal() 
    { 
    Display = new TerminalDisplay(); 

    MouseLeftButtonDown += HandleMouseMessage; 
    MouseMove += HandleMouseMessage; 
    MouseLeftButtonUp += HandleMouseMessage; 

    KeyDown += HandleKeyMessage; 

    CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, ExecuteCopy, CanExecuteCopy)); 
    } 

    public void ProcessCharacter(char ch) 
    { 
    Display.ProcessCharacter(ch); 
    } 

    private void HandleMouseMessage(object sender, MouseEventArgs e) 
    { 
    if(!_selecting && e.RoutedEvent != Mouse.MouseDownEvent) return; 
    if(e.RoutedEvent == Mouse.MouseUpEvent) _selecting = false; 

    var block = e.Source as TextBlock; if(block==null) return; 
    var cell = ((TextBlock)e.Source).DataContext as TerminalCell; if(cell==null) return; 
    var index = Display.GetIndex(cell); if(index<0) return; 
    if(e.GetPosition(block).X > block.ActualWidth/2) index++; 

    if(e.RoutedEvent == Mouse.MouseDownEvent) 
    { 
     Display.SelectionStart = index; 
     _selecting = true; 
    } 
    Display.SelectionEnd = index; 
    } 

    private void HandleKeyMessage(object sender, KeyEventArgs e) 
    { 
    // TODO: Code to covert e.Key to VT100 codes and report keystrokes to client 
    } 

    private void CanExecuteCopy(object sender, CanExecuteRoutedEventArgs e) 
    { 
    if(Display.SelectedText!="") e.CanExecute = true; 
    } 
    private void ExecuteCopy(object sender, ExecutedRoutedEventArgs e) 
    { 
    if(Display.SelectedText!="") 
    { 
     Clipboard.SetText(Display.SelectedText); 
     e.Handled = true; 
    } 
    } 
} 

public enum CharacterDoubling 
{ 
    Normal = 5, 
    Width = 6, 
    HeightUpper = 3, 
    HeightLower = 4, 
} 

public class TerminalCell : INotifyPropertyChanged 
{ 
    char _character; 
    Brush _foreground, _background; 
    CharacterDoubling _doubling; 
    bool _isBold, _isUnderline; 
    bool _isCursor, _isMouseSelected; 

    public char Character { get { return _character; } set { _character = value; Notify("Character", "Text"); } } 
    public Brush Foreground { get { return _foreground; } set { _foreground = value; Notify("Foreground"); } } 
    public Brush Background { get { return _background; } set { _background = value; Notify("Background"); } } 
    public CharacterDoubling Doubling { get { return _doubling; } set { _doubling = value; Notify("Doubling", "ScaleX", "ScaleY", "TransformOrigin"); } } 
    public bool IsBold { get { return _isBold; } set { _isBold = value; Notify("IsBold", "FontWeight"); } } 
    public bool IsUnderline { get { return _isUnderline; } set { _isUnderline = value; Notify("IsUnderline", "UnderlineVisibility"); } } 

    public bool IsCursor { get { return _isCursor; } set { _isCursor = value; Notify("IsCursor"); } } 
    public bool IsMouseSelected { get { return _isMouseSelected; } set { _isMouseSelected = value; Notify("IsMouseSelected"); } } 

    public string Text { get { return Character.ToString(); } } 
    public int ScaleX { get { return Doubling!=CharacterDoubling.Normal ? 2 : 1; } } 
    public int ScaleY { get { return Doubling==CharacterDoubling.HeightUpper || Doubling==CharacterDoubling.HeightLower ? 2 : 1; } } 
    public Point TransformOrigin { get { return Doubling==CharacterDoubling.HeightLower ? new Point(1,0) : new Point(0,0); } } 
    public FontWeight FontWeight { get { return IsBold ? FontWeights.Bold : FontWeights.Normal; } } 
    public Visibility UnderlineVisibility { get { return IsUnderline ? Visibility.Visible : Visibility.Hidden; } } 

    // INotifyPropertyChanged implementation 
    private void Notify(params string[] propertyNames) { foreach(string name in propertyNames) Notify(name); } 
    private void Notify(string propertyName) 
    { 
    if(PropertyChanged!=null) 
     PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
    } 
    public event PropertyChangedEventHandler PropertyChanged; 
} 

public class TerminalDisplay : INotifyPropertyChanged 
{ 
    // Basic state 
    private TerminalCell[] _buffer; 
    private TerminalCell[][] _lines; 
    private int _height, _width; 
    private int _row, _column; // Cursor position 
    private int _scrollTop, _scrollBottom; 
    private List<int> _tabStops; 
    private int _selectStart, _selectEnd; // Text selection 
    private int _saveRow, _saveColumn; // Saved location 

    // Escape character processing 
    string _escapeChars, _escapeArgs; 

    // Modes 
    private bool _vt52Mode; 
    private bool _autoWrapMode; 
    // current attributes 
    private bool _boldMode, _lowMode, _underlineMode, _blinkMode, _reverseMode, _invisibleMode; 
    // saved attributes 
    private bool _saveboldMode, _savelowMode, _saveunderlineMode, _saveblinkMode, _savereverseMode, _saveinvisibleMode; 
    private Color _foreColor, _backColor; 
    private CharacterDoubling _doubleMode; 

    // Computed from current mode 
    private Brush _foreground; 
    private Brush _background; 

    // Hidden control used to synchronize blinking 
    private FrameworkElement _blinkMaster; 

    public TerminalDisplay() 
    { 
    Reset(); 
    } 

    public void Reset() 
    { 
    _height = 24; 
    _width = 80; 
    _row = 0; 
    _column = 0; 
    _scrollTop = 0; 
    _scrollBottom = _height; 
    _vt52Mode = false; 
    _autoWrapMode = true; 
    _selectStart = 0; 
    _selectEnd = 0; 
    _tabStops = new List<int>(); 
    ResetBuffer(); 
    ResetCharacterModes(); 
    UpdateBrushes(); 
    _saveboldMode = _savelowMode = _saveunderlineMode = _saveblinkMode = _savereverseMode = _saveinvisibleMode = false; 
    _saveRow = _saveColumn = 0; 
    } 
    private void ResetBuffer() 
    { 
    _buffer = (from i in Enumerable.Range(0, Width * Height) select new TerminalCell()).ToArray(); 
    UpdateSelection(); 
    UpdateLines(); 
    } 
    private void ResetCharacterModes() 
    { 
    _boldMode = _lowMode = _underlineMode = _blinkMode = _reverseMode = _invisibleMode = false; 
    _doubleMode = CharacterDoubling.Normal; 
    _foreColor = Colors.White; 
    _backColor = Colors.Black; 
    } 

    public int Height { get { return _height; } set { _height = value; ResetBuffer(); } } 
    public int Width { get { return _width; } set { _width = value; ResetBuffer(); } } 

    public int Row { get { return _row; } set { CursorCell.IsCursor = false; _row=value; CursorCell.IsCursor = true; Notify("Row", "CursorCell"); } } 
    public int Column { get { return _column; } set { CursorCell.IsCursor = false; _column=value; CursorCell.IsCursor = true; Notify("Row", "CursorCell"); } } 

    public int SelectionStart { get { return _selectStart; } set { _selectStart = value; UpdateSelection(); Notify("SelectionStart", "SelectedText"); } } 
    public int SelectionEnd { get { return _selectEnd; } set { _selectEnd = value; UpdateSelection(); Notify("SelectionEnd", "SelectedText"); } } 

    public TerminalCell[][] Lines { get { return _lines; } } 

    public TerminalCell CursorCell { get { return GetCell(_row, _column); } } 

    public TerminalCell GetCell(int row, int column) 
    { 
    if(row<0 || row>=Height || column<0 || column>=Width) 
     return new TerminalCell(); 
    return _buffer[row*Height + column]; 
    } 

    public int GetIndex(int row, int column) 
    { 
    return row * Height + column; 
    } 

    public int GetIndex(TerminalCell cell) 
    { 
    return Array.IndexOf(_buffer, cell); 
    } 

    public string SelectedText 
    { 
    get 
    { 
     int start = Math.Min(_selectStart, _selectEnd); 
     int end = Math.Max(_selectStart, _selectEnd); 
     if(start==end) return string.Empty; 
     var builder = new StringBuilder(); 
     for(int i=start; i<end; i++) 
     { 
     if(i!=start && (i%Width==0)) 
     { 
      while(builder.Length>0 && builder[builder.Length-1]==' ') 
      builder.Length--; 
      builder.Append("\r\n"); 
     } 
     builder.Append(_buffer[i].Character); 
     } 
     return builder.ToString(); 
    } 
    } 

    ///////////////////////////////// 

    public void ProcessCharacter(char ch) 
    { 
    if(_escapeChars!=null) 
    { 
     ProcessEscapeCharacter(ch); 
     return; 
    } 
    switch(ch) 
    { 
     case '\x1b': _escapeChars = ""; _escapeArgs = ""; break; 
     case '\r': Column = 0; break; 
     case '\n': NextRowWithScroll();break; 

     case '\t': 
     Column = (from stop in _tabStops where stop>Column select (int?)stop).Min() ?? Width - 1; 
     break; 

     default: 
     CursorCell.Character = ch; 
     FormatCell(CursorCell); 

     if(CursorCell.Doubling!=CharacterDoubling.Normal) ++Column; 
      if(++Column>=Width) 
      if(_autoWrapMode) 
      { 
       Column = 0; 
       NextRowWithScroll(); 
      } 
      else 
       Column--; 
     break; 
    } 
    } 
    private void ProcessEscapeCharacter(char ch) 
    { 
    if(_escapeChars.Length==0 && "78".IndexOf(ch)>=0) 
    { 
     _escapeChars += ch.ToString(); 
    } 
    else if(_escapeChars.Length>0 && "()Y".IndexOf(_escapeChars[0])>=0) 
    { 
     _escapeChars += ch.ToString(); 
     if(_escapeChars.Length != (_escapeChars[0]=='Y' ? 3 : 2)) return; 
    } 
    else if(ch==';' || char.IsDigit(ch)) 
    { 
     _escapeArgs += ch.ToString(); 
     return; 
    } 
    else 
    { 
     _escapeChars += ch.ToString(); 
     if("[#?()Y".IndexOf(ch)>=0) return; 
    } 
    ProcessEscapeSequence(); 
    _escapeChars = null; 
    _escapeArgs = null; 
    } 

    private void ProcessEscapeSequence() 
    { 
    if(_escapeChars.StartsWith("Y")) 
    { 
     Row = (int)_escapeChars[1] - 64; 
     Column = (int)_escapeChars[2] - 64; 
     return; 
    } 
    if(_vt52Mode && (_escapeChars=="D" || _escapeChars=="H")) _escapeChars += "_"; 

    var args = _escapeArgs.Split(';'); 
    int? arg0 = args.Length>0 && args[0]!="" ? int.Parse(args[0]) : (int?)null; 
    int? arg1 = args.Length>1 && args[1]!="" ? int.Parse(args[1]) : (int?)null; 
    switch(_escapeChars) 
    { 
     case "[A": case "A": Row -= Math.Max(arg0??1, 1); break; 
     case "[B": case "B": Row += Math.Max(arg0??1, 1); break; 
     case "[c": case "C": Column += Math.Max(arg0??1, 1); break; 
     case "[D": case "D": Column -= Math.Max(arg0??1, 1); break; 

     case "[f": 
     case "[H": case "H_": 
     Row = Math.Max(arg0??1, 1) - 1; Column = Math.Max(arg0??1, 1) - 1; 
     break; 

     case "M": PriorRowWithScroll(); break; 
     case "D_": NextRowWithScroll(); break; 
     case "E": NextRowWithScroll(); Column = 0; break; 

     case "[r": _scrollTop = (arg0??1)-1; _scrollBottom = (arg0??_height); break; 

     case "H": if(!_tabStops.Contains(Column)) _tabStops.Add(Column); break; 
     case "g": if(arg0==3) _tabStops.Clear(); else _tabStops.Remove(Column); break; 

     case "[J": case "J": 
     switch(arg0??0) 
     { 
      case 0: ClearRange(Row, Column, Height, Width); break; 
      case 1: ClearRange(0, 0, Row, Column + 1); break; 
      case 2: ClearRange(0, 0, Height, Width); break; 
     } 
     break; 
     case "[K": case "K": 
     switch(arg0??0) 
     { 
      case 0: ClearRange(Row, Column, Row, Width); break; 
      case 1: ClearRange(Row, 0, Row, Column + 1); break; 
      case 2: ClearRange(Row, 0, Row, Width); break; 
     } 
     break; 

     case "?l": 
     case "?h": 
     var h = _escapeChars=="?h"; 
     switch(arg0) 
     { 
      case 2: _vt52Mode = h; break; 
      case 3: Width = h ? 132 : 80; ResetBuffer(); break; 
      case 7: _autoWrapMode = h; break; 
     } 
     break; 
     case "<": _vt52Mode = false; break; 

     case "m": 
     if (args.Length == 0) ResetCharacterModes(); 
     foreach(var arg in args) 
      switch(arg) 
      { 
       case "0": ResetCharacterModes(); break; 
       case "1": _boldMode = true; break; 
       case "2": _lowMode = true; break; 
       case "4": _underlineMode = true; break; 
       case "5": _blinkMode = true; break; 
       case "7": _reverseMode = true; break; 
       case "8": _invisibleMode = true; break; 
      } 
     UpdateBrushes(); 
     break; 

     case "#3": case "#4": case "#5": case "#6": 
     _doubleMode = (CharacterDoubling)((int)_escapeChars[1] - (int)'0'); 
     break; 

     case "[s": _saveRow = Row; _saveColumn = Column; break; 
     case "7": _saveRow = Row; _saveColumn = Column; 
      _saveboldMode = _boldMode; _savelowMode = _lowMode; 
      _saveunderlineMode = _underlineMode; _saveblinkMode = _blinkMode; 
      _savereverseMode = _reverseMode; _saveinvisibleMode = _invisibleMode; 
      break; 
     case "[u": Row = _saveRow; Column = _saveColumn; break; 
     case "8": Row = _saveRow; Column = _saveColumn; 
      _boldMode = _saveboldMode; _lowMode = _savelowMode; 
      _underlineMode = _saveunderlineMode; _blinkMode = _saveblinkMode; 
      _reverseMode = _savereverseMode; _invisibleMode = _saveinvisibleMode; 
      break; 

     case "c": Reset(); break; 

     // TODO: Character set selection, several esoteric ?h/?l modes 
    } 
    if(Column<0) Column=0; 
    if(Column>=Width) Column=Width-1; 
    if(Row<0) Row=0; 
    if(Row>=Height) Row=Height-1; 
    } 

    private void PriorRowWithScroll() 
    { 
    if(Row==_scrollTop) ScrollDown(); else Row--; 
    } 

    private void NextRowWithScroll() 
    { 
    if(Row==_scrollBottom-1) ScrollUp(); else Row++; 
    } 

    private void ScrollUp() 
    { 
    Array.Copy(_buffer, _width * (_scrollTop + 1), _buffer, _width * _scrollTop, _width * (_scrollBottom - _scrollTop - 1)); 
    ClearRange(_scrollBottom-1, 0, _scrollBottom-1, Width); 
    UpdateSelection(); 
    UpdateLines(); 
    } 

    private void ScrollDown() 
    { 
    Array.Copy(_buffer, _width * _scrollTop, _buffer, _width * (_scrollTop + 1), _width * (_scrollBottom - _scrollTop - 1)); 
    ClearRange(_scrollTop, 0, _scrollTop, Width); 
    UpdateSelection(); 
    UpdateLines(); 
    } 

    private void ClearRange(int startRow, int startColumn, int endRow, int endColumn) 
    { 
    int start = startRow * Width + startColumn; 
    int end = endRow * Width + endColumn; 
    for(int i=start; i<end; i++) 
     ClearCell(_buffer[i]); 
    } 

    private void ClearCell(TerminalCell cell) 
    { 
    cell.Character = ' '; 
    FormatCell(cell); 
    } 

    private void FormatCell(TerminalCell cell) 
    { 
    cell.Foreground = _foreground; 
    cell.Background = _background; 
    cell.Doubling = _doubleMode; 
    cell.IsBold = _boldMode; 
    cell.IsUnderline = _underlineMode; 
    } 

    private void UpdateSelection() 
    { 
    var cursor = _row * Width + _height; 
    var inSelection = false; 
    for(int i=0; i<_buffer.Length; i++) 
    { 
     if(i==_selectStart) inSelection = !inSelection; 
     if(i==_selectEnd) inSelection = !inSelection; 

     var cell = _buffer[i]; 
     cell.IsCursor = i==cursor; 
     cell.IsMouseSelected = inSelection; 
    } 
    } 

    private void UpdateBrushes() 
    { 
    var foreColor = _foreColor; 
    var backColor = _backColor; 
    if(_lowMode) 
    { 
     foreColor = foreColor * 0.5f + Colors.Black * 0.5f; 
     backColor = backColor * 0.5f + Colors.Black * 0.5f; 
    } 
    _foreground = new SolidColorBrush(foreColor); 
    _background = new SolidColorBrush(backColor); 
    if(_reverseMode) Swap(ref _foreground, ref _background); 
    if(_invisibleMode) _foreground = _background; 
    if(_blinkMode) 
    { 
     if(_blinkMaster==null) 
     { 
     _blinkMaster = new Control(); 
     var animation = new DoubleAnimationUsingKeyFrames { RepeatBehavior=RepeatBehavior.Forever, Duration=TimeSpan.FromMilliseconds(1000) }; 
     animation.KeyFrames.Add(new DiscreteDoubleKeyFrame(0)); 
     animation.KeyFrames.Add(new DiscreteDoubleKeyFrame(1)); 
     _blinkMaster.BeginAnimation(UIElement.OpacityProperty, animation); 
     } 
     var rect = new Rectangle { Fill = _foreground }; 
     rect.SetBinding(UIElement.OpacityProperty, new Binding("Opacity") { Source = _blinkMaster }); 
     _foreground = new VisualBrush { Visual = rect }; 
    } 
    } 
    private void Swap<T>(ref T a, ref T b) 
    { 
    var temp = a; 
    a = b; 
    b = temp; 
    } 

    private void UpdateLines() 
    { 
    _lines = new TerminalCell[Height][]; 
    for(int r=0; r<Height; r++) 
    { 
     _lines[r] = new TerminalCell[Width]; 
     Array.Copy(_buffer, r*Height, _lines[r], 0, Width); 
    } 
    } 

    // INotifyPropertyChanged implementation 
    private void Notify(params string[] propertyNames) { foreach(string name in propertyNames) Notify(name); } 
    private void Notify(string propertyName) 
    { 
    if(PropertyChanged!=null) 
     PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
    } 
    public event PropertyChangedEventHandler PropertyChanged; 

} 

請注意,如果你不喜歡的視覺造型剛剛更新TerminalCell的DataTemplate。例如,光標可以是一個閃爍的矩形,而不是一個固定的矩形。

此代碼很有趣寫。希望它對你有用。它可能有一個或兩個(或三個)的錯誤,因爲我從未真正執行過它,但我希望這些錯誤很容易被清除。如果你解決了一些問題,我會歡迎編輯這個答案。

+0

我修正了一些錯誤:VT100有24行,而不是25;您需要在每行改變80到132個字符之後重置緩衝區;代碼7和8不僅保存/恢復光標位置,還保存屬性;復位需要重置所有內容;有些東西缺少默認值。 – Gabe 2010-07-11 19:05:55

+0

我認爲最大的問題是我相信你誤解了雙倍高度/寬度是如何工作的。加倍模式是緩衝行的屬性,而不是當前單元的屬性。因此,當您發送#6代碼時,VT100將光標所在行的像素時鐘減半,導致每個像素的寬度是其兩倍。這意味着該行只能有40或66個字符,並且光標不能超過位置40或66. – Gabe 2010-07-11 19:16:10

+0

我還應該注意,使用DataTriggers和DockPanel使得此WPF無需重構。 – Gabe 2010-07-12 06:59:04

1

我不明白爲什麼你會發愁的RTF越來越錯綜複雜。是的,它會的。但它不是你的負擔來解決它,微軟程序員做到了前一陣子,不得不編寫代碼來渲染令人費解的RTF,它運行良好,對你來說完全不透明

是的,它不會超快速,但是你在模擬80x25顯示器以前運行在9600波特率,完全取代控制器以使其達到最佳狀態沒有什麼意義,並且將成爲一項主要工作。

+0

的來源True。我想我擔心它可能會有多慢。此外,由於我將保留緩衝區,因此文檔可能會變大。它要吃多少內存?猜猜我只需要嘗試一下。 光標怎麼樣?我希望那裏有一個,但不是讓用戶能夠通過點擊來移動它。但我希望他們能夠突出顯示部分並複製它。這有點像只讀模式位不完全。 – InfinitiesLoop 2010-07-10 16:48:53

+0

它永遠不會變得太大,你只需要模擬一個屏幕。這也防止了它變慢。從實時出價導出您自己的課程,以吃掉鼠標點擊和鍵盤敲擊。 – 2010-07-10 16:56:10

+0

欣賞您的輸入。在等待答覆之前,我會稍等一下,看看我能否得到任何其他想法。如果沒有其他人蔘加我猜這是:) – InfinitiesLoop 2010-07-10 17:01:08

1

那麼,爲了報告我的狀態,我確定這對於WPF或Silverlight來說並不可行。

提出的方法的問題是,有80 * 24的TextBlocks加上一些其他元素,對forecolor,backcolor等有多個綁定。當屏幕需要滾動時,必須重新評估每個綁定,它非常非常緩慢。更新整個屏幕需要幾秒鐘。在我的應用程序中,這是不可接受的,屏幕將不斷滾動。

我嘗試了很多不同的東西來優化它。我嘗試使用每行80行的一個文本塊。我試着批量更改通知。我試着讓它成爲手動更新每個文本塊的'滾動'事件。沒有什麼真正的幫助 - 緩慢的部分是更新UI,而不是它完成的方式。

有一件事會幫助我,如果我設計了一種機制,不要爲每個單元格設置文本塊或運行,而只是在文本樣式更改時更改文本塊。因此,例如一串相同顏色的文本將只有一個文本塊。但是,這將會非常複雜,並且最終只會幫助屏幕上幾乎沒有風格變化的場景。我的應用程序會有很多顏色(認爲是ANSI藝術),所以在這種情況下它仍然會很慢。

我認爲會有幫助的另一件事是如果我沒有更新文本塊,而是在屏幕滾動時滾動它們。所以文本塊將從頂部移動到底部,然後只有新的需要更新。我設法通過使用可觀察集合來實現這一目標。它有幫助,但它仍然太慢!

我甚至考慮過使用OnRender的自定義WPF控件。我創建了一個以各種方式使用drawingContext.RenderText來查看速度有多快的例子。但即使如此,處理不斷更新屏幕的速度也太慢了。

那就是這樣..我放棄了這個設計。我不是在看用實際控制檯窗口如下所述:

No output to console from a WPF application?

我真的不喜歡的是,由於窗口是雖然主窗口分開的,所以我在尋找一種方式來如果可能的話,將控制檯窗口嵌入到WPF窗口中。我會再問一個關於這個問題的SO問題,當我這樣做的時候,會在這裏連接它。

UPDATE:嵌入控制檯窗口也失敗了,因爲它並不需要刪除標題欄。我已經實現了它作爲一個低級別的繪畫自定義WinForms控件,並且我在WPF中承載了它。這工作很好,經過一些優化,其速度非常快。

1

有效顯示文本的唯一方法是使用TextFormatter。 我已經實現了基於文本的RPG遊戲的telnet客戶端,它工作得很好。 您可以查看http://mudclient.codeplex.com

+0

但它支持完全仿真嗎?例如,您需要能夠隨時打印到屏幕上的任何單元格,而不僅僅是逐行轉發。 – InfinitiesLoop 2011-10-13 22:10:36

+0

不,完全仿真不受支持,但我看不到添加它的任何問題。 – petka 2011-10-17 07:18:11

+0

此方法僅適用於WPF。對於Silverlight,我認爲你應該看看最新版本的XNA支持。 – petka 2011-10-17 07:18:50