none
GetPositionAtOffset method does not working properly if any text is selected in the RichTextBox

    Question

  • Hi,

    In the RichTextBox control, GetPositionAtOffset method does not working properly if the user selected any text.
    I am using below way which works fine until the user select any text.
    Selection.Start.GetPositionAtOffset(startOffset, LogicalDirection.Forward)

    RichTextBox::ClearAllProperties() method is called for the whole Text in the RichTextBox before calling the GetPositionAtOffset(..) method. But it returns wrong offset values. Is there any way to get rid of this confusion?

    Thanks,
    Charith


    CJ
    Tuesday, December 22, 2009 9:57 AM

Answers

  • Hi,

    I tried it out with this code:

    Class Window1 
    
        Public Shared ReadOnly StartProperty As DependencyProperty = DependencyProperty.Register("StartOffset", GetType(Integer), GetType(Window1), New PropertyMetadata(0))
        Public Property StartOffset() As Integer
            Get
                Return GetValue(StartProperty)
            End Get
            Set(ByVal value As Integer)
                SetValue(StartProperty, value)
            End Set
        End Property
    
    
        Public Shared ReadOnly EndOffsetProperty As DependencyProperty = DependencyProperty.Register("EndOffset", GetType(Integer), GetType(Window1), New PropertyMetadata(0))
        Public Property EndOffset() As Integer
            Get
                Return GetValue(EndOffsetProperty)
            End Get
            Set(ByVal value As Integer)
                SetValue(EndOffsetProperty, value)
            End Set
        End Property
    
    
        Private Sub CheckRangeButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
            StartOffset = rtb.Document.ContentStart.GetOffsetToPosition(rtb.Selection.Start)
            EndOffset = rtb.Document.ContentStart.GetOffsetToPosition(rtb.Selection.End)
            _Range = New TextRange(rtb.Selection.Start, rtb.Selection.End)
        End Sub
    
        Private Sub MarkRangeButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
            Dim startPos As TextPointer = rtb.Document.ContentStart.GetPositionAtOffset(StartOffset)
            Dim endpos As TextPointer = rtb.Document.ContentStart.GetPositionAtOffset(EndOffset)
            rtb.Selection.Select(startPos, endpos)
            rtb.Selection.ApplyPropertyValue(ForegroundProperty, Brushes.Yellow)
        End Sub
    
        Private _Range As TextRange
    
        Private Sub rtb_SelectionChanged(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
            If _Range IsNot Nothing Then
                StartOffset = rtb.Document.ContentStart.GetOffsetToPosition(_Range.Start)
                EndOffset = rtb.Document.ContentStart.GetOffsetToPosition(_Range.End)
            End If
        End Sub
    End Class
    

    This is the XAML:

    <Window x:Class="Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300"
        x:Name="myWindow">
      <DockPanel>
        <Button x:Name="CheckRangeButton" Click="CheckRangeButton_Click" DockPanel.Dock="Bottom">_Check TextRange</Button>
        <Button x:Name="MarkRangeButton" Click="MarkRangeButton_Click" DockPanel.Dock="Bottom">_Mark TextRange</Button>
        <TextBlock DockPanel.Dock="Bottom">
          <TextBlock.Text>
            <MultiBinding StringFormat="Start offset: {0}, End offset: {1}">
              <Binding ElementName="myWindow" Path="StartOffset"/>
              <Binding ElementName="myWindow" Path="EndOffset"/>
            </MultiBinding>
          </TextBlock.Text>
        </TextBlock>
        <RichTextBox x:Name="rtb" SelectionChanged="rtb_SelectionChanged">
          <FlowDocument>
            <Paragraph>
              Severe winter weather continued its stranglehold on Europe as travelers battled heavy snow, airlines canceled flights and motorists were left stranded.
            </Paragraph>
          </FlowDocument>
        </RichTextBox>
    </DockPanel>
    </Window>
    

    To me it looks like changing the Selection per se doesn't influence the start and end of the TextRange once it is marked. The offsets for the word "Europe" are one greater than what you wrote, but that is to be expected, because of the Paragraph that surrounds the text.

    So, what doesn't work as expected here?
    http://wpfglue.wordpress.com
    Thursday, December 24, 2009 12:33 PM

All replies

  • Hi,

    what do you mean by "wrong" offset? What offset did you expect? The number of characters up to the start of the selection?

    This seems to create a lot of confusion, but actually, these offsets are never in characters, only in symbols, and each closing and opening tag of an element in the RichTextBox's FlowDocument is one symbol. I'm not sure whether calling ClearAllProperties really removes the tags of the formatting or only the formatting properties...

    In your case, even if there is no formatting at all, there will always be at least one symbol for the beginning of the document, which will not show up as a character on screen.

    I don't know exactly what you want to do, but probably with the WPF RichTextBox control, you would have to use a structured model of the document instead of a linear model.
    http://wpfglue.wordpress.com
    Tuesday, December 22, 2009 10:33 AM
  • What do you mean?
    << you would have to use a structured model of the document instead of a linear model.

    Can you give me some more details?

    Thanks!

    CJ
    Tuesday, December 22, 2009 4:51 PM
  • Hi,

    what I mean is this: in a TextBox or old RichTextBox, one is used to remember a position like this: Character number 231 in the text. In a FlowDocument, it is probably more appropriate to remember something like: the first occurrence of the word "test" in the first Run of the second Paragraph in the first Section, i.e. it is like numeric index vs. XPath.

    TextPositions are a way to hold on to a certain place in the text while the formatting and other parts of the text change, but one should imho not try to use numerical indices to find them, except in a given Run, when it is known that the whole Run is uninterrupted by other elements.

    My rule of thumb would be: if you want to remember a certain place in a given document and know that the document is going to change, find the place using something like an XPath through the document, or a text search, or the selection, or whatever way you have to find the position without assuming a numerical character index; then hold on to this TextPosition as an object in memory as long as you are going to need it. If you want to save the document to file and still be able to remember the TextPosition, you can save the TextPosition's numerical offset and use it to create a new TextPosition, as long as you make sure that the document doesn't change between the time you get the offset from the TextPosition and the time you create the TextPosition anew from the offset. Change here means editing as well as formatting, since both may affect the symbol count. For all other purposes, I'd try not to use the offset directly.

    http://wpfglue.wordpress.com
    Tuesday, December 22, 2009 6:49 PM
  • For example, let's say we have the below text in the RichTextBox.

    "Severe winter weather continued its stranglehold on Europe as travelers battled heavy snow, airlines canceled flights and motorists were left stranded."

    According to this text, the word "Europe" is in the offset 53-59. I cannot keep all these annotations (annotations like “Europe”) as TextRanges as there are thousands of such annotations. So I am creating TextRanges on demand. 

    *If we apply any text properties then this offset can be shifted. I understand that. *

    Time to time I am applying some text properties to some annotations (Eg: “Europe”). In order to get rid of that offset shift ClearAllProperties() method is called before calling the GetPositionAtOffset() method. Then it clears all the text properties and works fine. 

    My confusion happens if user select any text in the RichTextBox. Let's say the user select "Severe winter" two words in the text box. If I call RichTextBox::ClearAllProperties() method then it clears the selection. Though if I call 

    TextPosition start = Selection.Start.GetPositionAtOffset(53, LogicalDirection.Forward)
    TextPosition end = Selection.Start.GetPositionAtOffset(59, LogicalDirection.Forward)

    TextRange trForEurope = new TextRange (start,end);

    trForEurope is not pointing the work "Europe". That's the issue.

    Below are my questions.

    1. ClearAllProperties method clears all the text properties and works fine until the user select any text in the textbox. What is the magical thing with text selection in the RichTextBox? Is there any other way to clear text selection properties?

    2. At least can I disallow user to select text in the textbox without disabling it?

    Thanks,
    Charith


    CJ
    Wednesday, December 23, 2009 4:14 AM
  • Hi,

    this looks like a different problem to me. Could it be that ClearAllProperties just removes the highlighting of the selection, while the selection itself remains in place? In that case, it could be that the selection.Start doesn't point to the start of the document, and that might cause an offset.

    Have you tried using RichTextBox.Document.ContentStart instead of Selection.Start? This should be a starting point that can't possibly change.
    http://wpfglue.wordpress.com
    Wednesday, December 23, 2009 6:08 PM
  • That's what I am already doing with the ClearAllProperties method.

    I've write a custom control by inheriting the RichTextBox control. Below is my ClearAllProperties method.

    public void ClearAllProperties()

    {

     

    TextRange tr = new TextRange(Document.ContentStart, Document.ContentEnd);

    tr.ClearAllProperties();

    }

    Thanks!


    CJ
    Thursday, December 24, 2009 2:40 AM
  • Hi,

    no, I was talking about the two lines in your post above:

    TextPosition start = Selection.Start.GetPositionAtOffset(53, LogicalDirection.Forward)
    TextPosition end = Selection.Start.GetPositionAtOffset(59, LogicalDirection.Forward)

    If the Selection moves, this will add an offset to the result, won't it? So, maybe using ContentStart etc. helps here.

    http://wpfglue.wordpress.com
    • Marked as answer by CharithJ Thursday, December 24, 2009 11:34 AM
    • Unmarked as answer by CharithJ Thursday, December 24, 2009 11:34 AM
    Thursday, December 24, 2009 7:28 AM
  • Hi,

    Nope, still no luck :(

    Is there any strange thing with the text selection in the RichTextBox? How does it differ from the applying of text properties?

    All the text properties go away with the ClearAllProperties() method. But the text selection which is doing by selecting the text from mouse cursor does not clearing.

    Thanks,
    CJ
    Thursday, December 24, 2009 11:40 AM
  • Hi,

    I tried it out with this code:

    Class Window1 
    
        Public Shared ReadOnly StartProperty As DependencyProperty = DependencyProperty.Register("StartOffset", GetType(Integer), GetType(Window1), New PropertyMetadata(0))
        Public Property StartOffset() As Integer
            Get
                Return GetValue(StartProperty)
            End Get
            Set(ByVal value As Integer)
                SetValue(StartProperty, value)
            End Set
        End Property
    
    
        Public Shared ReadOnly EndOffsetProperty As DependencyProperty = DependencyProperty.Register("EndOffset", GetType(Integer), GetType(Window1), New PropertyMetadata(0))
        Public Property EndOffset() As Integer
            Get
                Return GetValue(EndOffsetProperty)
            End Get
            Set(ByVal value As Integer)
                SetValue(EndOffsetProperty, value)
            End Set
        End Property
    
    
        Private Sub CheckRangeButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
            StartOffset = rtb.Document.ContentStart.GetOffsetToPosition(rtb.Selection.Start)
            EndOffset = rtb.Document.ContentStart.GetOffsetToPosition(rtb.Selection.End)
            _Range = New TextRange(rtb.Selection.Start, rtb.Selection.End)
        End Sub
    
        Private Sub MarkRangeButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
            Dim startPos As TextPointer = rtb.Document.ContentStart.GetPositionAtOffset(StartOffset)
            Dim endpos As TextPointer = rtb.Document.ContentStart.GetPositionAtOffset(EndOffset)
            rtb.Selection.Select(startPos, endpos)
            rtb.Selection.ApplyPropertyValue(ForegroundProperty, Brushes.Yellow)
        End Sub
    
        Private _Range As TextRange
    
        Private Sub rtb_SelectionChanged(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
            If _Range IsNot Nothing Then
                StartOffset = rtb.Document.ContentStart.GetOffsetToPosition(_Range.Start)
                EndOffset = rtb.Document.ContentStart.GetOffsetToPosition(_Range.End)
            End If
        End Sub
    End Class
    

    This is the XAML:

    <Window x:Class="Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300"
        x:Name="myWindow">
      <DockPanel>
        <Button x:Name="CheckRangeButton" Click="CheckRangeButton_Click" DockPanel.Dock="Bottom">_Check TextRange</Button>
        <Button x:Name="MarkRangeButton" Click="MarkRangeButton_Click" DockPanel.Dock="Bottom">_Mark TextRange</Button>
        <TextBlock DockPanel.Dock="Bottom">
          <TextBlock.Text>
            <MultiBinding StringFormat="Start offset: {0}, End offset: {1}">
              <Binding ElementName="myWindow" Path="StartOffset"/>
              <Binding ElementName="myWindow" Path="EndOffset"/>
            </MultiBinding>
          </TextBlock.Text>
        </TextBlock>
        <RichTextBox x:Name="rtb" SelectionChanged="rtb_SelectionChanged">
          <FlowDocument>
            <Paragraph>
              Severe winter weather continued its stranglehold on Europe as travelers battled heavy snow, airlines canceled flights and motorists were left stranded.
            </Paragraph>
          </FlowDocument>
        </RichTextBox>
    </DockPanel>
    </Window>
    

    To me it looks like changing the Selection per se doesn't influence the start and end of the TextRange once it is marked. The offsets for the word "Europe" are one greater than what you wrote, but that is to be expected, because of the Paragraph that surrounds the text.

    So, what doesn't work as expected here?
    http://wpfglue.wordpress.com
    Thursday, December 24, 2009 12:33 PM
  • Was it so difficult for WPF to provide plain text version of the structured model? All is messed up when working with selections.

    Saturday, May 18, 2019 9:28 AM