如果存在隐藏文本,WinForms RichTextBox 选择问题

WinForms RichTextBox selection issue if there is hidden text

我有一个 selectRichTextBox 控件的问题。如果控件包含隐藏文本,selection 会表现得很奇怪。

问题如下:假设我的控件有以下文本:

There is the little upgraded control that hopefully will make a differnce when it is hidden text the reason

然后假设我们通过应用适当的 RTF 标签来隐藏单词 upgraded, hopefully, hidden

@"{\rtf1\ansi\ansicpg1252\deff0\deflang2057{\fonttbl{\f0\fni‌​l\fcharset0 Microsoft Sans Serif;}} \viewkind4\uc1\pard\f0\fs17 There is the little \v upgraded \v0 control that \v hopefully \v0 will make a differnce when it is \v hidden \v0 text the reason\par}";

一切看起来都不错,但是当用户尝试使用键盘 select 文本时,selection 似乎每次到达隐藏词时都会重置。

我的控件包含隐藏文本是至关重要的(一些来自我的对象的重要 ID 在控件内形成内容作为隐藏文本存储在特殊位置,我可以't/don'我不想改变它)。

我正在使用以下 Form,其中 richTextBox 是有问题的 RichTextBoxRichTextBox_SelectionChanged 是我们将尝试的 SelectionChanged event 处理程序用于解决我们的问题。

public MainForm()
{
    InitializeComponent();

    this.richTextBox.Rtf =
        @"{\rtf1\ansi\ansicpg1252\deff0\deflang2057{\fonttbl{\f0\fni‌​l\fcharset0 Microsoft Sans Serif;}}\viewkind4\uc1\pard\f0\fs17 My \v upgraded \v0 control that \v hopefully \v0 will make it\par}";            
    this.richTextBox.SelectionChanged += RichTextBox_SelectionChanged;
}

基本上,这个想法很简单 - 使用 SelectionChanged 处理程序正确地 Select 隐藏数据以及先前的选择。

为此,我们必须存储之前的选择数据:

private class SelectionData
{
    public static SelectionData FromStartAndEnd(
        Int32 start,
        Int32 end)
    {
        return new SelectionData(
            start: start,
            length: end - start);
    }

    public SelectionData(TextBoxBase tb)
        : this(
            start: tb.SelectionStart,
            length: tb.SelectionLength)
    {            }

    public SelectionData(Int32 start, Int32 length)
    {
        this.Start = start;
        this.Length = length;
    }

    public readonly Int32 Start, Length;
    public Int32 End
    {
        get
        {
            return this.Start + this.Length;
        }
    }
}

在某些领域:

private SelectionData _previousSelection;

和update/fix选择里面的SelectionChanged hanlder

private void RichTextBox_SelectionChanged(object sender, EventArgs e)
{
    var newSelection = new SelectionData(this.richTextBox);
    this.SelfUpdateSelection(newSelection);
}

SelfUpdateSelection 方法类似于:

private Boolean _isSelectionSelfUpdating = false;

private void SelfUpdateSelection(SelectionData newSelection)
{
    if (!this.IsKeyBoardSelection())
    {
        // Or it will use previous selection when we don't need it.
        this._previousSelection = null; 
        return;
    }
    if (this._isSelectionSelfUpdating)
        return;

    this._isSelectionSelfUpdating = true;
    try
    {
        var fixedSelection = this.FixSelection(newSelection);
        this.richTextBox.Select(
            start: fixedSelection.Start,
            length: fixedSelection.Length);
        this._previousSelection = fixedSelection;
    }
    finally
    {
        this._isSelectionSelfUpdating = false;
    }
}

IsKeyBoardSelection 为简单起见,可以像下面这样,但正确检测选择更改源会更加困难:

private bool IsKeyBoardSelection()
{
    // It may not be true, but usually close enough.
    return Control.ModifierKeys.HasFlag(Keys.Shift);
}

FixSelection 方法应该比较 newSelection 是否可以是 this._previousSelection 并创建一个新的 SelectionData 将同时包含 newSelectionthis._previousSelection 以及它们之间隐藏的数据。

你可以这样使用:

private SelectionData FixSelection(SelectionData newSelection)
{
    if (this._previousSelection == null)
        return newSelection;

    var start = Math.Min(
        newSelection.Start,
        this._previousSelection.Start);
    var end = Math.Max(
        newSelection.End,
        this._previousSelection.End);
    return SelectionData.FromStartAndEnd(
        start: start,
        end: end);
}

但是它:

  • 仅适用于 向前(向右箭头)选择 - 可以通过向 FixSelection 添加一些额外的逻辑来解决。
  • 还需要一些额外的this._previousSelection处理(比如在 FocusLost 事件上重置它)——有一些边缘情况,但仍然没有什么不可能。

    public MainForm()
    {
        ...
        this.richTextBox.LostFocus += RichTextBox_LostFocus;
    }
    
    private void RichTextBox_LostFocus(object sender, EventArgs e)
    {
        this._previousSelection = null;
    }
    

P.S.: 为简单起见,我已经在表单中实现了带有字段和 form-level 处理程序的所有内容,但通过一些努力,它可以变成一些可重用的东西(最坏的派生 RichTextBox,最好的一些外部组件将为 RichTextBox 提供这样的处理)。