7

我有一个带有RichEditBox(editor) 和Grid(MarginNotes) 的 Windows Store 应用程序。

我需要始终匹配两个元素的垂直滚动位置。这样做的目的是允许用户在文档的边缘添加注释。

我已经根据光标位置计算出注释定位 - 添加注释时,文本选择由光标之前的所有内容组成。然后将该选择添加到 aRichEditBox内的第二个不可见的StackPanel. 然后我得到ActualHeight这个控件,它给了我在网格中的注释位置。

我的问题是,当我RichEditBox向上和向下滚动时,Grid不会相应地滚动。

第一种技术

我尝试将它们都放在 aScrollViewer中,并禁用滚动RichEditBox

<ScrollViewer x:Name="EditorScroller" 
    VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150" />
            <ColumnDefinition Width="{Binding *" />
            <ColumnDefinition Width="150" />
        </Grid.ColumnDefinitions>
        <Grid x:Name="MarginNotes" Grid.Column="0" HorizontalAlignment="Right"                  
            Height="{Binding ActualHeight, ElementName=editor}">
        </Grid>
        <StackPanel Grid.Column="1">
            <RichEditBox x:Name="margin_helper" Opacity="0" Height="Auto"></RichEditBox>
        </StackPanel>
        <RichEditBox x:Name="editor" Grid.Column="1" Height="Auto"
            ScrollViewer.VerticalScrollBarVisibility="Hidden" />
    </Grid>
</ScrollViewer>

当我滚动到RichEditBox控件的底部并按几次 Enter 时,光标消失了。ScrollViewer不会随光标自动滚动。

我尝试添加 C# 代码来检查光标的位置,将其VerticalOffset与编辑器的高度和高度进行比较,然后相应地调整滚动。这有效,但速度非常慢。最初,KeyUp当我键入一个句子时,我将它用于使应用程序停止的事件。之后我将其设置为 5 秒计时器,但这仍然会降低应用程序的性能,并且还意味着在光标消失和RichEditBox滚动之间可能会有 5 秒的延迟。

第二种技术

我还尝试放入MarginNotes它自己的ScrollViewer,并以编程方式设置VerticalOffset基于 my RichEditBoxsViewChanged事件。

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="150" />
        <ColumnDefinition Width="{Binding *" />
        <ColumnDefinition Width="150" />
    </Grid.ColumnDefinitions>
    <ScrollViewer x:Name="MarginScroller" Grid.Column="0" 
         VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
        <Grid x:Name="MarginNotes" HorizontalAlignment="Right"                  
            Height="{Binding ActualHeight, ElementName=editor}">
        </Grid>
    </ScrollViewer>
    <StackPanel Grid.Column="1">
        <RichEditBox x:Name="margin_helper" Opacity="0" Height="Auto"></RichEditBox>
    </StackPanel>
    <RichEditBox x:Name="editor" Grid.Column="1" Height="Auto" 
        Loaded="editor_loaded" SizeChanged="editor_SizeChanged" />
</Grid>

相关事件处理程序

void editor_Loaded(object sender, RoutedEventArgs e)
{
    // setting this in the OnNavigatedTo causes a crash, has to be set here. 
    // this uses WinRTXAMLToolkit as suggested by Nate Diamond to find the 
    // ScrollViewer and add the event handler
    editor.GetFirstDescendantOfType<ScrollViewer>().ViewChanged += editor_ViewChanged;
}

private void editor_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
    // when the RichEditBox scrolls, scroll the MarginScroller the same amount
    double editor_vertical_offset = ((ScrollViewer)sender).VerticalOffset;
    MarginScroller.ChangeView(0, editor_vertical_offset, 1);       
}

private void editor_SizeChanged(object sender, SizeChangedEventArgs e)
{
    // when the RichEditBox size changes, change the size of MarginNotes to match
    string text = "";
    editor.Document.GetText(TextGetOptions.None, out text);
    margin_helper.Document.SetText(TextSetOptions.None, text);
    MarginNotes.Height = margin_helper.ActualHeight;
}

这有效,但相当滞后,因为ViewChanged在滚动停止后,直到事件触发后才应用滚动。我尝试使用该ViewChanging事件,但由于某种原因它根本没有触发。此外,在Grid快速滚动后有时会出现错误定位。

4

1 回答 1

1

因此,使这变得困难的是文本的大小或文本在不同类型的 TextBoxes 中的位置意味着同步滚动条并不能保证您正在同步文本。话虽如此,这就是你的做法。

void MainPage_Loaded(object sender, RoutedEventArgs args)
{
    MyRichEditBox.Document.SetText(Windows.UI.Text.TextSetOptions.None, MyTextBox.Text);
    var textboxScroll = Children(MyTextBox).First(x => x is ScrollViewer) as ScrollViewer;
    textboxScroll.ViewChanged += (s, e) => Sync(MyTextBox, MyRichEditBox);
}

public void Sync(TextBox textbox, RichEditBox richbox)
{
    var textboxScroll = Children(textbox).First(x => x is ScrollViewer) as ScrollViewer;
    var richboxScroll = Children(richbox).First(x => x is ScrollViewer) as ScrollViewer;
    richboxScroll.ChangeView(null, textboxScroll.VerticalOffset, null);
}

public static IEnumerable<FrameworkElement> Children(FrameworkElement element)
{
    Func<DependencyObject, List<FrameworkElement>> recurseChildren = null;
    recurseChildren = (parent) =>
    {
        var list = new List<FrameworkElement>();
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
        {
            var child = VisualTreeHelper.GetChild(parent, i);
            if (child is FrameworkElement)
                list.Add(child as FrameworkElement);
            list.AddRange(recurseChildren(child));
        }
        return list;
    };
    var children = recurseChildren(element);
    return children;
}

决定何时调用同步是很棘手的。也许在 PointerReleased、PointerExit、LostFocus、KeyUp 上——有很多滚动方式才是真正的问题。你可能需要处理所有这些。但是它就是这样啊。至少你可以。

祝你好运。

于 2014-05-09T22:55:56.517 回答