Matching the vertical scroll position of a Grid to a RichEditBox or TextBox - textbox

I've got a Windows Store app with a RichEditBox (editor) and a Grid (MarginNotes).
I need the vertical scroll position of the two elements to be matched at all times. The purpose of this is to allow the user to add notes in the margin of the document.
I've already figured out Note positioning based on the cursor position - when a note is added, a text selection is made of everything up to the cursor. that selection is then added to a second, invisible RichEditBox, inside a StackPanel. I then get the ActualHeight of this control which gives me the position of the note in the grid.
My issue is that when I scroll the RichEditBox up and down, the Grid does not scroll accordingly.
First Technique
I tried putting them both inside a ScrollViewer, and disabling scrolling on the 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 to the bottom of the RichEditBox control, and hit enter a few times, the cursor drops out of sight. The ScrollViewer doesn't scroll automatically with the cursor.
I tried adding C# code which would check the position of the cursor, compare it to the VerticalOffset and height of the editor, and then adjust the scroll accordingly. This worked, but was incredibly slow. Initially I had it on the KeyUp event which brought the app to a standstill when I typed a sentence. Afterwards I put it on a 5 second timer, but this still slowed down the app performance and also meant that there could be a 5 second delay between the cursor dropping out of sight and the RichEditBox scrolling.
Second Technique
I also tried putting just MarginNotes in its own ScrollViewer, and programmatically setting the VerticalOffset based off my RichEditBoxs ViewChanged event.
<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 quite laggy as scrolling is not applied until the ViewChanged event fires, after scrolling has stopped. I tried using the ViewChanging event, but it does not fire at all for some reason. Additionally, the Grid was sometimes mis-positioned after a fast scroll.

So, what makes this difficult is that the size of the text or the placement of the text in different types of TextBoxes means that syncing the scrollbar doesn't guarantee you are syncing the text. Having said that, here's 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 invoke the sync is tricky. Maybe on PointerReleased, PointerExit, LostFocus, KeyUp - there are a lot of ways to scroll is the real issue there. You might need to handle all of those. But, it is what it is. At least you can.
Best of luck.

Related

CollectionView with Horizontal Layout ScrollTo Method not working Xamarin

I am using a CollectionView to show a list of elements in Xamarin.Forms. I have also set CollectionView.ItemsLayout property to GridItemsLayout. But when I use ScrollTo method to scroll the element it does not scroll to the said element. How can I scroll to a specified index or specified element in CollectionView?
Code:
<CollectionView x:Name="fyCarousel" HeightRequest="30" Margin="0,10,10,0"
HorizontalScrollBarVisibility="Never" VerticalScrollBarVisibility="Never">
<CollectionView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Horizontal" HorizontalItemSpacing="10">
</GridItemsLayout>
</CollectionView.ItemsLayout>
</CollectionView>
For Scrolling I am using:
int pos = chipsSections.IndexOf(x => x.Name == Name);
fyCarousel.ScrollTo(pos);

Nativescript IoS Listview Dynamic Height Elements

I'm building a nativescript angular app with a tabstrip and one of the pages has a search bar and results shown in a ListView. The results returned can vary in length, so thus the height of each item can be different. Everything displays fine when the search results come back, however if I go to another 'page' via the tab strip and return, iOS redraws all the listview items as the same height, causing some results to be cut off, and others to have a ton of white space.
If I start scrolling up and down in the listview and different items become visible, they start fixing themselves, thus causing the results to jerk down/up as heights change. This is not an issue on Android. What am I doing wrong?
<GridLayout rows="auto *" class="p-20">
<GridLayout row="0" rows="*">
<StackLayout row="0" class="input-field">
<SearchBar #searchField hint="Search ..." class="input" (submit)="startSearch($event)" (clear)="startSearch($event)"></SearchBar>
</StackLayout>
</GridLayout>
<GridLayout row="1">
<ActivityIndicator [busy]="isSearchingKJ"></ActivityIndicator>
<ListView *ngIf="kjResults?.length > 0" [items]="kjResults" (itemTap)="select($event)">
<ng-template let-item="item" let-i="index">
<StackLayout>
<Label class="search" [text]="item.book + ' ' + item.chapter + ':' + item.verse"></Label>
<Label class="search-results" textWrap="true" (loaded)="displaySearchResult($event, item)"></Label>
</StackLayout>
</ng-template>
</ListView>
</GridLayout>
</GridLayout>
Here is the code generating the formatted text for each item:
displaySearchResult(args, result) {
const container = <Label>args.object;
const formattedStringLabel = new FormattedString();
for (const fragment of result.text) {
const span = new Span();
span.text = fragment.text;
if (fragment.highlight) {
span.fontWeight = '800';
}
formattedStringLabel.spans.push(span);
}
container.formattedText = formattedStringLabel;
}
Example of iOS graphical glitches:

