UWP Scroll Text from end to start - c#

I am implementing a scrolling text that when pointer enters it, it starts scrolling its content.
I am able to get it scrolling using the code below:
private DispatcherTimer ScrollingTextTimer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(16) };
ScrollingTextTimer.Tick += (sender, e) =>
{
MainTitleScrollViewer.ChangeView(MainTitleScrollViewer.HorizontalOffset + 3, null, null);
if (MainTitleScrollViewer.HorizontalOffset == MainTitleScrollViewer.ScrollableWidth)
{
MainTitleScrollViewer.ChangeView(0, null, null);
ScrollingTextTimer.Stop();
}
};
XAML:
<ScrollViewer
x:Name="MainTitleScrollViewer"
Grid.Row="0"
Grid.Column="1"
Margin="10,5"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Disabled">
<TextBlock
x:Name="MainTitleTextBlock"
VerticalAlignment="Bottom"
FontSize="24"
Foreground="White" />
</ScrollViewer>
However, there is an additional feature that I want to implement. When the text scrolls to its end, I don't want it to scroll back to the start. I want it to keep scrolling to the start. You can see what I mean from the screenshots I posted below. The screenshots are from Groove Music. You may need to check it out if I didn't explain my question well.
A possible solution might be doubling the text and putting some space between them. But I don't know when to stop scrolling if so.

The effect of this kind of marquee is recommended to use Storyboard. The timer may cause lack due to time interval.
Here is a complete demo, I hope to help you.
xaml
<Grid>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center" BorderBrush="Gray" BorderThickness="1" Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Source="ms-appx:///Assets/StoreLogo.png" Width="100" Height="100" VerticalAlignment="Center"/>
<StackPanel Grid.Column="1" Margin="20,0,0,0" VerticalAlignment="Center">
<ScrollViewer Width="200"
PointerEntered="ScrollViewer_PointerEntered"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
PointerExited="ScrollViewer_PointerExited">
<TextBlock FontSize="25" x:Name="TitleBlock">
<TextBlock.RenderTransform>
<TranslateTransform X="0"/>
</TextBlock.RenderTransform>
</TextBlock>
</ScrollViewer>
<TextBlock FontSize="20" FontWeight="Bold" Text="Gotye" Margin="0,10,0,0"/>
</StackPanel>
</Grid>
</Grid>
xaml.cs
Storyboard _scrollAnimation;
public ScrollTextPage()
{
this.InitializeComponent();
string text = "How about you?";
TitleBlock.Text = text + " " + text;
}
private void ScrollViewer_PointerEntered(object sender, PointerRoutedEventArgs e)
{
AnimationInit();
_scrollAnimation.Begin();
}
private void ScrollViewer_PointerExited(object sender, PointerRoutedEventArgs e)
{
_scrollAnimation.Stop();
}
public void AnimationInit()
{
_scrollAnimation = new Storyboard();
var animation = new DoubleAnimation();
animation.Duration = TimeSpan.FromSeconds(5);
animation.RepeatBehavior = new RepeatBehavior(1);
animation.From = 0;
// Here you need to calculate based on the number of spaces and the current FontSize
animation.To = -((TitleBlock.ActualWidth/2)+13);
Storyboard.SetTarget(animation, TitleBlock);
Storyboard.SetTargetProperty(animation, "(UIElement.RenderTransform).(TranslateTransform.X)");
_scrollAnimation.Children.Add(animation);
}
Simply put, scrolling the TextBlock horizontally is more controllable than scrolling the ScrollViewer.
The idea is similar to yours, using a string stitching method to achieve seamless scrolling, and calculate the width of the space by the current font size, so as to accurately scroll to the beginning of the second string.
Best regards.

This is my way of doing it and source code is here(xaml) and here(csharp):
I created a UserControl called ScrollingTextBlock.
This is XAML content of the UserControl.
<Grid>
<ScrollViewer x:Name="TextScrollViewer">
<TextBlock x:Name="NormalTextBlock" />
</ScrollViewer>
<ScrollViewer x:Name="RealScrollViewer">
<TextBlock x:Name="ScrollTextBlock" Visibility="Collapsed" />
</ScrollViewer>
</Grid>
Basically, you need two ScrollViewers that overlaps.
The first ScrollViewer is for detecting if the text is scrollable. And the TextBlock in it is for putting the text.
The second ScrollViewer is the real ScrollViewer. You will be scrolling this one not the first one. And the TextBlock in it will have its Text equal to
ScrollTextBlock.Text = NormalTextBlock.Text + new string(' ', 10) + NormalTextBlock.Text
The new string(' ', 10) is just some blank space to make your text not look concatenated tightly, which you can see from the image in the question. You can change it into whatever you want.
Then in the csharp code you need (explanations are in the comments):
// Using 16ms because 60Hz is already good for human eyes.
private readonly DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(16) };
public ScrollingTextBlock()
{
this.InitializeComponent();
timer.Tick += (sender, e) =>
{
// Calculate the total offset to scroll. It is fixed after your text is set.
// Since we need to scroll to the "start" of the text,
// the offset is equal the length of your text plus the length of the space,
// which is the difference of the ActualWidth of the two TextBlocks.
double offset = ScrollTextBlock.ActualWidth - NormalTextBlock.ActualWidth;
// Scroll it horizontally.
// Notice the Math.Min here. You cannot scroll more than offset.
// " + 2" is just the distance it advances,
// meaning that it also controls the speed of the animation.
RealScrollViewer.ChangeView(Math.Min(RealScrollViewer.HorizontalOffset + 2, offset), null, null);
// If scroll to the offset
if (RealScrollViewer.HorizontalOffset == offset)
{
// Re-display the NormalTextBlock first so that the text won't blink because they overlap.
NormalTextBlock.Visibility = Visibility.Visible;
// Hide the ScrollTextBlock.
// Hiding it will also set the HorizontalOffset of RealScrollViewer to 0,
// so that RealScrollViewer will be scrolling from the beginning of ScrollTextBlock next time.
ScrollTextBlock.Visibility = Visibility.Collapsed;
// Stop the animation/ticking.
timer.Stop();
}
};
}
public void StartScrolling()
{
// Checking timer.IsEnabled is to avoid restarting the animation when the text is already scrolling.
// IsEnabled is true if timer has started, false if timer is stopped.
// Checking TextScrollViewer.ScrollableWidth is for making sure the text is scrollable.
if (timer.IsEnabled || TextScrollViewer.ScrollableWidth == 0) return;
// Display this first so that user won't feel NormalTextBlock will be hidden.
ScrollTextBlock.Visibility = Visibility.Visible;
// Hide the NormalTextBlock so that it won't overlap with ScrollTextBlock when scrolling.
NormalTextBlock.Visibility = Visibility.Collapsed;
// Start the animation/ticking.
timer.Start();
}

Related

I want to have a border highlight animation on my app

I want after the button i clicked that there comes a animation and lands on the picture that is the number of the _random but i cant find anything on the google
ive tried to google and ask around but still havent got what i want
public sealed partial class FilePage : Page
{
DataPage value = new DataPage();
Random _random = new Random();
private int time = 0;
DispatcherTimer Timer;
public FilePage()
{
this.InitializeComponent();
}
private void ButtonClick1(object sender, RoutedEventArgs e)
{
//////////
//////////cut some off to keep this short (turning the border black again)
//////////
Timer = new DispatcherTimer();
Timer.Interval = new TimeSpan(0, 0, 3);
Timer.Tick += Timer_Tick;
Timer.Start();
}
public void Timer_Tick(object sender, object e)
{
int value = _random.Next(1, 13);
((Grid)gMainGrid.FindName($"g{value}")).BorderBrush = new SolidColorBrush(Colors.White);
Timer.Stop();
SpinnerSecond.Visibility = Visibility.Collapsed;
DataWatch.valueWatch = value;
RollKnop.Visibility = Visibility.Visible;
}
}
}
so i want to have a animation that goes over the borders like this:
https://learn.microsoft.com/en-us/windows/apps/images/fluent/traveling-focus-fullscreen-light-rf.gif for like the 3 seconds and then land on the right one, im sorry if i sound like a demander but im already googling for 2 days
Pretty sure that you have to use ControlPaint.DrawBorder if the pictures are in pictureboxes, you draw for one picture, after a short delay you remove it, then draw it on the other, etc
After 3 secondes, you keep that going until it lands on the good picture then you stop everything
EDIT : You might want to check that if that hasn't been already done : How Do I Create a Colored Border on a PictureBox Control?
To short, you can create a loop that traverses the elements inside the container and sets the BorderBrush and BorderThickness for them.
This is a simple code example:
ColorBorderPage.xaml
<StackPanel>
<controls:WrapPanel x:Name="ItemsContainer" HorizontalAlignment="Center" Width="160" Height="120" Margin="0,50">
<StackPanel Background="Gray" Width="100" Height="50" Margin="0,0,10,10"/>
<StackPanel Background="Gray" Width="50" Height="50" Margin="0,0,0,10"/>
<StackPanel Background="Gray" Width="160" Height="50"/>
</controls:WrapPanel>
<Button Content="Click Me" Click="Button_Click" HorizontalAlignment="Center"/>
</StackPanel>
I created a Panel called ItemsContainer, which replaces the image with a StackPanel and add a button to trigger the animation.
ColorBorderPage.xaml.cs
private async void Button_Click(object sender, RoutedEventArgs e)
{
int count = ItemsContainer.Children.Count;
int index = 0;
while (true)
{
var item = ItemsContainer.Children[index] as StackPanel;
item.BorderBrush = new SolidColorBrush(Colors.LightGreen);
item.BorderThickness = new Thickness(3);
await Task.Delay(3000);
item.BorderThickness = new Thickness(0);
index = index == count - 1 ? 0 : index + 1;
}
}
In the code, by first getting the number of child elements in the container, then setting the Border one by one through the while loop, and restarting when looping to the end.
According to your animation requirements, you can use Task.Delay(), which will delay the specified time before proceeding to the next step.
This is just an example, but it should be able to meet your needs, you just need to replace the StackPanel with an Image.
Best regards.

