Relatively positioned elements in WPF documents

    A couple of months ago I had to implement an interface using WPF. Mostly used FlowDocument, as It was necessary to organize UI in the style of web pages as closely as possible.
    Accustomed to the freedom of positioning HTML elements using CSS, I could not find a solution for the relative positioning of nested elements. Properties Top, Left, Right, Bottom are completely absent in floating WPF documents. MSDN only issued the Figure class . However, HorizontalOffset and VerticalOffset do not work when using FlowDocumentScrollViewer. Google search also did not help.
    However, the solution was more than simple.

    How browser engines work

    The most interesting information about its internal implementation is provided by WebKit. A blog is a valuable source of information for those who do not want to study the source code of a project, but want to know about its architecture.

    Of particular interest was the WebCore Rendering series of posts. On this page the main phrase was found, which prompted a solution to the problem: “Relative positioning is literally nothing more than a paint-time translation”, which in Russian: “Relative positioning is literally nothing more than moving when drawing”.

    Armed with this "postulate", I decided to find something similar in the bowels of WPF.

    Text effect

    If for UIElement we usually use RenderTrasform and 2D transforms of the form Translate, Rotate and Scale, the same mechanism is provided for objects containing text. The container for all transformations is the TextEffect class .

    So, let's get started. An example from the blog page will be used as an example .


    Here is a line of text. This part is shifted
    up a bit
    , but the rest of the line is in its original position.

    The same example, but only for WPF, without positioning:


    
            Here is a line of text.
            This part is shifted
              up a bit
            , but the rest of the line is in its original position.
          

    Before you apply TextEffect, you need to know that it only works with the Run type . When applied to a Span or other element in a document, the effect will not be observed.

    Apply the effect:

    void DoEffect()
    {
      foreach (var run in shiftedText.Inlines.OfType())
      {
        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);
      }
    }
    

    And the code to cancel:

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

    Thus, we were able to get the desired effect. I hope the code comes in handy for others as well.
    Link to the project with an example .

    Also popular now: