【问题标题】:WinForms RichTextBox selection issue if there is hidden text如果有隐藏文本,WinForms RichTextBox 选择问题
【发布时间】:2017-05-29 16:38:24
【问题描述】:

我遇到了 RichTextBox 控件的选择问题。如果控件包含隐藏文本,则选择的行为会很奇怪。

  • 如果我用鼠标进行选择,有时会出现错误,有时不会。特别是如果我选择更多行,错误似乎消失了。
  • 但如果用户尝试使用 键盘 选择文本,则该错误非常烦人。

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

有一点升级的控制,希望能做一个 不同的时候是隐藏文字的原因

然后假设我们通过应用适当的 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}";

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

包含隐藏文本对我的控件至关重要(我的对象中构成控件内容的一些重要 id 存储为特殊位置的隐藏文本,我不能/不想更改那个)。

【问题讨论】:

  • 你能用干净的新项目重现这个问题吗?我的意思是你可以为这个问题创建一个MCVE 吗?
  • 是的,它似乎很容易重现。我不认为这是我的代码中的错误,而是富文本框控件的行为,或者控件本身的错误。 here you can find sample project with the bug 只需单击文本的开头(在第一个文本框内)它已经包含隐藏内容,按住 shift 并按左箭头,就像您通常使用键盘选择文本一样。选择会在到达隐藏词时重置
  • 你能提供一个 MCVE 吗?我的意思是至少使用了 RTF。只需检查您是否可以使用带有该 RTF 的干净项目真正重现它。
  • 我不知道如何在这里发布链接 :),所以我不得不编辑我之前的评论。请在我之前评论的链接中找到测试项目。感谢您提供帮助。
  • 您最好将示例 RTF 放在问题本身中。 richTextBox.Rtf = @"{\rtf1\ansi{\fonttbl\f0\fswiss Helvetica;}\f0\pard This is some {\b bold} text.\par }" 之类的东西,因此可以使用 richTextBox 创建新的 WinForms 项目并重现该问题。

标签: c# winforms selection richtextbox hidden


【解决方案1】:

我正在使用以下Form,其中richTextBox 是有问题的RichTextBoxRichTextBox_SelectionChangedSelectionChanged 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;

并更新/修复 SelectionChanged 处理程序中的选择

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;
    }
    

PS: 为简单起见,我在表单中实现了所有内容,包括字段和表单级处理程序,但通过一些努力,它可以变成可重用的东西(最坏的情况是派生 RichTextBox,最好的情况是一些将为RichTextBox 提供此类处理的外部组件。

【讨论】:

  • 感谢您的回复,很抱歉在很长一段时间后将其标记为已解决。这是我问题的正确答案,但不幸的是,它不是我们问题的答案。由于我们的规则略有变化,我们不得不采取另一个方向。根据新要求,将我们的数据隐藏在文本中是不可能的,也不允许这样做。无论如何,这是一个有趣的问题,感谢您的所有帮助。
猜你喜欢
  • 1970-01-01
  • 2011-04-10
  • 2013-04-26
  • 2012-02-28
  • 1970-01-01
  • 2014-11-02
  • 1970-01-01
  • 2022-06-11
  • 2014-02-28
相关资源
最近更新 更多