事情真的很曲折.
最开始需要画出波浪线. 在考虑过spellcheck不适合之后, 决定自己做一个custom control, 直接继承自TextBox.
修改它的模板, 在里面增加一个listbox, 然后创建一个listbox的style,让其ListBox的ItemPanel为空的Grid从而消除它的边框; 修改ItemContainerStyle使之里面只放一个Path,或者直接修改ItemTemplate使其为一个Path. Path的Data用TemplateBinding绑定到控件里的一个ErrorList中, 用转换类实现从数据转换为Geometry.
画线还没完. 当文本框中的内容很多, 鼠标拖动的时候显示的文本就会变化, 那红线肯定也要相应的变话.
由于计算红线位置的函数是异步的, 一开始是用TextChanged做计算错误位置,然后用SelectionChanged做红线偏移,结果就是SelectionChanged先记录下红线的位置,准备做偏移,同时或者之前或者之后TextChanged准备计算错误位置, 然后TextChanged会先完成然后保存新的位置, 然后SelectionChanged做完偏移又保存一遍,结果TextChanged就白做了... UI元素中加锁肯定不现实,只好改变思路:
因为View和ViewModel完全分离,所以没法直接绑定事件.不过可以利用Interactivity来绑定,把元素自己当做参数传进去.
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectionChangedCommand}" CommandParameter="{Binding ElementName=myErrorTextBox}" />
</i:EventTrigger>
</i:Interaction.Triggers>
在selectionChangedCommand里做错误检查,来更新ErrorList. 这时候只要在TextBox的ErrorList属性的PropertyChangedCallback里加上偏移位置修正就可以了.要注意的是这里不是new 一个ErrorList,而是更改ErrorList里面每个错误的偏移, 因为如果用new就会把原来的数据绑定属性毁了,以后就没法产生更新了.
事情就是这么简单,但是之前试了很多歪路都不得正解,下面列出其中一些:
尝试过使用new出来的ErrorList重新赋给ErrorTextBox, 结果就是错误没法更新了.
尝试过在ErrorTextBox里写一个static的方法,传入一个ErrorTextBox然后修正其错误位置,但是这个方法的根本问题是错误检查是异步的,不论在哪里调用这个static 方法,它的参数都是没有更新位置的ErrorList,然后异步的错误检查更新ErrorList之后,这个没有更新位置的ErrorList会覆盖之,导致线程安全问题.
尝试过把上面的static方法改成非静态,然后在ErrorList更新之后调用这个ErrorTextBox的位置修正方法,但是和上面一样. 跟进去发现ErrorList更新之后虽然ErrorTextBox在修正的时候会把修正位置改入ErrorList,但是红线并不会发生改变并且下一次这个修正位置就丢失了. 猜测是数据绑定的机制造成的原因, 把Mode改成OneWay也没有用.
总之现在看起来效果还不错. 就这样吧.
代码如下:
ErrorTextBox.xaml
<!--Control colors.-->
<Color x:Key="WindowColor">#FFE8EDF9</Color>
<Color x:Key="ContentAreaColorLight">#FFC5CBF9</Color>
<Color x:Key="ContentAreaColorDark">#FF7381F9</Color>
<Color x:Key="DisabledControlLightColor">#FFE8EDF9</Color>
<Color x:Key="DisabledControlDarkColor">#FFC5CBF9</Color>
<Color x:Key="DisabledForegroundColor">#FF888888</Color>
<Color x:Key="SelectedBackgroundColor">#FFC5CBF9</Color>
<Color x:Key="SelectedUnfocusedColor">#FFDDDDDD</Color>
<Color x:Key="ControlLightColor">White</Color>
<Color x:Key="ControlMediumColor">#FF7381F9</Color>
<Color x:Key="ControlDarkColor">#FF211AA9</Color>
<Color x:Key="ControlMouseOverColor">#FF3843C4</Color>
<Color x:Key="ControlPressedColor">#FF211AA9</Color>
<Color x:Key="GlyphColor">#FF444444</Color>
<Color x:Key="GlyphMouseOver">sc#1, 0.004391443, 0.002428215, 0.242281124</Color>
<!--Border colors-->
<Color x:Key="BorderLightColor">#FFCCCCCC</Color>
<Color x:Key="BorderMediumColor">#FF888888</Color>
<Color x:Key="BorderDarkColor">#FF444444</Color>
<Color x:Key="PressedBorderLightColor">#FF888888</Color>
<Color x:Key="PressedBorderDarkColor">#FF444444</Color>
<Color x:Key="DisabledBorderLightColor">#FFAAAAAA</Color>
<Color x:Key="DisabledBorderDarkColor">#FF888888</Color>
<Color x:Key="DefaultBorderBrushDarkColor">Black</Color>
<!--Control-specific resources.-->
<Color x:Key="HeaderTopColor">#FFC5CBF9</Color>
<Color x:Key="DatagridCurrentCellBorderColor">Black</Color>
<Color x:Key="SliderTrackDarkColor">#FFC5CBF9</Color>
<Color x:Key="NavButtonFrameColor">#FF3843C4</Color>
<LinearGradientBrush x:Key="MenuPopupBrush"
EndPo