为了避免这是一个 X-Y 问题,我将首先解释我想要实现的目标,然后解释我尝试实现它但失败的原因。
问题:我正在搜索一堆文本。搜索会产生许多来自不同文本的命中,当我单击命中时,我希望查看器打开文本,加粗搜索模式的匹配项,并自动滚动到该文本的位置。同时遵守 MVVM 模式,并且尽可能不通过在 ViewModel 中使用 UI 元素来破坏它。我使用 Caliburn Micro 作为 MVVM 框架。最后,文本是阿拉伯语的。
我尝试过的:
ListBox:虽然它支持滚动和格式化,但它将文本分成列表项,不允许选择多行。如果我将文本合并到一个项目中,我将失去格式化和滚动的能力。所以我很快就把它扔掉了。
TextBox:它缺乏对文本的特定部分进行格式化而使其余文本保持未格式化的能力,并且不支持滚动到特定位置。
RichTextBox(本机和扩展 WPF):可以对其进行调整以允许通过绑定进行滚动。但是,这对于阿拉伯语来说是可怕的,我发现可用的解析器无法生成阿拉伯语的 RTF 文本。
ScrollView 和 TextBlock:我找到了扩展它的代码,以允许选择和复制文本,以及绑定到格式化文本的能力。我可以让它轻松突出显示我需要的部分,我也可以从中选择和复制。问题是,我无法动态确定其高度并绑定到它,以便我可以滚动到正确的位置。
第四个选项是我目前正在使用的选项,这是 XAML:
<Border Grid.Row="1" Grid.Column="4" BorderThickness="0.5" BorderBrush="Gray" Margin="5">
<ScrollViewer local:Attached.VerticalOffset="{Binding ViewerScrollOffset, Mode=TwoWay}">
<local:SelectableTextBlock Margin="5" local:Attached.FormattedText="{Binding DisplayText}"
FlowDirection="RightToLeft" TextWrapping="Wrap" />
</ScrollViewer>
</Border>
VerticalOffset
代表 ScrollViewer
,FormattedText
代表 SelectableTextBlock
。 (关键词中链接的来源)。
我可以滚动到
ScrollViewer
中的位置,但由于高度因 TextBlock
中文本的大小而变化,因此在不知道完整高度的情况下无法判断要去哪里。我知道可以从后面的代码访问 ScrollableHeight
属性,但它会破坏 MVVM 模式,我希望有一个解决方案可以通过正确绑定来实现此目的。我尝试以多种方式绑定到 Height
和 ScrollViewer
的 TextBlock
(更改模式、获取祖先高度、使用触发器等),但它不起作用,我认为它甚至不是正确的属性检索。
我如何绑定到
ScrollableHeight
以便我可以准确计算我的 VerticalOffset
需要在哪里?是否有我忘记的更好的方法可以解决我在问题开头所述的问题?
好吧,所以我花了一些时间修改一些东西,但我最终让它工作起来,我认为这是任何具有附加属性
ScrollViewer
的必要补充。我能够创建一个新的附加属性,在 VirtualOffset
更改时报告它,该属性基于
ScrollableHeight
事件,其中
ScrollChanged
或 ExtentHeightChange
MAY 中的任何更改都会导致ViewportHeightChange
的变化。但是,有可能 ScrollabeHeight
或 ViewportHeight
已更改而 ExtentHeight
未更改,并且在调整视图大小但文本不够大而无法滚动时会发生这种情况。这是附加的属性代码(ScrollableHeight
是我用于附加属性的类):
Attached
然后我在视图中绑定它:
public static readonly DependencyProperty VerticalOffsetLimitProperty =
DependencyProperty.RegisterAttached("VerticalOffsetLimit", typeof(double),
typeof(Attached), new FrameworkPropertyMetadata(double.NaN,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnVerticalOffsetLimitPropertyChanged));
private static readonly DependencyProperty VerticalScrollHeightBindingProperty =
DependencyProperty.RegisterAttached("VerticalScrollLimitBinding", typeof(bool?), typeof(Attached));
public static double GetVerticalOffsetLimit(DependencyObject depObj)
{
return (double)depObj.GetValue(VerticalOffsetLimitProperty);
}
public static void SetVerticalOffsetLimit(DependencyObject depObj, double value)
{
depObj.SetValue(VerticalOffsetLimitProperty, value);
}
private static void OnVerticalOffsetLimitPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
ScrollViewer scrollViewer = d as ScrollViewer;
if (scrollViewer == null)
return;
BindVerticalOffsetLimit(scrollViewer);
}
public static void BindVerticalOffsetLimit(ScrollViewer scrollViewer)
{
if (scrollViewer.GetValue(VerticalScrollHeightBindingProperty) != null)
return;
scrollViewer.SetValue(VerticalScrollHeightBindingProperty, true);
scrollViewer.ScrollChanged += (s, se) =>
{
if (se.ViewportHeightChange == 0 && se.ExtentHeightChange==0)
return;
SetVerticalOffsetLimit(scrollViewer, scrollViewer.ScrollableHeight);
};
}
我让属性发出
<Border Grid.Row="1" Grid.Column="4" BorderThickness="0.5" BorderBrush="Gray" Margin="5">
<ScrollViewer local:Attached.VerticalOffsetLimit="{Binding ViewerScrollLimit, NotifyOnTargetUpdated=True}"
local:Attached.VerticalOffset="{Binding ViewerScrollOffset}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TargetUpdated">
<i:InvokeCommandAction Command="{Binding ScrollToHighlightedWordCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<local:SelectableTextBlock Margin="5" local:Attached.FormattedText="{Binding DisplayText}"
FlowDirection="RightToLeft" TextWrapping="Wrap">
</local:SelectableTextBlock>
</ScrollViewer>
</Border>
事件信号,并使用 Microsoft.Xaml.Behaviors.Wpf 设置绑定到 ViewModel 中命令的事件触发器。最后,使用 Prism.Core 我发出命令来计算滚动的偏移量。
注意:如果调整窗口/视图的大小,它将调用TargetUpdated
并且滚动将重置为突出显示的单词,除非 ViewModel 中的函数考虑到了这一点。
或者,事件触发器可以移动到ScrollToHighlightedWordCommand
内的
SelectableTextBlock
,但这并不能保证在调用滚动调整命令时 ScrollViewer
是最新的。事实上,UI Dispatcher 总是会在更新 ViewerScrollLimit
属性之前调用滚动调整命令。它不完美,但它有效..