UWP Game: Advanced Splash Screen

  • Tutorial
Hello, Habr! We continue our experimental series of articles for self-taught programmers, in which Alexei Plotnikov , one of the members of our Microsoft Developer community, talks about creating a game on UWP. Today we’ll talk about the extended splash screen. Do not forget to leave comments, you can influence the development process.




I give the floor to the author.

Objectively speaking, the topic of an extended splash screen should be considered closer to the end of this cycle. This is not only not the most important part of the application, but, in principle, not mandatory. However, since the cycle is written in parallel with the development of the project, this introduces some adjustments to the order in which articles are written. I have an expanded splash screen ready, and during the cycle there will be situations when I will be sent to him, so I decided to talk about him first.

We start small - what is a splash screen and why should it be expanded? You have certainly come across application screensavers long before the advent of UWP. We won’t go far. When you start Visual Studio, you enter a rectangular image with the appropriate caption.



Each program needs a little time to initialize and load the interface, and so that at this time the user understands that something is happening, a splash screen is displayed. In programs where the startup process is more complicated and takes more time, an advanced splash screen is used. On these, we often see text or a status bar showing the download status of the application.

Now let's see how it works in UWP. The main difference is the platform features. Since it is universal, the concept of “window” is not a priority here, and the concept of “screen” comes to the fore. The container in which the screen is displayed depends on the device and the window is only on the PC.

The first thing a user sees when launching your application is a splash screen consisting of a PNG image and a background fill. If you just created your application and did not change anything, the splash screen will be painted in the main color of the system, and in the center there will be a basic “stub” picture, which is automatically created with the application. By default, this picture is located in the “Assets” folder and is called “SplashScreen.png”. Also, the file name may contain something like “scale-200”. This is part of the scaling system, which I will discuss separately in one of the articles in the series, and now I will only discuss it in general terms.



Since your application can run on a variety of devices, you have the opportunity to create several versions of images with different sizes so that at different screen resolutions it does not lose quality from stretching or compressing. The standard name of any image in the project consists of two parts - the name and extension. Such an image will be perceived as an image on a scale of 100%. If you add “.scale-xxx” between the name and the extension, where xxx is the scaling factor, then it will be considered by default for screens with this scale, and stretch or shrink on the rest (if there are no versions with their scale). For example, for a scale of 200%, the name of the splash screen image would be “SplashScreen.scale-200.png”. Further in the text I will consider only images at a scale of 100%, чтобы не дублировать тему, запланированную для будущих статей.

Now directly about the size of the image. All basic visual resources of the application have fixed sizes for each of the scales. The image on the splash screen in a standard scale should have a size of 620x300 pixels. If you try to use a different size, you will receive a warning about the error, however, looking at the screenshot above, you can see a square image of a clearly different size. In fact, it also has a size of 620x300 pixels, only most of it is transparent, which is not only not forbidden, but even recommended in many scenarios.

The background color of the splash screen uses the default system color, or the one you specified in the application manifest.



Setting the color for the background, as well as choosing your own image, is carried out in the manifest editor window on the "Visual Resources" tab in the "Screen Saver" subsection. As mentioned in the previous article, you can open the manifest editor by double-clicking on the Package.appxmanifest file in the object browser. This editor is developing along with the UWP platform and in the latest versions of VS there is a tool that allows you to quickly form all the necessary variations from one image on different scales, but I will talk about this tool separately in another article, but here I will consider only manual parameter setting .

In the first field “Screensaver background” you need to specify the background color in HTML (HEX) format. You can get the color value in a suitable format in almost any image editor (including Paint 3D) or online, but do not forget about the rules for writing this color, according to which the “#” sign should be at the beginning of the alphanumeric value. For example, my screen saver uses the value “# fae57a” as its background.



The second screen saver prompts you to specify the storage folder and base name for the image file. The “Assets” folder is not an obligatory place for storing images, just as the “SplashScreen” name is not obligatory, however, the need to change them may arise only when transferring old projects. In other cases, it is much easier to use basic values, so in my project I leave the name and path unchanged. I also note that when specifying the base name, you do not need to add an indication of the scale. Such a prefix in the name appears automatically when you select in the windows below.

Actually, the windows below contain a preview of the installed images and buttons for their selection for each scale. Also next to the windows there is a hint what size the image should have for a particular scale. As you might have guessed, you can select an image of the appropriate size anywhere with any name, and it will be placed in the specified folder with the specified name and adding scale data.
After choosing the background color and image, you can start the application and observe the result of the changes.



So, the standard splash screen is ready and it's time to move on to creating an advanced version. As I mentioned above, in more complex programs, instead of the standard image of the screensaver, a screensaver with a download status is displayed. The expanded UWP splash screen performs the same function, however, it makes sense to use it only if there are lengthy tasks at the start. In my application, these tasks include: loading saves, initial calculation of changes, accessing a database on the network and checking purchases. On different devices, all these tasks will take different time and it would be nice if on weaker devices at this moment the user understood that the application did not hang.

Despite the name “Extended splash screen” that has become fixed in the documentation, in fact we will deal with a self-created page that will simulate a splash screen with the addition of loading indicators and in any case it will be preceded by the display of the standard screen, which we examined above . As a result, the extended screensaver should for the most part repeat the standard and appear most smoothly after the standard.

From a design point of view, this stage is very important and, depending on the flight of your imagination, you can create a very interesting and attractive screen saver that will greatly affect the perception of your application, so do not underestimate this step and limit yourself to banal download indicators, such as Progressring.

After a little thought, I decided to implement the load indication by rotating the blades on the mill and adding a text line that displays the current status of the load. Thus, as planned, the standard screen will quietly turn into an expanded one, the blades will begin to rotate, and text will appear below. However, taking the first steps in this matter, I came to the conclusion that the available materials do not fully cover the issue, and some of them are not used by the most successful “crutches”.

Below I will tell you my own recipe for creating a splash screen, based on articles on the network and the official guide, but still different from them.

Step 1. Create a blank page


As I mentioned above, the extended splash screen is a regular application page, which we will make as similar as possible to the basic splash screen. To add a new page to the project, go to the "Project> Add New Item" menu and select the "Blank Page" item in the dialog that appears. In the official documentation, it is recommended to use "ExtendedSplash" as the name of this page, and although we are free to choose any other name, we have no objective reasons for this. We indicate the proposed name and click "Add."

The newly created page consists of two files: one for marking ExtendedSplash.xaml and the second for code ExtendedSplash.xaml.cs / vb . We will return to them a little later, but for now the next step.

Step 2. Assigning ExtendedSplash as the Start


In the official guide, this step is done at the end, but I prefer to do it in advance. The fact is that it’s not enough for us to simply add a country and to make it appear right behind the system splash screen, we need to perform some manipulations in the App.xaml.cs / vb file .

As you remember from the previous article, this is the main application file responsible for its life cycle. In particular, it has the OnLaunched procedure, which is executed immediately when the application starts and in which the start page is set.

Here is the code for this procedure immediately after creating a new project (VB.NET):

Protected Overrides Sub OnLaunched(e As Windows.ApplicationModel.Activation.LaunchActivatedEventArgs)
        Dim rootFrame As Frame = TryCast(Window.Current.Content, Frame)
        If rootFrame Is Nothing Then
            rootFrame = New Frame()
            AddHandler rootFrame.NavigationFailed, AddressOf OnNavigationFailed
            If e.PreviousExecutionState = ApplicationExecutionState.Terminated Then
            End If
            Window.Current.Content = rootFrame
        End If

        If e.PrelaunchActivated = False Then
            If rootFrame.Content Is Nothing Then
                rootFrame.Navigate(GetType(MainPage), e.Arguments)
            End If
            Window.Current.Activate()
        End If
End Sub

To reduce the code, commenting has been removed from it.

