Вопрос

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

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

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

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

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

Обновление: Посмотрите, как предлагаемый раствор не удается из-за совершенного в моем ответе ниже. :(
VT100 Эмуляция терминала в WPF или Silverlight

Это было полезно?

Решение

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

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

    .
  • ввод клавиатуры,
  • альтернативные наборы символов,
  • несколько мод Esoteric VT100, которые я никогда не видел,

Самые интересные части были:

    .
  • двойная ширина / двойные высоты, для которых я использовал Rendertransform и Rendertransformorigin
  • мигание, для которого я использовал анимацию на общий объект, поэтому все персонажи будут мигать вместе
  • подчеркивание, для которой я использовал сетку и прямоугольник, так что это будет больше похоже на дисплей VT100
  • Курсор и выбор, для которого я устанавливаю флаг в самих ячеек и используете данные, чтобы изменить дисплей
  • Использование как одномерного массива, так и вложенный массив, указывающий на те же объекты, чтобы облегчить прокрутку и выделение

Вот 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;

}
.

Обратите внимание, что если вам не нравится визуальный стиль, просто обновите DataTemplate TerminalCell. Например, курсор может быть мигающим прямоугольником вместо твердого.

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

Другие советы

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

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

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

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

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

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

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

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

Я даже рассмотрел пользовательский контроль WPF с использованием Onrender. Я создал тот, который использовал DrawingContext.renderntext различным способом увидеть, как быстро это может быть. Но даже это слишком дарн, медленно, чтобы справиться постоянно обновлять экран.

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top