How to get the Current Line Index in WinUI UWP TextBox?

Viewed 222

I know that SelectionStart property of WinUI UWP TextBox will return the CaretIndex. But, I want to get the exact Column and Line Position of Text. In WPF, GetLineFromCharacterIndex(CaretIndex) and TextBox.Lines[LineIndex].Length could be used to find the Current Line Index and Column number respectively. How can I achieve the same in WinUI UWP Textbox ?

3 Answers

Try this method:

public static int GetCurrentLineIndex(TextBox textBox)
{
    int caretIndex = textBox.SelectionStart;
    if (caretIndex == 0)
        return 0;

    string[] lines = textBox.Text?.Split('\r') ?? Array.Empty<string>();
    int offset = 0;
    for (int i = 0; i < lines.Length; i++)
    {
        string line = lines[i];
        offset += line.Length;
        if (caretIndex <= offset)
            return i;
        offset++;
    }

    return 0;
}

It may need some slight improvement but it should give you the idea how you could determine the current line of the cursor.

You can call it from wherever you want to get the index, e.g.:

private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    int index = GetCurrentLineIndex(sender as TextBox);
    //...
}

Maybe you could do something like this:

var text = Textbox.Text;
var lines = text.Split('\r');
...

This has worked for me in the past using WPF but I have never tried UWP.

This also seems like a workaround so there might be a better, more practical, solution.

This example uses MVVM structure but you can apply the same concepts with a temp variable which stores the previous value.

<TextBox Height="600" Width="600" 
         Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
         TextWrapping="Wrap" AcceptsReturn="True"/>

Then I added this to the constructor:

this.DataContext = this;

This isnt best practice and if you were using MVVM you would set up a ViewModel and use that (I did this for testing purposes).

Then I created my properties like this:

    private int _line;

public int Line
{
    get { return _line; }
    set
    {
        _line = value;
        tb1.Text = value.ToString();
    }
}

private int _column;

public int Column
{
    get { return _column; }
    set
    {
        _column = value;
        tb2.Text = value.ToString();
    }
}

private string _text;

public string Text
{
    get { return _text; }
    set
    {
        if (_text + '\r' != value)
        {
            Line = GetLine(_text, value);
            Column = GetColumn(_text, value, Line);
        }
        else
        {
            Line++;
            Column = 0;
        }

        _text = value;
    }
}

Then added my functions:

public int GetLine(string original, string newText)
{
    var oLines = GenArray(original);
    var nLines = GenArray(newText);

    //set this to -1 if you want 0-based indexing
    int count = 0;
    foreach (var line in nLines)
    {
        count++;
        if (oLines.Length < count || line != oLines[count - 1])
        {
            break;
        }
    }

    return count;
}

public int GetColumn(string original, string newText, int lineChanged)
{
    var oLine = GenArray(original)[lineChanged - 1];
    var nLine = GenArray(newText)[lineChanged - 1];

    //set this to -1 if you want 0-based indexing
    int count = 0;
    foreach (var c in nLine)
    {
        count++;
        if (oLine.Length < count || c != oLine[count - 1])
        {

        }
    }

    return count;
}

private string[] GenArray(string text)
{
    string[] lines;
    if (text == null)
    {
        lines = new string[1] { "" };
    }
    else if (text.Contains('\r'))
    {
        lines = text.Split('\r');
    }
    else
    {
        lines = new string[1] { text };
    }

    return lines;
}

If you don't use MVVM just do this:

public string[] TempLines { get; set; }
...
//after the calculation code has finished
TempLines = TextBox.Split('\r');

Then you can substitute TempLines for value

Related