UWP XAML Popup not respecting VerticalAlignment?

I'm trying to center a Popup in a Windows Store/UWP app.
In brief, I'm taking MainPage and adding...
A TextBlock with some text
A Button with an event handler, Button_Click
A Popup named popupTest. It contains...
A Border with...
A StackPanel with
A TextBlock
A Button. This Button's event handle sets the Popup's IsOpen to false.
Button_Click calls _centerPopup, which tries to center the Popup and then sets IsOpen to true. I can't get this to work.
private void _centerPopup(Popup popup, Border popupBorder, FrameworkElement extraElement = null)
{
double ratio = .9; // How much of the window the popup fills, give or take. (90%)
Panel pnl = (Panel)popup.Parent;
double parentHeight = pnl.ActualHeight;
double parentWidth = pnl.ActualWidth;
// Min 200 for each dimension.
double width = parentWidth * ratio > 200 ? parentWidth * ratio : 200;
double height = parentHeight * ratio > 200 ? parentHeight * ratio : 200;
popup.Width = width;
popup.Height = height;
//popup.HorizontalAlignment = HorizontalAlignment.Center;
popup.VerticalAlignment = VerticalAlignment.Top; // <<< This is ignored?!
// Resize the border too. Not sure how to get this "for free".
popupBorder.Width = width;
popupBorder.Height = height;
// Not using this here, but if there's anything else that needs resizing, do it.
if (null != extraElement)
{
extraElement.Width = width;
extraElement.Height = height;
}
}
If I don't try to resize and center the Popup in Button_Click, here's what I get after clicking "Click Me"...
private void Button_Click(object sender, RoutedEventArgs e)
{
//_centerPopup(this.popupTest, this.popupTestBorder);
this.popupTest.IsOpen = true;
}
If I uncomment out the call to _centerPopup, I get this, with the popup staying under the button:
private void Button_Click(object sender, RoutedEventArgs e)
{
_centerPopup(this.popupTest, this.popupTestBorder);
this.popupTest.IsOpen = true;
}
That's no good. I thought popup.VerticalAlignment = VerticalAlignment.Top; would've fixed that.
FrameworkElement.VerticalAlignment Property
Gets or sets the vertical alignment characteristics applied to this element when it is composed within a parent element such as a panel or items control.
Move Popup to top of StackPanel?
Strangely, if I move the Popup up to the top of my StackPanel, it actually pushes the other controls down after being shown.
Clicking "Click Me" without _centerPopup:
That looks promising! It's floating over the other controls nicely, and there's no obvious impact to the layout after it's closed.
But add back _centerPopup, even after commenting out setting VerticalAlignment to Top, and things die a horrible, fiery death.
It looks perfect until you notice that every other control was pushed down. ??? Here's after clicking "Click to close":
Other controls are pushed down permanently. Why does that happen? Shouldn't the popup float like it did before I resized it?
Full Source
XAML
<Page
x:Class="PopupPlay.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:PopupPlay"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel Name="StackMain">
<TextBlock>
This is some text<LineBreak />
This is some text<LineBreak />
This is some text<LineBreak />
This is some text<LineBreak />
</TextBlock>
<Button Click="Button_Click" Content="Click Me"></Button>
<Popup x:Name="popupTest">
<Border
Name="popupTestBorder"
Background="{StaticResource ApplicationPageBackgroundThemeBrush}"
BorderBrush="{StaticResource ApplicationForegroundThemeBrush}"
BorderThickness="2">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Name="txtPopup"
Text="This is some text"
FontSize="24"
HorizontalAlignment="Center" />
<Button Name="btnClose"
Click="btnClose_Click"
Content="Click to close"></Button>
</StackPanel>
</Border>
</Popup>
</StackPanel>
</Page>
Full MainPage.xaml.cs code
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
namespace PopupPlay
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_centerPopup(this.popupTest, this.popupTestBorder);
this.popupTest.IsOpen = true;
}
private void _centerPopup(Popup popup, Border popupBorder, FrameworkElement extraElement = null)
{
double ratio = .9; // How much of the window the popup fills, give or take. (90%)
Panel pnl = (Panel)popup.Parent;
double parentHeight = pnl.ActualHeight;
double parentWidth = pnl.ActualWidth;
// Min 200 for each dimension.
double width = parentWidth * ratio > 200 ? parentWidth * ratio : 200;
double height = parentHeight * ratio > 200 ? parentHeight * ratio : 200;
popup.Width = width;
popup.Height = height;
//popup.HorizontalAlignment = HorizontalAlignment.Center;
popup.VerticalAlignment = VerticalAlignment.Top; // <<< This is ignored?!
// Resize the border too. Not sure how to get this "for free".
popupBorder.Width = width;
popupBorder.Height = height;
// Not using this here, but if there's anything else that needs resizing, do it.
if (null != extraElement)
{
extraElement.Width = width;
extraElement.Height = height;
}
}
private void btnClose_Click(object sender, RoutedEventArgs e)
{
this.popupTest.IsOpen = false;
}
}
}
There are several questions that seem related. I do not see a viable fix. (Note: These are not all UWP specific.)
Center Popup in XAML
Place Popup at top right corner of a window in XAML
How to set vertical offset for popup having variable height
Painfully, this same setup is working for me in another app when it's positioned in a much more complicated grid with a Pivot, but I see that pivots are buggy.
Wpf's Placement stuff sounds promising, but doesn't exist in UWP-land.
Your Popup is inside a vertical StackPanel, which means the StackPanel will lay out the popup alongside the other child elements of the panel, which is why it pushes down the text.
Also, the VerticalAlignment is being ignored by the panel because the panel allocated exactly enough vertical space for the popup's size, and so there is no room for it to align the popup vertically within the space it was allocated.
I would suggest using a Grid as the root element for the Page, and putting the StackPanel and Popup directly inside the Grid, like this:
<Grid>
<StackPanel Name="StackMain">
<TextBlock>
This is some text<LineBreak />
This is some text<LineBreak />
This is some text<LineBreak />
This is some text<LineBreak />
</TextBlock>
<Button Click="Button_Click" Content="Click Me"></Button>
</StackPanel>
<Popup x:Name="popupTest">
<Border
Name="popupTestBorder"
Background="{StaticResource ApplicationPageBackgroundThemeBrush}"
BorderBrush="{StaticResource ApplicationForegroundThemeBrush}"
BorderThickness="2">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Name="txtPopup"
Text="This is some text"
FontSize="24"
HorizontalAlignment="Center" />
<Button Name="btnClose"
Click="btnClose_Click"
Content="Click to close"></Button>
</StackPanel>
</Border>
</Popup>
</Grid>
Grids are good for this purpose, when you want to have overlapping elements or multiple elements that do not affect the position and size of any other child element. You want the layout of the popup to be separate from the layout of the stack panel and its children, so you should organize your XAML as such.
Try changing your xaml as follows...
<Page...>
<Grid x:Name="LayoutRoot">
<Popup>
</Popup>
<Grid x:Name="ContentPanel">
</Grid>
</Grid>
</Page>
So move the Popup control outside the content area and put your stacklayout with all content inside the ContentPanel Grid ( as shown in code sample above )
That should stop pushing the other controls down...

