simonalexander2005
simonalexander2005

Reputation: 4577

Preserving indenting when wrapping in a wpf textblock control

I have a WPF textblock set up with the property TextWrapping="Wrap".

When I pass in a long string with a tab character (vbTab in my case) at the start, I would like the wrapping to honour this and keep the wrapped parts of the string indented. For example, instead of:

[vbTab]thisisreallylong

andwrapped

I want

[vbTab]thisisreallylong

[vbTab]andwrapped

and ideally for multiple tabs, etc. too.

[edit - additional details]

Because the textblock will be of variable size and contain multiple lines of text with various amounts of indenting, I can't just have a margin or manually split the strings and add tabs.

Essentially what I want is for it to treat lines of text like paragraphs, that keep their indenting when they wrap.

Upvotes: 1

Views: 1905

Answers (1)

pushpraj
pushpraj

Reputation: 13679

Based on your idea, I am able to come up with this solution

I'll convert all the tabs in the beginning of every line to .5 inch margin each and will add the same text in a paragraph and apply the calculated margin to the same

A TextBlock was not feasible for the same as it is useful for basic text inlines like run bold, inline ui container etc. adding paragraph was more complicated in a TextBlock so I made the solution based on FlowDocument.

Result

result

below example demonstrate the same using FlowDocumentScrollViewer or RichTextBox or FlowDocumentReader or plain FlowDocument

I have created the solution using attached properties, so you can attach the same to any of the mentioned or even add your own host for the document. you simply have to set IndentationProvider.Text to the desired host.

XAML

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:l="clr-namespace:PreservingIndentationDemo"
        Title="MainWindow"
        Height="350"
        Width="525">
    <Window.Resources>
        <sys:String x:Key="longString"
                    xml:space="preserve">&#x09;this is really long and wrapped
        
&#x09;&#x09;another line this is also really long and wrapped
        
&#x09;one more line this is also really long and wrapped
        
another line this is also really long and wrapped
        
&#x09;&#x09;another line this is also really long and wrapped
        </sys:String>
    </Window.Resources>
    <Grid>
        <FlowDocumentScrollViewer l:IndentationProvider.Text="{StaticResource longString}" />
        <!--<RichTextBox l:TextToParaHelper.Text="{StaticResource longString}" IsReadOnly="True"/>-->
        <!--<FlowDocumentReader l:TextToParaHelper.Text="{StaticResource longString}" />-->
        <!--<FlowDocument l:TextToParaHelper.Text="{StaticResource longString}" />-->
    </Grid>
</Window>

&#x09; refers to tab char

IndentationProvider

Class IndentationProvider

    Public Shared Function GetText(obj As DependencyObject) As String
        Return DirectCast(obj.GetValue(TextProperty), String)
    End Function

    Public Shared Sub SetText(obj As DependencyObject, value As String)
        obj.SetValue(TextProperty, value)
    End Sub

    ' Using a DependencyProperty as the backing store for Text.  This enables animation, styling, binding, etc...
    Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.RegisterAttached("Text", GetType(String), GetType(IndentationProvider), New PropertyMetadata(Nothing, AddressOf OnTextChanged))

    Private Shared Sub OnTextChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
        Dim blocks As BlockCollection = Nothing

        Dim rtb As RichTextBox = TryCast(d, RichTextBox)
        If rtb IsNot Nothing Then
            rtb.Document.Blocks.Clear()
            blocks = rtb.Document.Blocks
        End If

        If blocks Is Nothing Then
            Dim fd As FlowDocument = TryCast(d, FlowDocument)
            If fd IsNot Nothing Then
                fd.Blocks.Clear()
                blocks = fd.Blocks
            End If
        End If

        If blocks Is Nothing Then
            Dim fdr As FlowDocumentReader = TryCast(d, FlowDocumentReader)
            If fdr IsNot Nothing Then
                fdr.Document = New FlowDocument()
                blocks = fdr.Document.Blocks
            End If
        End If

        If blocks Is Nothing Then
            Dim fdr As FlowDocumentScrollViewer = TryCast(d, FlowDocumentScrollViewer)
            If fdr IsNot Nothing Then
                fdr.Document = New FlowDocument()
                blocks = fdr.Document.Blocks
            End If
        End If

        Dim newValue As String = TryCast(e.NewValue, String)
        If Not String.IsNullOrWhiteSpace(newValue) Then
            For Each line As String In newValue.Split(ControlChars.Lf)
                Dim leftMargin As Double = 0
                Dim newLine As String = line
                While newLine.Length > 0 AndAlso newLine(0) = ControlChars.Tab
                    leftMargin += 0.5
                    newLine = newLine.Remove(0, 1)
                End While
                Dim marginInch As String = leftMargin & "in"
                Dim marginDip As Double = CDbl(New LengthConverter().ConvertFromString(marginInch))

                Dim para As New Paragraph(New Run(newLine)) With {.Margin = New Thickness(marginDip, 0, 0, 0)}
                blocks.Add(para)
            Next
        End If
    End Sub
End Class

Demo

try demo project

Upvotes: 4

Related Questions