To fully understand what is happening in this procedure, a deeper understanding of the life cycle and navigation of the application will be required, but we will talk about this in one of the following articles. So far, all you need to understand from this code is that first you create a Frame object, which is set as the content (visual root) of the application, and then this Frame navigates to the MainPage page of the application.

And here it would be logical to assume that instead of MainPage you need to set ExtendedSplash and we will get the desired page as the start page, but this option does not suit us, since in this case the ExtendedSplash page will be saved in the navigation history and you can return to it, and we do not need this. To solve this problem, instead of navigating, set the application content not to Frame, but directly to ExtendedSplash, which, although it is a Page object, can also be the visual root of the application.

We fix the code as follows:

Protected Overrides Sub OnLaunched(e As Windows.ApplicationModel.Activation.LaunchActivatedEventArgs)
        Dim rootFrame As Frame = TryCast(Window.Current.Content, Frame)
        If rootFrame Is Nothing Then
            rootFrame = New Frame()
            AddHandler rootFrame.NavigationFailed, AddressOf OnNavigationFailed
            If (e.PreviousExecutionState <> ApplicationExecutionState.Running) Then
                Window.Current.Content = New ExtendedSplash(e.SplashScreen)
            End If
        End If
        If e.PrelaunchActivated = False Then
            If rootFrame.Content Is Nothing Then
                rootFrame.Navigate(GetType(MainPage), e.Arguments)
            End If
            Window.Current.Activate()
        End If
End Sub

As you noticed, the code block underwent changes in the first condition. At the bottom of this block, a check is made to see if the application is running and if it is not running, then a new ExtendedSplash page object is assigned as application content.

Also, as a parameter, when declaring a new ExtendedSplash class, the SplashScreen class is given. This class is an important part of creating an extended splash screen, as it contains data on the size and position of the image on the splash screen, which will help us quietly replace the base screen with your own. Well, so that this class contains the necessary data, we take it from the arguments received by the OnLaunched procedure from the system.

There is the last touch. In the ExtendedSplash.xaml.cs / vb code file, you need to add the New constructor, which will accept an object of type SplashScreen:

    Public Sub New(splashscreen As SplashScreen)
        InitializeComponent()
End Sub

Now, when the application starts, we will see the ExtendedSplash page, not the MainPage, which will allow us to immediately see the changes made to the page layout and more conveniently debug the code.

Step 3. Page Layout


The first and simplest change in the layout of the ExtendedSplash page is to set the background color of the page to the same as the main splash screen. The newly created page already contains the Grid root element with the Background property. By default, this property sets a system resource, but we will replace it with a color, which was previously indicated in the application manifest. In principle, the color value can be pointed to the direct Background = "# fae57a", but I store the main colors of the application in a special file ApplicationStyle.xaml , which I spoke about at the end of the previous article. The resource for the background I refer to as “BackgroundBrush”, and then refer to it in the right places.

In the style resource file:

<SolidColorBrush x:Key="BackgroundBrush" Color="#fae57a"/>
В файле ExtendedSplash.xaml:
<Grid Background="{StaticResource BackgroundBrush}"…

And finally, we got to the place where the main differences between my method and the one described in the official guide and other articles begin.

If you have already studied an article from the official documentation, then you noticed that we are proposing to use the Canvas element to position the image on the page. It is needed so that we can set the image not only size, but also position, but in practice, everything does not go as perfectly as expected.

The authors of the manual proceed from the fact that the splash screen image can be scaled differently in different situations. The reason may be both the scaling factor on different devices, and the size of the window on the PC. The behavior of the basic image in all these situations is regulated by the system, and so that we can simulate this behavior, we are given the aforementioned SplashScreen class, whose only property is ImageLocation of the Rect type. This property contains information about the size (width, height) and position of the image (X, Y), so it’s quite logical that for manipulating the image based on the available data, the Canvas element is suitable for us, which works with objects based on such parameters.

And in fact, if you implement an extended screen saver as recommended, then everything will work, but only on a PC. If you run the application on the phone, then completely unpredictable metamorphoses begin to occur. Your image will either not display or it will not display correctly. When you rotate the screen, in a horizontal position, the image runs away to the edge of the screen, partially cutting off and only when you return to the vertical position, it will take the right place.

