You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Caliburn Micro的WPF MVVM阿拉伯文本搜索定位滚动问询

解决方案:Caliburn Micro WPF MVVM阿拉伯文本搜索滚动与高亮

一、解决ScrollViewer+SelectableTextBlock的滚动绑定问题

ScrollViewer的ScrollableHeight是只读依赖属性,无法直接双向绑定,可通过附加属性实现VM与控件的状态同步,同时绑定VerticalOffset控制滚动:

1. 实现附加属性

public static class ScrollViewerExtensions
{
    // 同步ScrollViewer的ScrollableHeight到VM
    public static readonly DependencyProperty ScrollableHeightProperty =
        DependencyProperty.RegisterAttached("ScrollableHeight", typeof(double), typeof(ScrollViewerExtensions), 
            new PropertyMetadata(default(double), OnScrollableHeightChanged));

    // 从VM控制ScrollViewer的VerticalOffset
    public static readonly DependencyProperty VerticalOffsetProperty =
        DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), typeof(ScrollViewerExtensions), 
            new PropertyMetadata(default(double), OnVerticalOffsetChanged));

    private static void OnScrollableHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is ScrollViewer scrollViewer)
        {
            scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged;
            scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
        }
    }

    private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        if (sender is ScrollViewer scrollViewer)
        {
            SetScrollableHeight(scrollViewer, scrollViewer.ScrollableHeight);
        }
    }

    private static void OnVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is ScrollViewer scrollViewer)
        {
            scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
        }
    }

    public static double GetScrollableHeight(DependencyObject obj) => (double)obj.GetValue(ScrollableHeightProperty);
    public static void SetScrollableHeight(DependencyObject obj, double value) => obj.SetValue(ScrollableHeightProperty, value);

    public static double GetVerticalOffset(DependencyObject obj) => (double)obj.GetValue(VerticalOffsetProperty);
    public static void SetVerticalOffset(DependencyObject obj, double value) => obj.SetValue(VerticalOffsetProperty, value);
}

2. XAML绑定与ViewModel配置

<ScrollViewer local:ScrollViewerExtensions.ScrollableHeight="{Binding ScrollableHeight, Mode=OneWayToSource}"
              local:ScrollViewerExtensions.VerticalOffset="{Binding TargetVerticalOffset, Mode=OneWay}">
    <local:SelectableTextBlock Text="{Binding ContentText}" HighlightedText="{Binding SearchKeyword}" />
</ScrollViewer>

ViewModel中定义对应属性,并在搜索结果点击时计算偏移:

public double ScrollableHeight { get; set; }
public double TargetVerticalOffset { get; set; }

public void OnSearchResultClicked(SearchResult result)
{
    // 根据匹配文本位置计算目标偏移量(需适配阿拉伯文RTL布局的位置计算逻辑)
    TargetVerticalOffset = CalculateVerticalOffset(result.MatchPosition);
    NotifyOfPropertyChange(() => TargetVerticalOffset);
}

二、更优替代方案:FlowDocumentScrollViewer + FlowDocument

原生FlowDocument对阿拉伯文RTL(从右到左)排版支持完善,自带滚动、文本选择功能,可轻松实现高亮与精准定位,完全符合MVVM模式:

1. ViewModel实现

public FlowDocument ContentDocument { get; set; }
public TextRange TargetMatchRange { get; set; }

public void OnSearchResultClicked(SearchResult result)
{
    // 生成带高亮的FlowDocument
    ContentDocument = GenerateHighlightedDocument(result.FullText, result.SearchKeyword);
    // 定位到匹配文本范围
    TargetMatchRange = FindMatchRange(ContentDocument, result.SearchKeyword);
    
    NotifyOfPropertyChange(() => ContentDocument);
    NotifyOfPropertyChange(() => TargetMatchRange);
}

private FlowDocument GenerateHighlightedDocument(string text, string keyword)
{
    var doc = new FlowDocument();
    var para = new Paragraph { FlowDirection = FlowDirection.RightToLeft }; // 开启阿拉伯文RTL布局

    int index = 0;
    while (index < text.Length)
    {
        int keywordIndex = text.IndexOf(keyword, index, StringComparison.OrdinalIgnoreCase);
        if (keywordIndex == -1)
        {
            para.Inlines.Add(new Run(text.Substring(index)));
            break;
        }
        if (keywordIndex > index)
        {
            para.Inlines.Add(new Run(text.Substring(index, keywordIndex - index)));
        }
        // 高亮匹配文本
        var highlightedRun = new Run(text.Substring(keywordIndex, keyword.Length)) { Background = Brushes.Yellow };
        para.Inlines.Add(highlightedRun);
        index = keywordIndex + keyword.Length;
    }
    doc.Blocks.Add(para);
    return doc;
}

private TextRange FindMatchRange(FlowDocument doc, string keyword)
{
    var range = new TextRange(doc.ContentStart, doc.ContentEnd);
    var position = range.Start;
    while (position != null)
    {
        if (position.GetTextInRun(LogicalDirection.Forward).Contains(keyword))
        {
            var start = position;
            var end = position.GetPositionAtOffset(keyword.Length);
            return new TextRange(start, end);
        }
        position = position.GetNextContextPosition(LogicalDirection.Forward);
    }
    return null;
}

2. 附加属性实现滚动定位

public static class FlowDocumentScrollViewerExtensions
{
    public static readonly DependencyProperty TargetMatchRangeProperty =
        DependencyProperty.RegisterAttached("TargetMatchRange", typeof(TextRange), typeof(FlowDocumentScrollViewerExtensions),
            new PropertyMetadata(null, OnTargetMatchRangeChanged));

    private static void OnTargetMatchRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue is TextRange range)
        {
            range.BringIntoView();
        }
    }

    public static TextRange GetTargetMatchRange(DependencyObject obj) => (TextRange)obj.GetValue(TargetMatchRangeProperty);
    public static void SetTargetMatchRange(DependencyObject obj, TextRange value) => obj.SetValue(TargetMatchRangeProperty, value);
}

3. XAML使用

<FlowDocumentScrollViewer local:FlowDocumentScrollViewerExtensions.TargetMatchRange="{Binding TargetMatchRange}"
                          Document="{Binding ContentDocument}" />

该方案优势:

  • 原生支持阿拉伯文RTL排版,无需额外解析处理
  • 自带文本选择、复制等功能,无需自定义控件
  • 通过TextRange.BringIntoView实现精准滚动,无需手动计算偏移量

内容的提问来源于stack exchange,提问作者Everyone

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 17:20:57