I am using a storyboard to change the opacity of a Rectangle object from 1 to 0. The Storyboard works perfectly using the following codebehind:
StoryboardFadeHider.Begin();
My page is using NavigationCacheMode = NavigationCacheMode.Enabled; to cache the page state. The animation is started in OnNavigatedTo but has a delayed BeginTime. When loading the page for the first time, this works perfectly as the Opacity is 1 and fades to 0. When loading the page from cache, the opacity is 0 because the storyboard run already. The result in this case is
Opacity is 0
Delay
Storyboard starts and sets Opacity to 1
fade to 0.
Setting the Opacity to 1 with FadeHider.Opacity = 1; before running the storyboard doesn't work somehow, my guess is that the cached storyboard overrides this.
This is the storyboard and Rectangle:
<Page.Resources>
<Storyboard x:Name="StoryboardFadeHider">
<DoubleAnimation Storyboard.TargetName="FadeHider" BeginTime="0:0:1" Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:0.5" />
</Storyboard>
</Page.Resources>
...
<Rectangle Grid.Row="0" Name="FadeHider" Opacity="1" Stretch="Fill" Fill="Black" />
How can i set the opacity to 1 everytime the page is loaded before running the storyboard?
When you navigate to other page reset the opacity to 1 in OnNavigatingFrom:
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
StoryboardFadeHider.Stop();
}
Also in OnNavigatedToyou can start the storyboard again :
protected override void OnNavigatedTo(NavigationEventArgs e)
{
StoryboardFadeHider.Begin();
}
Related
Is there a way to prevent the animation from starting if the width is greater than 0. The problem is that when the width is already 250 if the animation starts, it will reset the panel width to 0 then will start the animation.
<Storyboard x:Key="Expand">
<DoubleAnimation From="0" To="250" Duration="00:00:01" Storyboard.TargetProperty="(DockPanel.Width)" Storyboard.Target="{Binding ElementName=Panel}"/>
</Storyboard>
I have several instances of the following xaml code. Essentially it's a bunch of grids which show up in a stackpanel in a small (24 pixel tall) state, showing one line of the textblock. When you click on the arrow image (or rather the border around the image as there's transparency in the image) then the grid expands to show all the details within it. I have 15 of these in total:
<Grid x:Name="borLecSec1" Style="{StaticResource SearchedSectionGrid}">
<TextBlock x:Name="txtLecSec1" Style="{StaticResource SearchedSectionText}"
PointerEntered="SearchSectionEntered" PointerExited="SearchSectionExited"
Tapped="SearchSectionTapped"/>
<Border x:Name="backArrowSection_Lec_1" Style="{StaticResource ExpandSectionButton}"
PointerEntered="backArrowSectionEnter" PointerExited="backArrowSectionExit"
Tapped="backArrowSectionTapped">
<Image x:Name="arrowSection_Lec_1" Style="{StaticResource ExpandSectionImage}">
<Image.RenderTransform>
<CompositeTransform ScaleY="1"/>
</Image.RenderTransform>
</Image>
</Border>
</Grid>
I haven't figured out how to animate the grids themselves expanding yet as they would have to go from 24 pixels to Auto which I haven't gotten to work. What I have gotten to work is that when clicked, this arrow flips vertically so that it now signifies that another click will cause the grid to collapse down. The storyboard for this animation is this:
<Storyboard x:Name="SearchSectionArrowExpand">
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)"
From="1" To="-1" Duration="00:00:0.15"/>
</Storyboard>
<Storyboard x:Name="SearchSectionArrowCollapse">
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)"
From="-1" To="1" Duration="00:00:0.15"/>
</Storyboard>
As there are many instances of this arrow image control, the target property of the storyboard is changed before every Begin() statement in the C# code behind. The code I have follows. In it, the working_grid and working_image objects correspond to borLecSec1 and arrowSection_Lec_1 respectively in the xaml above.
if (working_grid.Height == 24)
{
System.Diagnostics.Debug.WriteLine("expand");
working_grid.Height = double.NaN;
SearchSectionArrowCollapse.Stop();
SearchSectionArrowExpand.Stop();
Storyboard.SetTargetName(SearchSectionArrowExpand, working_image_name);
SearchSectionArrowExpand.Begin();
}
The collapse part of the code is very similar in the subsequent else. The Stop() commands are necessary as I get an error if they're not there saying the root storyboard must be stopped before re-targeting. So everything I've said works fine. What doesn't work is that if I expand the first grid, so arrowSection_Lec_1 has a ScaleY of -1, if I then expand the second grid giving arrowSection_Lec_2 a ScaleY of -1 as well, the first image reverts back to having a ScaleY of 1 even though its corresponding grid is still expanded.
The solution I thought up is to have the storyboard Completed event set the ScaleY of the appropriate arrow explicitly so that it would maintain this position even if the storyboard is run again for a different arrow. I can't figure out how to reference this property in C#.
So for clarity, my question is how would I set the ScaleY transform of arrowSection_Lec_1 to -1 from the code behind?
Here is how to access the Scale from the codebehind:
var transform = (CompositeTransform)arrowSection_Lec_1.RenderTransform;
transform.ScaleY = -1;
I love Androids new animation where you touch a control (listviewitem, button etc etc) and it does a neat animation like this:
I'm wondering how this can be implemented in a nice way globally for all the 'clickable' controls in WPF.
What I specifically need help with is how the circles should be created on the control. The only thing I've thought of was to create own user-controls for each other control (buttons, radiobuttons, etc) where I have a parent for the ellipse as well as the original control itself.
<UserControl>
<Grid MouseLeftButtonDown="handler">
<Button/> <--- this would be the button which you normally would place
</Grid >
</UserControl>
And in the handler-method then create an ellipse on the point e.GetPosition(handler) using the margin-properties and later animate it. This solution would work. But it would be a hassle to do this for every control I would want the ripple effect on. Basically something like this:
void handler(object sender, MouseButtonEventArgs e)
{
Grid parent = (Grid)sender;
Ellipse ellipse = new Ellipse();
ellipse.Height = 10; // would be animated
ellipse.Width = 10; // would be animated
Point p = e.GetPosition(parent);
ellipse.Margin = new Thickness(p.X, p.Y, 0, 0);
parent.Children.Add(ellipse);
// do the animation parts to later remove the ellipse
}
Is there a cleaner, more expandable way to place ellipses on my controls other than the way I earlier demonstrated since not all controls support having children?
UPDATE:
This problem was so interesting to me that I implemented it. You can find it on my Github page: https://github.com/Domysee/WpfCustomControls. There are multiple custom controls, the one you are looking for is RippleEffectDecorator.
Now I explain what I did:
I created a custom control that inherits from ContentControl, RippleEffectDecorator. It defines an additional dependency property HighlightBackground, which is used for the background after you clicked the element.
The ControlTemplate of RippleEffectDecorator consists of a Grid, an Ellipse and a ContentPresenter.
<ControlTemplate TargetType="{x:Type l:RippleEffectDecorator}">
<Grid x:Name="PART_grid" ClipToBounds="True" Background="{TemplateBinding Background}"
Width="{Binding ElementName=PART_contentpresenter, Path=ActualWidth}"
Height="{Binding ElementName=PART_contentpresenter, Path=ActualHeight}">
<Ellipse x:Name="PART_ellipse"
Fill="{Binding Path=HighlightBackground, RelativeSource={RelativeSource TemplatedParent}}"
Width="0" Height="{Binding Path=Width, RelativeSource={RelativeSource Self}}"
HorizontalAlignment="Left" VerticalAlignment="Top"/>
<ContentPresenter x:Name="PART_contentpresenter" />
</Grid>
</ControlTemplate>
I used a Grid instead of a Border so that I can add multiple child elements (necessary that Ellipse and ContentPresenter can overlap). The ellipse binds its Height property to its own width, so that it is always a circle.
Now to the important part: the animation.
The Grid defines in its resources a Storyboard, which is played on every MouseDown event.
<Storyboard x:Key="PART_animation" Storyboard.TargetName="PART_ellipse">
<DoubleAnimation Storyboard.TargetProperty="Width" From="0" />
<ThicknessAnimation Storyboard.TargetProperty="Margin" />
<DoubleAnimation BeginTime="0:0:1" Duration="0:0:0.25" Storyboard.TargetProperty="Opacity"
From="1" To="0" />
<DoubleAnimation Storyboard.TargetProperty="Width" To="0" BeginTime="0:0:1.25" Duration="0:0:0" />
<DoubleAnimation BeginTime="0:0:1.25" Duration="0:0:0" Storyboard.TargetProperty="Opacity" To="1" />
</Storyboard>
The storyboard animates the width property of the ellipse so that it fills the area completely. It also has to animate the Margin, because the ellipse positions itself relative to the upper left point (not around its center).
The start position of the ellipse, its target width and its position in the container throughout the effect has to be set programmatically.
I overwrite the OnApplyTemplate() method to add an event handler to the mouse down event, which starts the storyboard and sets all necessary values.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
ellipse = GetTemplateChild("PART_ellipse") as Ellipse;
grid = GetTemplateChild("PART_grid") as Grid;
animation = grid.FindResource("PART_animation") as Storyboard;
this.AddHandler(MouseDownEvent, new RoutedEventHandler((sender, e) =>
{
var targetWidth = Math.Max(ActualWidth, ActualHeight) * 2;
var mousePosition = (e as MouseButtonEventArgs).GetPosition(this);
var startMargin = new Thickness(mousePosition.X, mousePosition.Y, 0, 0);
//set initial margin to mouse position
ellipse.Margin = startMargin;
//set the to value of the animation that animates the width to the target width
(animation.Children[0] as DoubleAnimation).To = targetWidth;
//set the to and from values of the animation that animates the distance relative to the container (grid)
(animation.Children[1] as ThicknessAnimation).From = startMargin;
(animation.Children[1] as ThicknessAnimation).To = new Thickness(mousePosition.X - targetWidth / 2, mousePosition.Y - targetWidth / 2, 0, 0);
ellipse.BeginStoryboard(animation);
}), true);
}
Note: the last parameter of AddHandler() determines whether or not you want to receive handled events. It is important to set this to true, because some UiElements handle mouse events (e.g. Button). Otherwise the MouseDownEvent would not fire and therefore the animation not executed.
To use it simply add the element on which you want to have this effect as child of RippleEffectDecorator, and the Background to Transparent:
<cc:RippleEffectDecorator Background="Green" HighlightBackground="LightGreen">
<Button FontSize="60" Background="Transparent">stuff</Button>
</cc:RippleEffectDecorator>
Note2: some elements include triggers which set the template on MouseOver (e.g. Button) and therefore hide the effect. If you dont want that you have to set the template of the button and remove these triggers. The easiest way is to use Blend, get the template of the button from it, remove all triggers and add it as template of your button.
there also a very cool WPF material design library
http://materialdesigninxaml.net/
I suggest using Custom Controls over UserControls for this. Almost everything could be handled in xaml that way. Once you have your control styled then all you have to do is add an Ellipse and set a MouseDown or Button.Pressed trigger to your ControlTemplate.Triggers. Then your animation would only need to increase the height and width of the Ellipse until the ripple effect was completed then fade the Opacity to 0.
For the Ellipse make sure you have the position fixed and for the same color scheme try a white fill and an opacity of 0.3-5.
For the ring effect on the button in the top corner of your add a 2nd Ellipse set the Fill to Transparent and the Stroke to White.
I have set up a textblock to set the string "ALARM" added to it when the alrm clock goes off. The code is working fine for this process. I am trying to make this string "ALARM" (or the textblock itself) flash when the alarm goes off.
I am able to work out the codes to make the string "ALARM" fade in fade out using mouse events but cant figure out how to make it happen w/o the need for mouse events. I tried textBlock_Loaded event and that doesn't work. I want the fade in fade out to be ongoing forever in a loop to create a flashing effect.
Please advice if there is an event that would fit my need. Been trying one by one down the list of available events with no success. My codes for the mouse events is below. Appreciate any advice. Thanks.
private void textBlock3_MouseLeave(object sender, MouseEventArgs e)
{
TextBlock textblk = (TextBlock)sender;
DoubleAnimation animation = new DoubleAnimation(0, TimeSpan.FromSeconds(2));
textblk.BeginAnimation(TextBlock.OpacityProperty, animation);
}
private void textBlock3_MouseEnter(object sender, MouseEventArgs e)
{
TextBlock textblk = (TextBlock)sender;
DoubleAnimation animation = new DoubleAnimation(1, TimeSpan.FromSeconds(2));
textblk.BeginAnimation(TextBlock.OpacityProperty, animation);
}
All you need is an EventTrigger with the "Loaded" event. Set the RepeatBehavior to "Forever" so that the Storyboard keeps repeating, and AutoReverse to "True":
<TextBlock x:Name="textBlock3" Text="hello world">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard RepeatBehavior="Forever" AutoReverse="True">
<DoubleAnimation Storyboard.TargetProperty="Opacity"
Duration="0:0:1"
To="0"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
I have two overlapping (in same position, having the same size) MediaElements. They will contain images. The opacity of one element will be set to 1.0 and the opacity of the other set to 0.0. The idea here would be a simple transition for a slide-show type deal. When it's time to display the next slide, the background element loads a picture and the opacity of both elements switches gradually.
I tried (successfully) to implement this behavior using System.Timers, only to find that having more than some arbitrary number of timers in the same application would cause .NET to randomly spawn and cede control of timer_elapsed to several different threads. This caused unpredictable results and generally made me question my sanity.
So, I decided to do the same thing, but with System.Threads and their Sleep functions. For whatever reason, gradually cycling the opacity worked perfectly with the insane timers but fails utterly with threads. And it fails in a ridiculous way. The opacity of both elements does change, but there's no in between. The element is shown either with opacity at 1.0 or 0.0. Otherwise I would notice that roughly half the pictures weren't being cycled through.
After much googling, I thought perhaps the priority of the thread that the opacity changes were occurring on was somehow keeping the UI elements from being rendered immediately. But then I recalled that because I was using dispatcher invocations on the media elements, all of the action was taking place on the main thread anyway, so it wouldn't make a difference.
Contemplate the following code: https://gist.github.com/956093
As suggested you should use the native animations; i have come accross this thread issue before as well and in general i try to avoid using Dispatchers, i pretty much only use them to modify data if i must (e.g. ObservableCollection cannot be modified in a background thread, don't know any other examples actually).
You could still use normal threads, they work well if you use bindings to update the UIElements which nicely bypasses the dispatching issue.
Animation example:
<Grid Name="testGrid" Tag="2">
<Grid.Resources>
<Storyboard x:Key="FadeAnim2to1">
<DoubleAnimation Storyboard.Target="{x:Reference img1}"
Storyboard.TargetProperty="Opacity"
Duration="0:0:1" To="1"/>
<DoubleAnimation Storyboard.Target="{x:Reference img2}"
Storyboard.TargetProperty="Opacity"
Duration="0:0:1" To="0"/>
</Storyboard>
<Storyboard x:Key="FadeAnim1to2">
<DoubleAnimation Storyboard.Target="{x:Reference img1}"
Storyboard.TargetProperty="Opacity"
Duration="0:0:1" To="0"/>
<DoubleAnimation Storyboard.Target="{x:Reference img2}"
Storyboard.TargetProperty="Opacity"
Duration="0:0:1" To="1"/>
</Storyboard>
</Grid.Resources>
<Image x:Name="img1" Source="Images/Default.ico" Width="200" Height="200" Opacity="0"/>
<Image x:Name="img2" Source="Images/Error.ico" Width="200" Height="200"/>
</Grid>
<Button Content="Fade" Click="Button1_Click"/>
private void Button1_Click(object sender, RoutedEventArgs e)
{
Storyboard anim;
if ((string)testGrid.Tag == "1") //This is just for brevity, you should of course not use the Tag to store state information, let alone number strings
{
anim = testGrid.Resources["FadeAnim1to2"] as Storyboard;
testGrid.Tag = "2";
}
else
{
anim = testGrid.Resources["FadeAnim2to1"] as Storyboard;
testGrid.Tag = "1";
}
anim.Begin();
}