Judging by the mass of discussions on the network, as well as on articles with recommended corrections, we can conclude that I am not the only one who encountered a similar problem. The most popular solution that I found recommends using different markup for different devices. Canvas for PC and Grid for mobile. Having completely repeated this decision, I got a workable splash screen in a compartment with a strong feeling that I was using excessive crutches.

In the end, I decided to independently comprehend what exactly is happening on the basic screen and how much I need to simulate it. As a result of a series of experiments on different devices and emulators, I realized that you can completely abandon the information about the image position. Regardless of the size of the window, the type of device or its orientation, the image on the main splash screen is always displayed in the center of the screen. Thus, we can only extract information about the size of the image, and we can easily repeat the basic screen.

My window layout in the first stage looks like this:

<Grid Background="{StaticResource BackgroundBrush}">
        <Image x:Name="SplashScreenImage3" Source="Assets/SplashScreens/SplashScreen3.png" Canvas.ZIndex="3" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <Image x:Name="SplashScreenImage2" Source="Assets/SplashScreens/SplashScreen2.png" Canvas.ZIndex="2" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <Image x:Name="SplashScreenImage1" Source="Assets/SplashScreens/SplashScreen1.png" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <TextBlock x:Name="LoadingSatatusTextBox" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,20" TextAlignment="Center" Foreground="{StaticResource ForegroundBrush}" FontWeight="Bold"/>
</Grid>

Before starting its analysis, I want to contact novice developers. If this is your first time encountering the XAML markup language, I highly recommend that you work as little as possible in the window designer (also called Constructor). Несмотря на его совершенствование с каждой новой версией Studio the Visual , он по-прежнему остается не точным инструментом. For example, when you drag an Image control onto a page, it may seem like you justified it in the center, but in fact, positioning can be set based on the indentation, and not based on the properties of the vertical and horizontal position. In our markup, it is extremely important to exclude any indentation and other factors affecting the position of images, except for two properties: HorizontalAlignment and VerticalAlignment.

But back to the markup. In order for it to work without errors, you need to add image files to the project that are used as sources in the Image elements. I located them in a subfolder of "SplashScreens" folder "Assets". To add images to the project, right-click on the desired folder and select the menu item: "Add> Existing item ...". There are three images, since I need to separate static elements from moving ones.



It is important to note that the first and third images are the same size as the base 620x300 pixels (for standard scale), while the third image contains an icon in the same place where it is located on the whole image, and the rest of it is transparent. This is done in order to minimize the labor involved in positioning them relative to each other. The image of the blades is cropped under the edge and does not have extra sections.

All three images are placed strictly in the center of the page. In our case, the center of the Grid element, but since it is the root element of the page and has no indentation, its center is equal to the center of the page. Next, we need to set the correct order of overlapping images on each other, so as not to get a situation in which the blades will be behind the mill. To do this, use the Canvas.ZIndex property, and it does not matter in what order the Image elements are placed in the markup, but only the value of this property is important. The higher the value, the more the element will be higher in overlapping (Z axis).

The last element in the markup is a text block that will display the download status. It is located in the center relative to the horizontal plane and below relatively vertical. A small indentation is also indicated below so that the text does not “stick” to the edge of the screen. Two more properties indicate text color and style.

The resource for the text color is set as follows:

<SolidColorBrush x:Key="ForegroundBrush" Color="#c89103"/>

At this point, the first stage of work with marking can be considered completed, however we will return to it when we add rotation animation for the blades.

Step 4. Page Code


The most important part of the work will be done in the page code, which is located in the file ExtendedSplash.xaml.cs / vb .

The first step is to import the Windows.UI.Core namespace and add the variables that we will need later in the code:

Imports Windows.UI.Core
Public NotInheritable Class ExtendedSplash
    Inherits Page
    Friend splashImageRect As Rect
    Private splash As SplashScreen
    Friend ScaleFactor As Double
    Public Sub New(splashscreen As SplashScreen)
        InitializeComponent()
    End Sub
