Aligning the vertical scroll position of a grid with a RichEditBox or TextBox

I have a Windows Store app with RichEditBox (editor) and Grid (MarginNotes).

I need a vertical scroll position of two elements that must be consistent at all times. The purpose of this is to allow the user to add notes to the document field.

I already realized that the positioning of notes is based on the cursor position - when a note is added, the text is selected from everything up to the cursor. this selection is then added to the second, invisible RichEditBox , inside a StackPanel . Then I get the ActualHeight this control, which gives me the position of the note in the grid.

My problem is that when I scroll the RichEditBox up and down, the Grid does not scroll accordingly.

First technique

I tried putting them inside a ScrollViewer and disabling scrolling on a 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> 

When I scroll the bottom of the RichEditBox control and press the enter button several times, the cursor drops out of view. ScrollViewer does not scroll automatically with the cursor.

I tried adding C # code that would check the position of the cursor, compare it with VerticalOffset and the height of the editor, and then adjust the scroll accordingly. It worked, but it was incredibly slow. I initially had this at a KeyUp event, which caused the application to stop when I typed in a sentence. Subsequently, I set it to a 5-second timer, but it still slowed down the performance of the application, and also meant that there could be a 5-second delay between moving the cursor out of sight and scrolling RichEditBox .

Second technique

I also tried putting only MarginNotes in my own ScrollViewer and programmatically setting VerticalOffset based on my RichEditBox 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> 

relevant event handlers

 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; } 

This worked, but was rather backward, as scrolling is not applied until the ViewChanged event ViewChanged , after scrolling has stopped. I tried using the ViewChanging event, but for some reason it does not fire at all. In addition, the Grid sometimes incorrectly positioned after a quick scroll.

+7
windows-store-apps winrt- xaml textbox scrollviewer
source share
1 answer

So, what makes it difficult is that text size or text placement in different types of text fields means that scrollbar synchronization does not guarantee text synchronization. Having said that, here is how you do it.

 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; } 

Deciding when to call synchronization is difficult. Perhaps on PointerReleased, PointerExit, LostFocus, KeyUp - there are many ways to scroll, this is a real problem. You may need to handle all of these. But that is, that is. At least you can.

Good luck.

+1
source share

All Articles