Pinch on a textbox in WPF

I got a working code for scaling frameworkelements in WPF via the ManipulationDelta method. It works with all kind of elements, as buttons, shapes or textblock, but not with textboxes.
Does anybody know how I can fix it?
Edit: Here´s a simpified Version of my Code:
private void canvas_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
var element = e.Source as FrameworkElement;
var transformation = element.RenderTransform as MatrixTransform;
var matrix = transformation == null ? Matrix.Identity : transformation.Matrix;
matrix.ScaleAt(e.DeltaManipulation.Scale.X,
e.DeltaManipulation.Scale.Y,
1,
1);
}
matrix.RotateAt(e.DeltaManipulation.Rotation,
e.ManipulationOrigin.X,
e.ManipulationOrigin.Y);
matrix.Translate(e.DeltaManipulation.Translation.X,
e.DeltaManipulation.Translation.Y);
element.RenderTransform = new MatrixTransform(matrix);
e.Handled = true;
}
The elements are created genericly, but it´s the same as this xaml:
<Canvas Name="SomeCanvas" ManipulationDelta="canvas_ManipulationDelta">
<TextBox Canvas.Left="400" Canvas.Top="200" Height="50" Name="s3" IsManipulationEnabled = "true" Background="#57FF3ACB" />
</Canvas>
This is because TextBox handles MouseDown events internally. They never get called, so the manipulation can't work. Here is the explanation:
http://msdn.microsoft.com/es-es/library/ms750580(v=vs.85).aspx
Alternatively, you could wrap the TextBox in a Border:
<Canvas Name="SomeCanvas" ManipulationDelta="canvas_ManipulationDelta">
<Border Canvas.Left="400" Canvas.Top="200" Background="Transparent" IsManipulationEnabled="True">
<TextBox Height="50" Name="s3" Background="#57FF3ACB" IsHitTestVisible="False" />
</Border>
</Canvas>
Put the IsHitTestVisible property to False in order to let the Mouse events pass from the TextBox to the Border.
Furthermore, you need to set the Border's Background to make it hit test visible.