End Class

Теперь разместим необходимый код в конструкторе класса:
Public Sub New(splashscreen As SplashScreen)
    InitializeComponent()
    AddHandler Window.Current.SizeChanged, AddressOf ExtendedSplash_OnResize
    splash = splashscreen
    If splash IsNot Nothing Then
        AddHandler splash.Dismissed, AddressOf DismissedEventHandler
        ScaleFactor = DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel
        splashImageRect = splash.ImageLocation
        SetSizeImage()
    End If
End Sub

The code starts by subscribing to the root container resize event. This will be needed to make changes to the image size when the window size or device orientation changes. Next, the SplashScreen class passed in the argument is transferred to the local variable, which we will refer to for information about the required image size.

After checking the local splash variable, which should not be empty, we proceed to further manipulations. Now you can subscribe to the Dismissed event, which occurs when the system splash screen closes. This point is important for understanding the general principle of the splash screen. In fact, while the code is being executed inside the New procedure, the page is still initializing and is not ready for display, therefore the basic splash screen “secures” the application indicating that it is running and the desired page will now appear. In many ways, this is why an extended splash screen is needed so that lengthy initialization processes accompany live visualization, rather than a static patch.

The next line of code saves the current scaling factor for a correct interpretation of the sizes, and the sizes themselves are stored further in the code into the splashImageRect variable. And at the end, the last line calls the procedure, where we operate with all the data received.

Of course, after adding this code, we will receive three error messages, since we do not have procedures for which the above events are attached, and there is also no SetSizeImage procedure.

Add them:

Private Sub SetSizeImage()
End Sub

Private Sub DismissedEventHandler(sender As SplashScreen, args As Object)
End Sub

Private Sub ExtendedSplash_OnResize(sender As Object, e As WindowSizeChangedEventArgs)
End Sub

И приступим к манипуляциям по установке размера изображения в процедуре SetSizeImage:
Private Sub SetSizeImage()
        SplashScreenImage1.Height = splashImageRect.Height / ScaleFactor
        SplashScreenImage1.Width = splashImageRect.Width / ScaleFactor

        SplashScreenImage2.Width = splashImageRect.Width / ScaleFactor * 0.3073
        SplashScreenImage2.Margin = New Thickness(-splashImageRect.Width / ScaleFactor * 0.547, -splashImageRect.Width / ScaleFactor * 0.1633, 0, 0)

        SplashScreenImage3.Height = splashImageRect.Height / ScaleFactor
        SplashScreenImage3.Width = splashImageRect.Width / ScaleFactor
        ScaleFactor = 1
End Sub

This code takes turns setting sizes for each image placed on the page. In the case of the first and third images, it is enough for us to set the width and height of the image equal to what we removed from the SplashScreen class, adjusted for the scaling factor. Please note that the last line of the ScaleFactor variable is assigned one and when you call this procedure again, the correction for the scaling factor will not actually occur. This is due to the same problem on which the solution from the official guide does not work on mobile. For some reason, the SplashScreen class informs you about the size of the image taking into account the scaling factor for the first time, and without further manipulations, such as turning the device. Именно поэтому при инициализации данный коэффициент сохраняется для первой манипуляции и сбрасывается до единицы для дальнейших.

It is absolutely possible that in one of the further assemblies of Windows 10 this oddity will be eliminated, and we will need to change this code, but so far it works correctly on all released assemblies up to the Fall Creators Update.

A separate consideration is the size setting for the second image. More precisely, in the case of it, we are not limited only to setting the size, but also to set the position, because initially the blades are in the middle, and not in the mill area.

By the way, it’s enough to set the size only for one of the sides, for example, for the width, since the image is square and proportionally changes the height to the same size. When setting the size, in addition to adjusting for the scaling factor, we also adjust the size based on the main image. More precisely, we take the image width as a basis and calculate what proportion of the blades occupy this size. In my case, it is 0.3073.

