One of the main design principles of Metro and Windows Store Apps is to make the UI as responsive as possible while maintaining the smooth transition effects in response to working threads.
For finished control implementation, please scroll to the bottom, unless you want to read a little more about the transitions and animations used for the control.
Following these two basic statements, we will try to solve a very common problem about using web resources in windows runtime. For this scenario, let’s assume we are working on an application that is supposed to visualize a certain dataset that is downloaded from a network source. In this dataset, let us again assume we have certain URIs that we want to use as a source to content images. The best solution for displaying a smooth layout while loading the images from this network source would be to set them to invisible until the image is loaded fully and then fade them in using a transition. (e.g. You can see a concrete example in the Bing app while it is opening the search results and loading the images)
This is not an uncommon problem and if we were dealing with WPF, we could easily deal with this using triggers.
<Style x:Key="StyleImageFadeIn" TargetType="{x:Type Image}"> <Setter Property="Opacity" Value="0" /> <Style.Triggers> <!--Sets visibility to Collapsed if Source is null - this will cause IsVisible to be false--> <Trigger Property="Source" Value="{x:Null}"> <Setter Property="Visibility" Value="Collapsed" /> </Trigger> <!--Fades-in the image when it becomes visible--> <Trigger Property="IsVisible" Value="True"> <Trigger.EnterActions> <BeginStoryboard> <BeginStoryboard.Storyboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:1"/> </Storyboard> </BeginStoryboard.Storyboard> </BeginStoryboard> </Trigger.EnterActions> </Trigger> </Style.Triggers> </Style>
Beautiful, so the image is collapsed when the Source is set to null. When it becomes visible, meaning when the source is set, we nicely fade it in.
BUT… Where are the triggers in WinRT and Windows Store Apps. The answer is: “They are gone”. However, we have the new, let’s say, “packaged” version of some commonly used animations such as “EntranceThemeTransition”, “RepositionThemeTransition” etc. These “packaged” transitions can be easily included in the transitions property of the framework elements. However, these are render transitions and it would not help us in our quest to load the image from the network source and fade it in, because in most cases the render transition would actually be played before the network resource is actually loaded.
We can go ahead and use the Image controls ImageOpened property and deal with the animation on the page’s implementation but we want a more compact solution that can easily be reused in our other projects.
So let’s try to implement a user control. First thing we do is to create a grid and add an image control in it. We also need to declare the fade-in animation and possibly fade-out animations for the image.
<UserControl x:Class="Samples.ImagePreview" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Name="ControlRoot" d:DesignHeight="300" d:DesignWidth="400"> <Grid> <Image x:Name="Image" Source=" " Height="{Binding ElementName=ControlRoot, Path=ActualHeight}" Width="{Binding ElementName=ControlRoot, Path=ActualWidth}" /> <Grid.Resources> <Storyboard x:Name="ImageFadeOut"> <FadeOutThemeAnimation Storyboard.TargetName="Image" /> </Storyboard> <Storyboard x:Name="ImageFadeIn"> <FadeInThemeAnimation Storyboard.TargetName="Image" /> </Storyboard> </Grid.Resources> </Grid> </UserControl>
Notice that instead of using a standard DoubleAnimation on opacity we are using the FadeInThemeAnimation and the FadeOutThemeAnimation.
Next let’s declare a dependency property for the image source and another one for a placeholder (just in case).
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(ImageSource), typeof(ImagePreview), new PropertyMetadata(default(ImageSource), SourceChanged)); public static readonly DependencyProperty PlaceHolderProperty = DependencyProperty.Register("PlaceHolder", typeof(ImageSource), typeof(ImagePreview), new PropertyMetadata(default(ImageSource))); public ImageSource Source { get { return (ImageSource)GetValue(SourceProperty); } set { SetValue(SourceProperty, value); } } public ImageSource PlaceHolder { get { return (ImageSource)GetValue(PlaceHolderProperty); } set { SetValue(PlaceHolderProperty, value); } }
So at first the PlaceHolder will be set as the source for the image control, and once the actual source is bound and loaded we can fadeout the placeholder and fade the actual image in. This process of fading out the placeholder and setting the source to the loaded image would look something like:
private void LoadImage(ImageSource source) { ImageFadeOut.Completed += (s, e) => { Image.Source = source; ImageFadeIn.Begin(); }; ImageFadeOut.Begin(); }
Once the Image control that is showing the placeholder, or another source as for that matter, is faded out, we can change the source and fade the control back in.
So only thing left to implement is a method to download the image from the network source when the bound Source is actually changed. (i.e. Notice in the dependency property declaration we already declared a callback for SourceChanged.)
private static void SourceChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) { var control = (ImagePreview)dependencyObject; var newSource = (ImageSource) dependencyPropertyChangedEventArgs.NewValue; System.Diagnostics.Debug.WriteLine("Image source changed: {0}", ((BitmapImage)newSource).UriSource.AbsolutePath); if(newSource != null) { var image = (BitmapImage) newSource; // If the image is not a local resource or it was not cached if (image.UriSource.Scheme != "ms-appx" && image.UriSource.Scheme != "ms-resource" && (image.PixelHeight * image.PixelWidth == 0)) { // TODO: control.DownloadImageAsync(newSource); } else { control.LoadImage(newSource); } } }
This implemented method is doing the usual checks, if it is a local resource it is just loading the image directly with a fade-in, if it is a remote resource, it is calling the method DownloadImageAsync (which we haven’t implemented and will not have to), which, in short, “downloads” the image as a Random Access Stream, loads it into a BitmapImage and assigns it to the image control with LoadImage method.
However, if we go down this road, you will notice that every time we open the same page with our control on it, the images will be fading in and out. Normally, when an image is opened with an image control, (unless instructed otherwise) windows runtime creates a cached version for this image data. Under normal circumstances, we would want to make use of this image caching.
The easiest option would be to create a secondary image control in the UserControl grid which will be used to load the image for the first time, and then once it is loaded assign it to the actual element. Using this “staging” element we can make use of the image caching and have a reusable control.
Below is the completed sample.
Happy coding everyone…
Finished Control
XAML:
<Grid> <Image x:Name="Staging" Visibility="Collapsed"/> <Image x:Name="Image" Source="{Binding ElementName=ControlRoot, Path=PlaceHolder}" Height="{Binding ElementName=ControlRoot, Path=ActualHeight}" Width="{Binding ElementName=ControlRoot, Path=ActualWidth}" /> <Grid.Resources> <Storyboard x:Name="ImageFadeOut"> <FadeOutThemeAnimation Storyboard.TargetName="Image" /> </Storyboard> <Storyboard x:Name="ImageFadeIn"> <FadeInThemeAnimation Storyboard.TargetName="Image" /> </Storyboard> </Grid.Resources> </Grid>
And the C# implementation:
public sealed partial class ImagePreview : UserControl { public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(ImageSource), typeof(ImagePreview), new PropertyMetadata(default(ImageSource), SourceChanged)); public static readonly DependencyProperty PlaceHolderProperty = DependencyProperty.Register("PlaceHolder", typeof(ImageSource), typeof(ImagePreview), new PropertyMetadata(default(ImageSource))); public ImageSource Source { get { return (ImageSource)GetValue(SourceProperty); } set { SetValue(SourceProperty, value); } } public ImageSource PlaceHolder { get { return (ImageSource)GetValue(PlaceHolderProperty); } set { SetValue(PlaceHolderProperty, value); } } public ImagePreview() { this.InitializeComponent(); } private static void SourceChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) { var control = (ImagePreview)dependencyObject; var newSource = (ImageSource) dependencyPropertyChangedEventArgs.NewValue; System.Diagnostics.Debug.WriteLine("Image source changed: {0}", ((BitmapImage)newSource).UriSource.AbsolutePath); if(newSource != null) { var image = (BitmapImage) newSource; // If the image is not a local resource or it was not cached if (image.UriSource.Scheme != "ms-appx" && image.UriSource.Scheme != "ms-resource" && (image.PixelHeight * image.PixelWidth == 0)) { image.ImageOpened += (sender, args) => control.LoadImage(image); control.Staging.Source = image; } else { control.LoadImage(newSource); } } } private void LoadImage(ImageSource source) { ImageFadeOut.Completed += (s, e) => { Image.Source = source; ImageFadeIn.Begin(); }; ImageFadeOut.Begin(); } }
Pingback: Image UserControl with FadeIn & FadeOut Animation for WinRT
Pingback: Image UserControl with FadeIn & FadeOut Animation for WinRT | Answer My Query
Pingback: Windows Store Developer Links – 2013-05-29 | Dan Rigby
hi your coding is always fabulous and time saving in searching code too.Thanks i m newbie to c# few months only the above code i make a mistake can you provide above sample please my mail id prasana.1412@gmail.com
LikeLike
Excellent sample! I made something similar in my project when I wanted to animate image changes.
There is just one problem in your sample that I should warn you about, considering that you are using the same storyboard every time and that LoadImage method could be called more than once. Using lambda to add a handler for storyboard completed event will create and add a new one every time LoadImage is executed. This is OK if you are using temporary storyboards to play animation only once before disposing of it but it is not the case here.
A good way around that would be to define event handler first so you can reference to itself in order to unsubscribe from completed event when it completes.
The body of LoadImage method should look like this:
LikeLiked by 1 person
I solved this by just declaring the handler once in the constructor isnce there’s really no need to pass source to the handler as it is held by the Staging element.
public ImagePreview()
{
this.InitializeComponent();
ImageFadeOut.Completed += (o, e) =>
{
Image.Source = this.Staging.Source;
ImageFadeIn.Begin();
};
}
private static void SourceChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var control = (ImagePreview)dependencyObject;
var image = dependencyPropertyChangedEventArgs.NewValue as BitmapImage;
if (image != null)
{
// If the image is not a local resource or it was not cached
if (image.UriSource.Scheme != “ms-appx” && image.UriSource.Scheme != “ms-resource” && (image.PixelHeight * image.PixelWidth == 0))
{
image.ImageOpened += (sender, args) => control.ImageFadeOut.Begin();
control.Staging.Source = image;
}
else
{
control.ImageFadeOut.Begin();
}
}
}
LikeLike
Selam Can,
3 resim var. İstiyorum ki repeat bir şekilde image show olsun. Windows Phone 8.1 için en basitinden fade in fade out olacak bir şekilde. Animasyon yapmak için uğraşıyorum, araştırıyorum ancak başarılı olamadım. En sonunda Google ile senin siteyi buldum. Cevap yazarsan mutlu olurum 🙂
LikeLike
selam Burhan,
turkiye’den comment falan gelince cok mutlu oluyorum 🙂
iki uc resmi rotate etcek bi control icin, fikrin ayni olmasi lazim… sadece placeholder elementinin yerine diger resimleri load edebilmeck icin Image control ekleyip timerla degisimi halledebilecegini dusunuyorum… zamanim oldugun da bi bakayim ornegi genisletebilirim belkide… bol sanslar…
LikeLike
Great tutorial. Many thanks.
LikeLike
İnceledim kodlarınızı, İngilizce bilmiyorum ancak isminizi sonradan fark ettim ve gördüm ki , yazınızı Türkçe yazmamışsınız. İngilizce yazmanıza söylenecek bir söz yok kendi adınıza ve bir çok insana çok yararı da olabilir tabi ancak neden Türkçe değil?! Bütün bunları kendime sordum ama bir de size sormak istedim.
LikeLike
fena fikir degil aslinda. ama uzun suredir ingilizce’yi calisma dili olarak kullandigim icin turkce yazmak cok daha fazla zaman alacak.
LikeLike