Updating listview text binding causes flickering on iOS using Xamarin.Forms

I am working on a Xamarin.Forms chat app which sends/receives text character by character, this means the text binding on a list view row is updated every 1-2 seconds or half second (depending on typing speed), on Android the listview item updates smoothly but on iOS it flickers/ and pulsates like a strobe light with every character entered.
Edit
Replication steps:
Bound a Listview's ItemSource to an ObservableCollection which updates for every new character typed in an Entry. Use DataTemplate with a label in it which displays the text typed in Entry field.
In Page.xaml
<ListView HasUnevenRows="True" ItemsSource="{Binding ObservableCollection}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Grid>
<Label Text="{Binding Text}"/>
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Entry Text="{Binding UserText}"/>
In PageViewModel.cs
/// <summary>
/// Gets or sets the text entered by the user
/// </summary>
public string UserText
{
get
{
return this.userText;
}
set
{
this.SetProperty(ref this.userText, value);
// Display text by updating listview
Device.BeginInvokeOnMainThread(() =>
{
this.ObservableCollection.Add(userText);
});
}
}
I created a ListViewRenderer on iOS and set AnimationsEnabled = false. The flicking is now gone!

Dynamic/instant resize in JavaFX

How do you create a JavaFX application that (instantly) dynamically resizes? Right now I have coded a simple application that dynamically resizes but the layout changes don't display until after the release of the mouse button on a drag. I want instantly see the results/layout changes before this button release.
I'm assuming this is done by just binding the correct values/controls with inverse... Any help would be great!
EDIT:
Here's how I got things working (thanks to Praeus). Exactly as he said, I had to bind my top level container/layout width and height to scene.width and scene.height. --
var scene: Scene;
Stage {
title: "Application title"
scene: scene = Scene {
content: [
XMigLayout {
width: bind scene.width;
height: bind scene.height;
...}]}}
Bind is different in JavaFX's 2.0 release. Angela Caicedo's video demonstrates how to bind properties nicely. A variable must be setup as an ObservableNumberValue and its get and set methods used. After it's setup you can then bind it to a component's property.
DoubleProperty x = new SimpleDoubleProperty(0);
x.set(1);
x.getValue();
imageView.xProperty().bind(x);
anchorPane.heightProperty().add(x);
Eric Bruno's post is another way of doing it.
scene.widthProperty().addListener(
new ChangeListener() {
public void changed(ObservableValue observable,
Object oldValue, Object newValue) {
Double width = (Double)newValue;
tbl.setPrefWidth(width);
}
});
scene.heightProperty().addListener(
new ChangeListener() {
public void changed(ObservableValue observable,
Object oldValue, Object newValue) {
Double height = (Double)newValue;
tbl.setPrefHeight(height);
}
});
Edits: Added another, more fitting answer along with where I found it.
Actually if you've already organized everything into layouts, then you might be able to just bind the top level layout container(s) width and height to the scene.width and scene.height. I don't think top level layout controls are managed by anything, so you may be safe just setting width and height of the layout control directly. If that doesn't work then you might have success binding the layoutInfo width and height.
Ideally you want to use layout management before resorting to binds. Excessive binds can be a performance problem, and can lead to slow redraws as you are resizing. In this case you can probably just use binds for the top level container, and then all of the children of the top level container should resize accordingly. At least I think that should work.
Yes I think you're on the right lines - binding would be the obvious way to do what you trying to do. You could also look at triggers.
Read the 1.3 guide to layouts as a starting point if you haven't already
The layout containers automatically manage the dynamic layout behavior of their content nodes. However, it is often desirable to also use the binding mechanism of the JavaFX Script to establish certain dynamic aspects of layout. For example, you can bind the width and height of an HBox container to the Scene height and width so that whenever Scene resizes, the HBox container resizes as well.
It's also probably worth reading articles and blog posts by JavaFX layout guru Amy Fowler
Here's what I ended up doing to get my resizing how I liked. There has to be an easier way but I surely don't know it.
My main method loads my controller and scene then calls my controllers init() method
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root = (Parent) loader.load();
primaryStage.setTitle("Robot Interface");
primaryStage.setScene(new Scene(root));
primaryStage.show();
Controller controller = (Controller)loader.getController();
controller.init();
I am using a gridpane that dynamically resizes as the whole stage is resized by the user so it always fits. This is becaus its percentage sizing. This is done in the fxml way easier than with binding IMO.
<GridPane fx:id="gridPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="500.0" prefWidth="700.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<columnConstraints>
<ColumnConstraints percentWidth="50" />
<ColumnConstraints percentWidth="50" />
</columnConstraints>
<rowConstraints>
<RowConstraints percentHeight="35" />
<RowConstraints percentHeight="35" />
<RowConstraints percentHeight="35" />
</rowConstraints>
<children>
<TextArea fx:id="textArea_Console" prefHeight="200.0" prefWidth="200.0" promptText="console/reponse" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<!-- <MediaView fx:id="mediaView_Images" fitHeight="225.0" fitWidth="225.0" GridPane.halignment="LEFT" GridPane.valignment="BOTTOM" /> -->
<TextArea fx:id="textArea_RoboAvail" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="121.0" prefWidth="123.0" promptText="robots available" GridPane.columnIndex="1" GridPane.halignment="RIGHT" GridPane.valignment="TOP" />
<TextArea fx:id="textArea_SensorData" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="249.0" prefWidth="106.0" promptText="sensor data" GridPane.halignment="RIGHT" GridPane.valignment="TOP" />
<TextArea fx:id="textArea_Scripting" prefHeight="200.0" prefWidth="200.0" promptText="Scripting" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label text="Currently Viewing" GridPane.halignment="LEFT" GridPane.valignment="TOP" />
<TextArea maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="250.0" prefWidth="100.0" promptText="to do list" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.valignment="TOP" />
<ImageView fx:id="imageView_Images" fitHeight="233.0" fitWidth="225.0" pickOnBounds="true" preserveRatio="true" GridPane.halignment="LEFT" GridPane.valignment="TOP" />
<Label fx:id="label_CheatSheet" text="Label" GridPane.halignment="LEFT" GridPane.rowIndex="2" GridPane.valignment="BOTTOM" />
</children>
Remember how my controller class had its init() method called. Well init() calls setupUI where I get my height and width and create callbacks for height and width changes later. Those change my class wide variables mHeight and mWidth;
private void setupUI()
{
imageView_Images.setImage( new Image("file:///C:\\Users\\administration\\IdeaProjects\\Robot User Interface\\Media\\filler.jpeg"));
mHeight = gridPane.getHeight();
mWidth = gridPane.getWidth();
//for initial startup sizing
dynamicRsize();
//callbacks to detect changes for sizing
gridPane.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
mWidth = newValue.doubleValue();
dynamicRsize();
}
});
gridPane.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
mHeight = newValue.doubleValue();
dynamicRsize();
}
});
setup_TextArea_Scriptng();
setup_Label_CheatSheet();
}
Finally I can call dynamicResize() which adjust every object in the gridpane as a percentage of that rows width or height
private void dynamicRsize()
{
//two columns 50/50
//three rows 35/35/30
double columnType1 = mWidth/2;
double rowType1 = mHeight * .35;
double rowType2 = mHeight * .3;
//node 1
imageView_Images.setFitWidth(columnType1 * .75);
textArea_SensorData.setPrefWidth(columnType1 * .25);
//node 2
//node 3
//node 4
//node 5
//node 6
}