Further, based on the same logic, we calculate the distance from the center to the desired point, taking as a basis the width of the entire image. The next line sets the indent left and up to the estimated distance.

We launch the application and see that at this stage the result does not visually differ from the main splash screen. Now we launch the application on the mobile and we see a slight shift of our image relative to the base one. This is caused by the fact that the base screen positions itself in the middle of the screen completely ignoring interface elements such as the status bar at the top of the phone or the bottom virtual button bar, which is present on some models. Let's make the required changes to the code.

First, prepare a variable to store the height of the status bar:

Friend statusBarRect As Rect
После в процедуре инициализации страницы запросим ее размер:
If Metadata.ApiInformation.IsTypePresent("Windows.UI.ViewManagement.StatusBar") Then
       statusBarRect = StatusBar.GetForCurrentView.OccludedRect
End If

This code is interesting in two ways. Firstly, here for the first time we come across adaptive code, which I mentioned in a previous article. The essence of the condition is that we check for the presence of the desired class on the target device, and if there is one, then the code inside the condition will execute without errors.

Despite the adaptive code, accessing the StatusBar will not succeed, and we will receive a message about the absence of such a class. This is due to the fact that UWP applications initially have only common code that can run on all supported devices without any manipulation. Links to classes specific to a particular platform are added separately. The status bar is unique to mobile devices, so first you need to add a link to the desired class library. Go to the menu "Project> Add link ...". We find the section “Universal Windows” and the subsection “Extensions” on the left. In the list of libraries we find the library "Windows Mobile Extensions for the UWP" and select the version equal to the minimum version of our application.

Now the code above works without errors. More precisely, it will only work on mobile, and on all other devices it will be ignored.

Finally, we will correct the positioning and image size setting code:

Private Sub SetSizeImage()
        SplashScreenImage1.Height = splashImageRect.Height / ScaleFactor
        SplashScreenImage1.Width = splashImageRect.Width / ScaleFactor
        SplashScreenImage1.Margin = New Thickness(0, -statusBarRect.Height, 0, 0)

        SplashScreenImage2.Width = splashImageRect.Width / ScaleFactor * 0.3073
        SplashScreenImage2.Margin = New Thickness(-splashImageRect.Width / ScaleFactor * 0.547, -splashImageRect.Width / ScaleFactor * 0.1633 - statusBarRect.Height, 0, 0)

        SplashScreenImage3.Height = splashImageRect.Height / ScaleFactor
        SplashScreenImage3.Width = splashImageRect.Width / ScaleFactor
        SplashScreenImage3.Margin = New Thickness(0, -statusBarRect.Height, 0, 0)

        ScaleFactor = 1
End Sub

The added code shifts each image upward by the size equal to the height of the status bar, and now when the application is launched on any device, our image is in the same place as the base one. Unfortunately, this solution is still not complete. Some phone models do not have hardware buttons, and instead the phone control buttons are located on the virtual panel of the phone itself, which leads to the same shift effect and, as I did not search, I could not find information about where to get the height of this panel, and, in general, to receive information about its presence or absence, in order to make the necessary changes as well. If you have this information, be sure to share it in the comments.

Now that all the necessary adjustments have been made, it remains to add a reaction to window resizing or device orientation:

Private Sub ExtendedSplash_OnResize(sender As Object, e As WindowSizeChangedEventArgs)
        If splash IsNot Nothing Then
            splashImageRect = splash.ImageLocation
            SetSizeImage()
        End If
End Sub

Everything is extremely simple here. We check if the object that stores the SplashScreen class is empty and, if not, we remove the size information from it, and then repeat the SetSizeImage procedure.

With the imitation of the base screen, we figured out. Now we move on to the place where we benefit from this. As I mentioned above, after closing the main splash screen, a corresponding event occurs that brings us to the DismissedEventHandler procedure. This procedure is performed in another thread, so it is not convenient to use it for our tasks. Edit the procedure as follows:

Private Async Sub DismissedEventHandler(sender As SplashScreen, args As Object)
        Await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, New DispatchedHandler(AddressOf DismissExtendedSplash))
