Pull to refresh

Относительно позиционированные элементы в документах WPF

Reading time3 min
Views3.9K
Пару месяцев назад мне пришлось реализовывать интерфейс с помощью WPF. В основном использовался FlowDocument, т.к. необходимо было максимально близко организовать UI в стиле веб-страниц.
Привыкший к свободе по позиционированию HTML-элементов с помощью CSS, я не мог найти решение по относительному позиционированию вложенных элементов. Свойства Top, Left, Right, Bottom полностью отсутствуют в плавающих WPF документах. MSDN выдал только класс Figure. Однако HorizontalOffset и VerticalOffset не работают при использовании FlowDocumentScrollViewer. Поиск в гугле также не помог.
Однако решение оказалось более чем простым.

Как работают браузерные движки

Наиболее интересной информацией о своей внутренней реализации предоставляет WebKit. Блог представляет собой ценнейший источник информации для тех, кто не хочет изучать исходный код проекта, но при этом хочет знать об его архитектуре.

Особый интерес вызвал цикл постов о WebCore Rendering. На этой странице и нашлась главная фраза, которая и натолкнула на решение проблемы: «Relative positioning is literally nothing more than a paint-time translation», что на русском: «Относительное позиционирование в буквальном смысле не более чем перемещение при прорисовке».

Вооружившись данным «постулатом», я решил найти что-либо похожее в недрах WPF.

TextEffect

Если для UIElement мы обычно используем RenderTrasform и 2D-трансформации вида Translate, Rotate и Scale, тот же механизм предусмотрен для объектов содержащих текст. Контейнером всех преобразований служит класс TextEffect.

Итак, приступим к реализации. В качестве примера будет использоваться пример со страницы блога.


<div style="border:5px solid black; padding:20px; width:300px; margin-left:auto; margin-right:auto">
Here is a line of text. <span style="position:relative;top:-10px; background-color:#eeeeee">This part is shifted<br> up a bit</span>, but the rest of the line is in its original position.
</div>

Тот же пример, но только для WPF, без позиционирования:


<FlowDocumentScrollViewer>
  <FlowDocumentScrollViewer.Document>
    <FlowDocument Name="doc" FontFamily="Verdana" FontSize="12px" LineHeight="18px">
      <Paragraph Padding="20px" BorderThickness="5px" BorderBrush="Black">
        Here is a line of text.
        <Span Name="shiftedText" Background="#EEE">This part is shifted<LineBreak/>
          up a bit
        </Span>, but the rest of the line is in its original position.
      </Paragraph>
    </FlowDocument>
  </FlowDocumentScrollViewer.Document>
</FlowDocumentScrollViewer>

Прежде чем применить TextEffect, необходимо знать, что он работает только с типом Run. При применении к Span, либо другому элементу в документе, эффекта наблюдаться не будет.

Применим эффект:

void DoEffect()
{
  foreach (var run in shiftedText.Inlines.OfType<Run>())
  {
    TextEffect f = new TextEffect();
    TranslateTransform t = new TranslateTransform(0, -10d);
    f.Transform = t;
    int selectionStart = doc.ContentStart.GetOffsetToPosition(run.ElementStart);
    int selectionLength = run.ElementStart.GetOffsetToPosition(run.ElementEnd);
    f.PositionStart = selectionStart;
    f.PositionCount = selectionLength;
    run.TextEffects.Add(f);
  }
}

И код для отмены:

void UndoEffect()
{
  foreach (var run in shiftedText.Inlines.OfType<Run>())
  {
    run.TextEffects.Clear();
  }
}

Таким образом, мы смогли получить желаемый эффект. Надеюсь, что код пригодится и другим.
Ссылка на проект с примером.
Tags:
Hubs:
0
Comments19

Articles

Change theme settings