为何WPF RichTextBox中TextDecorationCollection始终为空?
我查过不少同领域处理下划线逻辑的相关问题,绝大多数内容围绕添加下划线的实现展开,很少涉及下划线检测场景,现有方案普遍逻辑冗余复杂,且没有提到常规实现会出现不符合逻辑的假阴性漏判问题。另外查到的多数过往方案要么依赖TextBlock这类其他控件,要么使用的是已经过时的语法。
问题描述
在*.NET Core 3.1*环境下,需要通过编程方式检测WPF RichTextBox的选中区域是否包含任意TextDecorations格式,但调试过程中发现,哪怕选中的内容全部带有下划线,TextDecorationCollection始终返回空值。
调试时选中完全带有下划线的内联元素(Run),TextDecorationCollection返回值仍为空,对应FlowDocument的结构已确认下划线格式真实存在。
已尝试方案
- 方案1
TextRange myrange = new TextRange(MainRtb.Selection.Start, MainRtb.Selection.End); if (myrange.GetPropertyValue(Inline.TextDecorationsProperty).Equals(TextDecorations.Underline)) { }
- 方案2
TextRange myrange = new TextRange(MainRtb.Selection.Start, MainRtb.Selection.End); var obj = myrange.GetPropertyValue(Inline.TextDecorationsProperty); if (obj == DependencyProperty.UnsetValue) { log.addLog("mix format"); } if (obj is TextDecorationCollection) { var objProper = obj as TextDecorationCollection; if (objProper.Count > 0) { log.addLog("all underlined"); } else { log.addLog("none underlined"); } }
- 方案3
foreach (Block block in MainRtb.Document.Blocks) { Paragraph p = block as Paragraph; if (p != null) { foreach (Inline inline in p.Inlines) { InlineUIContainer iuic = inline as InlineUIContainer; if (iuic != null) { Console.WriteLine("found underline"); } } } }
问题原因
早年技术论坛有帖子提到GetPropertyValue方法存在缺陷,实际验证下来核心问题有三个:
- 方案1直接用
Equals(TextDecorations.Underline)做判断属于引用比较,通过RTF加载、手动代码设置的下划线会生成新的TextDecorationCollection实例,哪怕集合内的装饰项完全一致,引用不同也会返回false,必然漏判。 TextDecorationsProperty是集合类型依赖属性,GetPropertyValue处理这类属性时,只要选区范围内的属性值不是同一个实例,就会返回DependencyProperty.UnsetValue,哪怕所有内容都带有下划线,只要TextDecorationCollection是分别创建的,就会被判定为格式混合,直接走不到集合长度判断的分支。- 方案3存在两个明显错误:一是只遍历了Paragraph下的第一层Inlines元素,遇到Span、Bold、Hyperlink这类嵌套包裹的Run元素会直接漏掉;二是错误将InlineUIContainer作为下划线判断依据,这个类型是UI元素容器,和下划线格式没有任何关联,完全无法实现检测。
早年帖子给出的旧方案在VS2019/.NET Core 3.1环境下无法编译,会提示IEnumerable需要显式声明类型,不能直接使用。
可行实现
直接用递归遍历范围内所有文本元素的方式检测,不存在漏判问题,兼容.NET Core 3.1环境:
/// <summary> /// 检测选中区域是否包含下划线格式 /// </summary> private bool SelectionHasUnderline() { TextRange selectionRange = new TextRange(MainRtb.Selection.Start, MainRtb.Selection.End); return CheckUnderlineInRange(selectionRange.Start, selectionRange.End); } private bool CheckUnderlineInRange(TextPointer start, TextPointer end) { TextPointer current = start; while (current != null && current.CompareTo(end) < 0) { TextElement element = current.GetAdjacentElement(LogicalDirection.Forward) as TextElement; if (element != null) { // 检测内联元素的下划线 if (element is Inline inline) { if (inline.TextDecorations != null && inline.TextDecorations.Any(td => td.Location == TextDecorationLocation.Underline)) { return true; } } // 检测块级元素设置的下划线 if (element is Block block) { if (block.TextDecorations != null && block.TextDecorations.Any(td => td.Location == TextDecorationLocation.Underline)) { return true; } } // 递归检测嵌套容器内的元素 if (element is Span span) { if (CheckUnderlineInRange(span.ContentStart, span.ContentEnd)) { return true; } } current = element.ContentEnd; } else { current = current.GetNextContextPosition(LogicalDirection.Forward); } } return false; }
调用SelectionHasUnderline()即可得到正确的检测结果,不会出现全部带下划线仍返回空的假阴性问题。
内容的提问来源于stack exchange,提问作者Micsupreeme
相关产品推荐
相关产品推荐