ActualHeight and ActualWidth of object is always zero

I have a Canvas with a TextBlock like so:
<Canvas x:Name="ContentPanel" Grid.Row="1" DoubleTapped="ContentPanel_DoubleTapped">
<TextBlock x:Name="WordBlock" FontSize="226.667" FontFamily="Segoe UI Semilight" TextAlignment="Center"
RenderTransformOrigin="0.5, 0.5">
<TextBlock.RenderTransform>
<TranslateTransform x:Name="translate"/>
</TextBlock.RenderTransform>
</TextBlock>
</Canvas>
My app is such that when the user navigates to this page, the TextBlock will be centered in the Canvas and if the TextBlock's width is greater than that of the canvas, the marquee animation will occur:
private void SetAnimation()
{
Canvas.SetLeft(WordBlock, (ContentPanel.ActualWidth - WordBlock.ActualWidth) / 2);
Canvas.SetTop(WordBlock, (ContentPanel.ActualHeight - WordBlock.ActualHeight) / 2);
if (WordBlock.ActualWidth > ContentPanel.ActualWidth)
{
MarqueeAnimation.From = WordBlock.ActualWidth;
MarqueeAnimation.To = -WordBlock.ActualWidth;
MarqueeAnimation.Duration = new Duration(new TimeSpan(0, 0, 10));
MarqueeBoard.Begin();
}
}
This method is called OnNavigatedTo. I can't figure out why the TextBlock won't center because the ActualHeight and ActualWidth properties are always coming back as 0.0. I don't want to put fixed sizes because this is a Windows Store app and would like for it to be scalable across different screen sizes.
Any ideas? I'm stuck.
When OnNavigatedTo is called I don't believe the page has actually been drawn yet. I had similar confusion when trying to resize and rearrange things. The answer appeared to be to wait until the page has loaded and do the calculation then:
public ChallengePage()
{
this.InitializeComponent();
this.Loaded += ChallengePage_Loaded;
}
void ChallengePage_Loaded(object sender, RoutedEventArgs e)
{
*Do your calculations which use ActualWidth and ActualHeight in here*
}
You'll need something like that I believe. Adding the Loaded handler into your initialize for the page, and then you can call SetAnimation from in there.

How to set two alignment in wrappanel

How can I get a wrappanel like the pics below? The two button < > and textblock align to left, and the textbox align to right, when I resize width of windows, the textbox auto wrap to new line.
Here is a quick and dirty way of doing it.
<WrapPanel Orientation="Horizontal" SizeChanged="WrapPanel_SizeChanged">
<TextBlock x:Name="DateTextBlock" TextWrapping="Wrap" MinWidth="280"><Run Text="July 03-09, 2011"/></TextBlock>
<TextBox x:Name="SearchTextBox" Width="250" HorizontalAlignment="Right" />
</WrapPanel>
Then in your your WrapPanel_SizeChanged handler you simply make the DataTextBlock as wide as possible - as wide as the panel less the width of the Search TextBox.
private void WrapPanel_SizeChanged(object sender, System.Windows.SizeChangedEventArgs e)
{
var panel = (WrapPanel)sender;
var maxWidth = panel.ActualWidth - SearchTextBox.ActualWidth;
DateTextBlock.Width = maxWidth;
}

Categories