End Sub

This line calls a new procedure with the name DismissExtendedSplash in the main thread and already in it we can perform all the tasks for which an extended splash screen was created. Notice that here we use the Async and Await keywords, which are part of asynchronous programming. In a nutshell, it works like this: the Await keyword says that this code will be executed as if in the background, while the main thread is not blocked and the interface remains responsive. Await can only be applied to asynchronous procedures / functions and only if the procedure / function in which we use it is marked Async.

The DismissExtendedSplash procedure we went to will look like this:

Private Async Sub DismissExtendedSplash()
    LoadingSatatusTextBox.Text = "Загрузка данных..."
    Await Task.Delay(New TimeSpan(0, 0, 15))
   
    Dim rootFrame As New Frame
    rootFrame.Navigate(GetType(MainPage))
    Window.Current.Content = rootFrame
End Sub

Its contents have been added for illustrative purposes only. In a full-fledged application, settings will be downloaded here, purchases will be checked, synchronization with the cloud and other long-term initial actions. To simulate these actions, the first two lines are added. One sets the text to display the download status, and the second pauses execution for 15 seconds. Of course, in a good application, even long starting tasks should not take 15 seconds, but in our case this is a simple convention.

After the specified time has elapsed, execution proceeds to where we create a new Frame element, navigate to the main page in it and replace the root content of the application with it. These lines must be beaten at the end of this procedure, so that after performing all lengthy tasks the user would see the main page of the application.
Result: after launching the application, we already see how after a split second the text appears below and after 15 seconds the main page opens.

Step 5. Adding a download indicator


Although we have added text that shows the loading status of the application, the page still remains static most of the time and looks “frozen”. The rotation animation for the mill blades will help to fix the situation.

We modify the XAML pages as follows:

<Grid Background="{StaticResource BackgroundBrush}">
        <Grid.Resources>
            <Storyboard x:Name="ImageRotateAnimation">
                <DoubleAnimation Storyboard.TargetName="ImageRotate" Storyboard.TargetProperty="Angle" To="360" BeginTime="0:0:0.5" Duration="0:0:5" RepeatBehavior="Forever"/>
            </Storyboard>
        </Grid.Resources>
        <Image x:Name="SplashScreenImage3" Source="Assets/SplashScreens/SplashScreen3.png" Canvas.ZIndex="3" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <Image x:Name="SplashScreenImage2" Source="Assets/SplashScreens/SplashScreen2.png" Canvas.ZIndex="2" HorizontalAlignment="Center" VerticalAlignment="Center" RenderTransformOrigin="0.5 0.5">
            <Image.RenderTransform>
                <RotateTransform x:Name="ImageRotate" Angle="0"/>
            </Image.RenderTransform>
        </Image>
        <Image x:Name="SplashScreenImage1" Source="Assets/SplashScreens/SplashScreen1.png" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <TextBlock x:Name="LoadingSatatusTextBox" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,20" TextAlignment="Center" Foreground="{StaticResource ForegroundBrush}" FontWeight="Bold"/>
</Grid>

The changes affected two markup sites. Firstly, a tilt transformation of 0 degrees was added for the second image. In fact, this was done in order to give the transformation a name and to be able to access it by name in the animation.

Secondly, a Storyboard object has been added to the resources of the root element, inside which the Double animation is located. In it, we indicate the name of the object and property with type Double to which the animation will apply. The animation is endless and flows from the start to the set position in five seconds. The theme of animation is quite extensive and very interesting, so it will definitely be touched on in this cycle.

After adding the necessary markup, it remains only to start the animation in the code. To do this, at the very end of the New procedure, add just one line:
That's it, now the user will see not a trivial screen with standard loading indicators, but an indication elegantly built into the logo and accompanying text data.



A complete example can be downloaded from this link .

In the next article I plan to consider the topic of styling the window title and status bar, which, as you can see in the screenshots, I have already stylized, and from you I will be glad to hear comments, questions and suggestions on this and future topics.