Default Layout Orientation when printing XPSs using the WPF XPS Viewer

Is there a way to set the Default Layout Orientation when printing XPSs using the WPF XPS Viewer?
My fixed document XPS has its page orientation set to Landscape, the Page Media Size has a width that is longer that its height and it displays correctly in the Viewer as Landscape.
Its just that when you hit the print button the Print Dialog preferences are defaulted to Portrait and it prints as such.
I'd rather not have to alter the users default print settings I'd much prefer it if the XPS Viewer would print the XPS as it was designed to be printed.
Fill the field of the PrintTicket:
PrintDialog pd = new PrintDialog();
PrintTicket pt = new PrintTicket();
pt.PageOrientation = PageOrientation.Landscape;
pd.PrintTicket = pd.PrintQueue.MergeAndValidatePrintTicket(pd.PrintQueue.DefaultPrintTicket, pt).ValidatedPrintTicket;
if (pd.ShowDialog() == true)
{
...
}
I believe the correct way to do this when creating a FixedDocument, is set the RenderTransform = RotateTransform(90) on the page content when the dimensions are higher than they are wide.
Example:
var visualContent = new Image
{
Source = image,
Stretch = Stretch.Uniform
};
visualContent.RenderTransformOrigin = new Point(0.5, 0.5);
visualContent.RenderTransform = new RotateTransform(90);
FixedPage fixedPage = new FixedPage();
fixedPage.Children.Add(visualContent);
var pageContent = new PageContent
{
Child = fixedPage
};
Not sure if that helps with a pre-existing XPS document however.
This is not really a problem with MXDW but one with the way drivers work on Windows. The user choice(s) is/are saved for a particular session. This means that you can reuse firs-print settings when printing between the first print and quitting the application. Most printers behave this way, until unless one comes up with a way to save this information somewhere and let the user re-use it across sessions.
So, I tried hacking the GPD file (where printing information for a printer is typically stored). The orientation has two possible values: PORTRAIT and LANDSCAPE_CC270 with the default being set to PORTRAIT. See below:
*%******************************************************************************
*% Orientation
*%******************************************************************************
*Feature: Orientation
{
*rcNameID: =ORIENTATION_DISPLAY
*DefaultOption: PORTRAIT
*Option: PORTRAIT
{
*rcNameID: =PORTRAIT_DISPLAY
}
*Option: LANDSCAPE_CC270
{
*rcNameID: =LANDSCAPE_DISPLAY
}
}
Now, if I were to change swap the default value to LANDSCAPE_CC270, the printing preferences stop coming up (and any print would fail). In fact, it appears, that specifying any other value keeps the default to PORTRAIT. Definitely, MS is
doing some sort of check to prevent us from hacking this driver. Looks like MS doesn't
want anyone to tamper with its settings :(
But you could try flirting with the GPD values a bit more and see if something of your liking comes up. Will keep hacking a bit more.
Caveat: GPD files shouldn't be tampered with if you don't know what you are doing. If you
still want to go ahead make a backup!
Hint: They are stored in %WINDOWS%system32\spool\drivers\w32x86\3 folder.
<Grid Margin="0,0,-8,-8">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<FlowDocumentScrollViewer Name="printpanel" HorizontalAlignment="Left" Width="959" FontFamily="Arial" Margin="0,-10,0,10">
<FlowDocument x:Name="FD">
<BlockUIContainer>
<Canvas>
<Label x:Name="lblReceipt" Visibility="Visible" Content="Receipt No." HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Top="178" FontSize="12" Canvas.Left="60"/>
<Label x:Name="txtReceiptNo" BorderThickness="2" BorderBrush="Black" HorizontalAlignment="Left" Padding="10,3,3,0" Height="23" VerticalAlignment="Top" Width="200" FontSize="12" Canvas.Left="187" Canvas.Top="177" FontFamily="Arial"/>
<Label x:Name="lblmemNo" Visibility="Visible" Content="Membership No." HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="12" Canvas.Left="462" Canvas.Top="177"/>
<Label x:Name="txtMembershipNo" BorderThickness="2" BorderBrush="Black" HorizontalAlignment="Left" Padding="10,3,3,0" Height="23" VerticalAlignment="Top" Width="177" FontSize="12" Canvas.Left="604" Canvas.Top="177" FontFamily="Arial">
</Label>
<Label x:Name="lblAuthCentr" Visibility="Visible" Content="Authorised Center." HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="60" Canvas.Top="221" FontSize="12"/>
<TextBox x:Name="txtAuthCentr" HorizontalAlignment="Left" TextWrapping="WrapWithOverflow" Padding="10,3,3,0" Height="38" VerticalAlignment="Top" Width="219" FontSize="12" Canvas.Left="238" Canvas.Top="219" FontFamily="Arial"/>
<Label x:Name="lblSector" Visibility="Visible" Content="Sector." HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="12" Canvas.Left="492" Canvas.Top="220"/>
<Label x:Name="txtSector" BorderThickness="2" BorderBrush="Black" HorizontalAlignment="Left" Padding="10,3,3,0" Height="23" VerticalAlignment="Top" Width="115" FontSize="12" Canvas.Left="567" Canvas.Top="220" FontFamily="Arial"/>
</Canvas>
</BlockUIContainer>
</FlowDocument>
</FlowDocumentScrollViewer>
<Button Name="btnOk" Content="Print" Height="30" Grid.Row="1" Click="btnOk_Click" Margin="355,0,404,0"></Button>
</Grid>
Just set FlowDocument Height and width
set FD.PageWidth = 1100; FD.PageHeight = 600;
private void btnOk_Click(object sender, RoutedEventArgs e)
{
if (File.Exists("printPreview.xps"))
{
File.Delete("printPreview.xps");
}
var xpsDocument = new XpsDocument("printPreview.xps", FileAccess.ReadWrite);
XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument);
DocumentPaginator docPage;
FD.PageWidth = 1100; // set FlowDocument Width
FD.PageHeight = 600; // set FlowDocument Height
docPage = ((IDocumentPaginatorSource)FD).DocumentPaginator;
writer.Write(docPage);
Document = xpsDocument.GetFixedDocumentSequence();
this.Close();
xpsDocument.Close();
var windows = new PrintWindow(Document);
windows.ShowDialog();
}

Resources