Соответствие вертикальной позиции прокрутки сетки в RichEditBox или TextBox

У меня есть приложение Магазина Windows с RichEditBox (редактор) и Grid (MarginNotes).

Мне нужно, чтобы вертикальное положение прокрутки двух элементов всегда совпадало. Цель этого состоит в том, чтобы позволить пользователю добавлять заметки на полях документа.

Я уже разобрался с позиционированием заметки на основе позиции курсора - когда добавляется заметка, делается выбор текста из всего, вплоть до курсора. этот выбор затем добавляется во второй, невидимый RichEditBoxвнутри StackPanel, Я тогда получаю ActualHeight этого элемента управления, который дает мне положение заметки в сетке.

Моя проблема в том, что когда я прокручиваю RichEditBox вверх и вниз, Grid не прокручивает соответственно.

Первая Техника

Я пытался положить их обоих в ScrollViewerи отключение прокрутки на 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 управление, и нажмите ввод несколько раз, курсор исчезает из поля зрения. ScrollViewer не прокручивается автоматически при помощи курсора.

Я попытался добавить код C#, который будет проверять положение курсора, сравнить его с VerticalOffset и высота редактора, а затем отрегулируйте прокрутку соответственно. Это работало, но было невероятно медленно. Первоначально у меня было это на KeyUp событие, которое привело к остановке приложения, когда я набрал предложение. После этого я установил таймер на 5 секунд, но это по-прежнему замедляло производительность приложения, а также означало, что между выпадением курсора из поля зрения и задержкой в ​​5 секунд может возникнуть задержка. RichEditBox прокрутки.

Вторая Техника

Я также пытался положить просто MarginNotes в своем собственном ScrollViewerи программно устанавливая VerticalOffset основываясь на моем RichEditBoxs ViewChanged событие.

<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 иногда был неправильно расположен после быстрой прокрутки.

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 - существует множество способов прокрутки, и это реальная проблема. Возможно, вам придется справиться со всеми этими. Но что есть, то есть. По крайней мере, вы можете.

Удачи.

Другие вопросы по тегам