System.Windows.Controls.TextBox.GetCharacterIndexFromLineIndex returns increasingly incorrect values for larger line numbers
- Dominant language
- C#
- Stars
- 7.7k
- Forks
- 1.3k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 61
Description
* Windows version: 1809
* Tested in .NET Framework Version 4.7.2
After trying to answer [this question](https://stackoverflow.com/questions/61502433/get-wpf-textbox-row-and-column-position) on StackOverflow, I'm going ahead and reporting this as a bug in the WPF.
---
**Summary**
After some number of lines, [`TextBox.GetCharacterIndexFromLineIndex`](https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.textbox.getcharacterindexfromlineindex) seems to erroneously return the starting index of the *next* line down, rather than the line at the given index.
In the StackOverflow question above, the originator said that this happened starting at line 16,312. In my tests, it started at line 8,512. I will be using 8,512 for my example, but whoever tries to reproduce this should be aware that it might start at a different line number.
The method skips an additional line for every 17,024 lines after 8,512 (note 8,512 * 2 = 17,024). Meaning starting at 25,536 the method skips 2 lines, at 42,560 it skips 3 lines, etc. The original poster on StackOverflow experienced the same pattern. For them, the increment was 32,624 and started at half that value (16,312).
Be aware that the last x lines of the TextBox gradually start returning correct values, where "x" is the number of lines that were being skipped up to that point. For example, if there are 42,600 lines, the return value for line 42,600 is correct (zero lines off), the value for line 42,599 is one line off, line 42,598 is two lines off, and every line before that is off by three. Remember this when testing or trying to reproduce.
---
**Reproduction Code**
MainWindow.xaml:
```
```
MainWindow.xaml.cs
```
using System.Text;
using System.Windows;
namespace Test
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
StringBuilder text = new StringBuilder();
for (int i = 0; i < 18000; i++)
{
text.AppendLine("Another Line");
}
TB.Text = text.ToString().TrimEnd(Environment.NewLine.ToCharArray());
}
private void TB_SelectionChanged(object sender, RoutedEventArgs e)
{
var caretIndex = TB.CaretIndex;
var line = TB.GetLineIndexFromCharacterIndex(caretIndex);
var colStart = TB.GetCharacterIndexFromLineIndex(line);
var pos = caretIndex - colStart;
System.Diagnostics.Debug.WriteLine($@"Line {line}, ColStart {colStart}, Caret {caretIndex}, Pos {pos}");
}
}
}
```
The above code generates a `Window` with a `TextBox` containing an 18,000 line `string`. The number of lines can be increased if needed. Changing the selection (caret position) sends various variables to the debug output.
- Line: The line number the caret is on
- ColStart: The index of the first character of that line - using the bugged `GetCharacterIndexFromLineIndex` method
- Caret: The overall index of the caret in the string
- Pos: The index of the caret within the current line
---
**Reproducting the Bug**
Place the caret at the very beginning of the first line of text (line 0, since we are using 0-based indices). Note the variables in the debug output- they should all be `0`.
Go down one line so that the caret is now at the start of line 1. Note that `ColStart` and `Caret` both output as `14`. This is what we expect. Whenever we put the caret at the beginning of a line, `ColStart` and `Caret` *should* be the same, since `GetCharacterIndexFromLineIndex` is supposed to return the starting index of the line.
Now scroll down to the bottom of the TextBox and put the caret at the beginning of the *second-to-last* line (line number 17,998). In my tests (and hopefully yours), you'll see that `ColStart` and `Caret` are no longer equal- `251,986` and `251,972` respectively. The result is that `Pos`- our caret position in the current line, shows as `-14`- fourteen characters *behind* the start of the line.
So at the beginning of the TextBox, we get correct values, and toward the bottom we get incorrect values; at some point `ColStart` stops agreeing with `Caret`.
You can pinpoint the exact line where the `ColStart` output becomes incorrect by process of elimination, go up and down the `TextBox`, narrowing it down. The original poster on StackOverflow said the incorrect values started at line 16,312, but my tests found it to start on line 8,512. Since that's what I can reproduce, I'm using that number in my instructions.
The `Caret` value at the end of line 8,511 (the last correct line) is `119,166`. Pressing the right arrow key moves the caret down to the start of the next line. `Caret` increases by 2, as you would except (since Windows' newline is two characters long), but `ColStart` has suddenly jumped ahead by 16! Again, that's 14 more than we'd except.
14 characters is the length of one of our lines (including newline characters), that's where the extra comes from. You can add or remove text from line 8,512, then return the caret to the start of that line and see that `ColStart` has changed. This confirms that `GetLineIndexFromCharacterIndex` is indeed returning the value for line 8,513, even though it is being asked for `8,512`.
All line indices including and after 8,512 erroneously return the starting index for the *next* line, up until 25,536 (8,512 + 8,512 * 2), where it starts skipping two lines. This pattern continues as described in the summary.
Contributor guide
Assessment
This issue has not been assessed yet.