影响单行的 Wpf 多行文本框样式

Wpf multiline textbox style that affects the individual lines

我需要制作一个多行文本框,在该行超过 80 个字符后更改字符的背景。因此,如果有人键入一个 85 个字符的句子,最后 5 个字符将具有黄色背景。我希望它能使此功能成为样式的一部分,因为目前我们正试图通过代码背后的逻辑来做到这一点,并且当有人快速输入时它会滞后。

当前突出显示 imlp

 private void HighlightLines()
        {
            try
            {
                //// x is the distance in the row from the left side of the Textbox. e.g. Each character is one unit.
                int x = 0;
                //// y is the distance in the columns from the top of the Textbox. e.g. Each character is one unit.
                int y = 0;
                //// lines is the number of newline characters found in the Text.
                int lines = 0;
                //// Point 1 is the starting point of the range that needs to be highlighted.
                TextPointer point1 = this.Document.ContentStart;
                //// Point 2 is the end point of the range that needs to be highlighted.
                TextPointer point2 = this.Document.ContentStart;
                //// Range is the distance from Point 1 to Point 2 needed to apply the Yellow color to the area past 69 characters.
                TextRange range;
                //// Additional Ranges is the collection of all the ranges that need to be Yellow.
                this.AdditionalRanges = new ObservableCollection<TextRange>();

                //// Count the number of lines.
                for (int i = 0; i < this.Text.Length; i++)
                {
                    if (this.Text[i] == '\n')
                    {
                        lines++;
                        this.AdditionalRanges.Add(new TextRange(this.Document.ContentStart, this.Document.ContentEnd));
                    }
                }
                //// This map is used to differentiate which lines need to be colored. (True means the range is over 69 characters. False means the opposite).
                bool[] map = new bool[lines];

                //// Traverse the whole text.
                for (int i = 0; i < this.Text.Length; i++)
                {
                    var currentCharacter = this.Text[i];
                    var newLineCharacter = '\n';

                    if (currentCharacter == newLineCharacter)
                    {
                        var pointDifference = point1.GetOffsetToPosition(point2);
                        point1 = point1.GetPositionAtOffset(pointDifference);
                        x = 0;
                        y++;
                    }
                    else if (x > 69)
                    {
                        range = new TextRange(point1, point2);
                        this.AdditionalRanges[y] = range;
                        map[y] = true;
                        if (point2.GetNextInsertionPosition(LogicalDirection.Forward) != null)
                        {
                            point2 = point2.GetNextInsertionPosition(LogicalDirection.Forward);
                        }
                    }
                    else if (point1.GetNextInsertionPosition(LogicalDirection.Forward) != null && point2.GetNextInsertionPosition(LogicalDirection.Forward) != null)
                    {
                        point1 = point1.GetNextInsertionPosition(LogicalDirection.Forward);
                        point2 = point2.GetNextInsertionPosition(LogicalDirection.Forward);
                        x++;
                    }
                }

                //// Make everything white.
                foreach (var item in this.AdditionalRanges)
                {
                    item.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.White);
                }

                //// Make the appropriate ranges Yellow.
                for (int i = 0; i < this.AdditionalRanges.Count; i++)
                {
                    if (map[i])
                    {
                        this.AdditionalRanges[i].ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Yellow);
                    }
                }
            }
            catch (Exception e)
            {
                // drop exception. this has only broke once and we dont exactly know why. 
            }
        }

它可能滞后,因为您的 HighlightLines() 方法每次都扫描整个文档,我假设它在每次按键后都会被调用。理想情况下,您只想重新扫描已更改的文本部分。幸运的是,TextChanged 事件提供了更改的确切偏移量。

下面的示例代码是为与 RichTextBox 一起工作而编写的,但您应该能够对其进行调整。此外,看起来您的代码正在检查 69 个字符而不是 80 个字符,所以这样做也是一样的:

RichTextBox txt;
...
bool suppressChanges = false;
private void Txt_TextChanged(object sender, TextChangedEventArgs e)
{
    if (!suppressChanges)
    {
        // suppress changes because changing highlights will trigger the event again
        suppressChanges = true;
        foreach (var change in e.Changes)
        {
            var changeStart = txt.Document.ContentStart.GetPositionAtOffset(change.Offset);
            TextRange changedRange;
            if (change.AddedLength > 0)
                changedRange = new TextRange(changeStart, changeStart.GetPositionAtOffset(change.AddedLength));
            else
                changedRange = new TextRange(changeStart, changeStart);
            SetRangeColors(changedRange);
        }

        //unsuppress changes
        suppressChanges = false;
    }
}

void SetRangeColors(TextRange range)
{
    // Scan one line at a time starting with the beginning of the range
    TextPointer current = range.Start.GetLineStartPosition(0);
    while (current != null && current.CompareTo(range.End) < 0)
    {
        // find the next line or the end of the document
        var nextLine = current.GetLineStartPosition(1, out int lines);
        TextPointer lineEnd;
        if (lines > 0)
            lineEnd = nextLine.GetNextInsertionPosition(LogicalDirection.Backward);
        else
            lineEnd = txt.Document.ContentEnd;
        var lineRange = new TextRange(current, lineEnd);

        // clear properties first or the offsets won't match the characters
        lineRange.ClearAllProperties();
        var lineText = lineRange.Text;
        if (lineText.Length > 69)
        {
            var highlight = new TextRange(current.GetPositionAtOffset(70), lineEnd);
            highlight.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Yellow);
        }

        // advance to the next line
        current = lineEnd.GetLineStartPosition(1);
    }
}