Эмуляция терминала VT100 в Windows WPF или Silverlight - PullRequest
7 голосов
/ 10 июля 2010

Я размышляю над созданием приложения WPF или Silverlight, которое работает так же, как окно терминала.За исключением того, что он в WPF / Silverlight, он сможет «улучшить» работу терминала с эффектами, изображениями и т. Д.

Я пытаюсь найти лучший способ эмулировать терминал.Я знаю, как справиться с эмуляцией VT100 в части анализа и т. Д. Но как это отобразить?Я подумал об использовании RichTextBox и, по сути, преобразовал escape-коды VT100 в RTF.

Проблема, с которой я сталкиваюсь, - это производительность.Терминал может получать только несколько символов за раз, и чтобы иметь возможность загружать их в текстовое поле по ходу дела, я бы постоянно создавал TextRanges и использовал Load () для загрузки RTF.Кроме того, чтобы каждая «сессия» загрузки была завершена, она должна полностью описывать RTF.Например, если текущий цвет красный, для каждой загрузки в TextBox потребуются коды RTF, чтобы сделать текст красным, или я предполагаю, что RTB не будет загружать его красным.

Это кажется очень избыточным -- полученный в результате эмуляции RTF-документ будет очень грязным.Кроме того, перемещение каретки не похоже на то, что она будет идеально обрабатываться RTB.Мне нужно что-то нестандартное, я думаю, но это пугает меня!

Надеюсь услышать яркие идеи или указатели на существующие решения.Возможно, есть способ встроить реальный терминал и наложить поверх него что-то еще.Единственное, что я нашел, - это старый элемент управления WinForms.

ОБНОВЛЕНИЕ: Посмотрите, как провалилось предлагаемое решение из-за перфорации, в моем ответе ниже.: (
эмуляция терминала VT100 в Windows WPF или Silverlight

Ответы [ 4 ]

16 голосов
/ 11 июля 2010

Если вы попытаетесь реализовать это с помощью RichTextBox и RTF, вы быстро столкнетесь со многими ограничениями и обнаружите, что тратите гораздо больше времени на обход различий, чем если бы вы реализовали эту функциональность самостоятельно.

На самом деле это довольно легкореализовать эмуляцию терминала VT100 с использованием WPF.Я знаю, потому что только сейчас я внедрил почти полный эмулятор VT100 за час или около того.Если быть точным, я реализовал все, кроме:

  • Клавишный ввод,
  • Альтернативные наборы символов,
  • Несколько эзотерических режимов VT100, которые я никогда не использовал,

Наиболее интересными частями были:

  • Символы двойной ширины / двойной высоты, для которых я использовал RenderTransform и RenderTransformOrigin
  • Мигание, для которого яЯ использовал анимацию на общем объекте, поэтому все персонажи будут мигать вместе
  • Подчеркивание, для которого я использовал Grid и Rectangle, так что это было бы больше похоже на отображение VT100
  • Курсор и выделение, для которого я устанавливаю флажок на сами ячейки и использую DataTriggers для изменения отображения
  • Использование как одномерного массива, так и вложенного массива, указывающего на одни и те же объекты, чтобы упростить прокрутку иselection

Вот 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.Например, курсор может быть мигающим прямоугольником, а не сплошным.

Этот код было весело писать.Надеюсь, это будет полезно для вас.Вероятно, в нем есть одна или две ошибки (или три), так как я никогда не выполнял их, но я ожидаю, что они будут легко устранены.Я бы приветствовал изменение этого ответа, если вы что-то исправите.

2 голосов
/ 13 октября 2011

Единственный способ эффективно отобразить текст - использовать TextFormatter.Я реализовал telnet-клиент для текстовых RPG-игр, и он работает довольно хорошо.Вы можете проверить источники на http://mudclient.codeplex.com

1 голос
/ 19 июля 2010

Ну, чтобы сообщить о своем статусе, я определил, что это нереально с WPF или Silverlight .

Проблема с предлагаемым подходом состоит в том, что имеется 80 * 24 TextBlocks плюс некоторые другие элементы, с несколькими привязками для переднего, заднего и т. Д. Когда экран должен прокручиваться, каждая из этих привязок должна быть перезаписана.оценивается, и очень, очень медленно.Обновление всего экрана занимает несколько секунд.В моем приложении это неприемлемо, экран будет постоянно прокручиваться.

Я пробовал много разных вещей, чтобы оптимизировать его.Я попытался использовать один текстовый блок с 80 прогонов в каждой строке.Я пытался дозировать уведомления об изменениях.Я попытался сделать так, чтобы событие «прокрутка» вручную обновляло каждый текстовый блок.Ничто действительно не помогает - медленная часть обновляет интерфейс, а не способ, которым это делается.

Одна вещь, которая могла бы помочь, - это если бы я разработал механизм, чтобы не иметь текстового блока или запускать для каждой ячейки, а изменять текстовые блоки только тогда, когда меняется стиль текста.Таким образом, строка текста того же цвета, например, будет только 1 текстовым блоком.Тем не менее, это будет очень сложно, и, в конце концов, это поможет только сценариям с небольшим изменением стиля на экране.В моем приложении будет много цветов (думаю, искусство ANSI), поэтому в этом случае оно все равно будет медленным.

Еще одна вещь, которая, я думаю, поможет, если я не обновлю текстовые блоки, носкорее прокручивал их вверх по мере прокрутки экрана.Таким образом, текстовые блоки будут перемещаться сверху вниз, и тогда только новые будут нуждаться в обновлении.Мне удалось добиться этого с помощью наблюдаемой коллекции.Это помогло, но все равно СЛИШКОМ МЕДЛЕННО!

Я даже рассматривал пользовательский элемент управления WPF с помощью OnRender.Я создал один, который использовал DrawingContext.RenderText различными способами, чтобы увидеть, насколько быстро это может быть.Но ДАЖЕ ЧТО слишком чертовски медленно, чтобы справляться с постоянным обновлением экрана.

Вот и все ... Я разочаровался в этом дизайне.Вместо этого я смотрю на использование фактического консольного окна, как описано здесь:

Нет вывода на консоль из приложения WPF?

Мне не очень нравится, так какокно отделено от основного окна, поэтому я ищу способ встроить консольное окно в окно WPF, если это вообще возможно.Я задам еще один вопрос по этому вопросу и свяжу его здесь, когда я это сделаю.

ОБНОВЛЕНИЕ: Встраивание окна консоли также не удалось, потому что не нужноего заголовок удален.Я реализовал его как пользовательский элемент управления WinForms низкого уровня рисования, и я размещаю его в WPF.Это прекрасно работает и после некоторых оптимизаций очень быстро.

1 голос
/ 10 июля 2010

Я не понимаю, почему вы будете беспокоиться о том, что RTF будет запутанным. Да, это будет. Но это не твое бремя, чтобы справиться с этим, программист Microsoft сделал это некоторое время назад, когда ему пришлось писать код для создания запутанного RTF. Он хорошо работает и совершенно непрозрачен для вас.

Да, это не будет супер-быстрым. Но что за эй, вы эмулируете дисплей 80x25, который раньше работал на скорости 9600 бод. Полностью заменить контроль, чтобы попытаться сделать его оптимальным, не имеет особого смысла, и это станет серьезным начинанием.

...