Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Splash Screen #2674

Closed
colinparks opened this issue Jun 20, 2019 · 18 comments
Closed

Splash Screen #2674

colinparks opened this issue Jun 20, 2019 · 18 comments

Comments

@colinparks
Copy link

I would like my application to open with a splash screen of a .ico or image file for a few seconds, then open to my MainWindow. Is there a way to do this yet, perhaps similar to how WPF handles splash screens? WPF and WinForms handles this by setting Build Action in an images properties to Splash Screen, but this option doesn't seem to be present in Avalonia.

My other idea is to create a separate window with the image that would, when the application is run, open for a certain amount of time before closing and opening to MainWindow. I would prefer to use a simpler method like WPF though.

@Gillibald
Copy link
Contributor

I never used the build in wpf splash screen you mention. Didn't know that exists. I usually show a dedicated window that I can send messages to from several threads to display current status etc. When loading is finished I close the splash and show the main window.

That's probably the cleanest and most reusable way.

@colinparks
Copy link
Author

I thought that may end up being the solution I'd have to use. How would you change it from opening to the MainWindow to the new dedicated window for the splash? I just need it to open the splash window and then close after a few seconds and open MainWindow, what would be the best way of accomplishing this?

@Gillibald
Copy link
Contributor

Gillibald commented Jun 20, 2019

Just start the app without an MainWindow AppBuilder.Start(). You should be able to override Application.OnStartup. First create your Splash and show it. Then do whatever you need to load etc before showing the MainWindow. When loading is finished close your Splash and show your MainWindow. Don't forget to assign your MainWindow to Application.MainWindow

This is only working with recent nightly build and will probably change before Avalonia hits 1.0

@maxkatz6
Copy link
Member

Just for information, UWP shows splash screen in main window until Window.Current.Activate(); is not called and main page is ready. It could be useful for Android and iOS applications. For desktop it seems just as an alternative for splash screen in separate window.

But it still can be implemented manually and is not blocked by Avalonia.

@JaggerJo
Copy link
Contributor

I think this will be handy on mobil plattforms

@Gillibald
Copy link
Contributor

Mobile has no concept of a window

@kekekeks
Copy link
Member

On mobile platforms splash screens should be shown before initializing Avalonia, since that is one of the expensive parts of UI initialization. On desktop you can just create a window without decorations, something like this:

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="MyApp.SplashWindow"
        Title="Loading MyApp" Width="300" Height="300"
        WindowStartupLocation="CenterScreen" HasSystemDecorations="False" CanResize="False">
    <TextBlock TextAlignment="Center" Text="{Binding StartupProgressText}"/>
</Window>

@colinparks
Copy link
Author

I created a splash window as you have @kekekeks. In the Program.cs file, under the AppMain method I have put in the code:

Splash Splash = new Splash();
Splash.Show();
Thread.Sleep(3000);
Splash.Close();

app.Run(new MainWindow());

But this just waits 3 seconds and opens only my Main Window. If I remove the Splash.Close(); it waits 3 seconds then opens both my Splash window and Main Window. Should this be a valid way of opening the splash window? The other option I considered was the use of timers.

@kekekeks
Copy link
Member

kekekeks commented Jun 21, 2019

Window needs a running main loop to function properly. You can use a custom cancellation token for app.Run and do the rest of initialization like this:

private static void AppMain(Application app, string[] args)
{
  var stop = new CancellationTokenSource();

  async void Start()
  {
      await Task.Delay(10);
      var splashViewModel = new SplashViewModel();
      var splash = new SplashWindow {DataContext = splashViewModel};
      splash.Show();
      app.MainWindow = await GetMainWindowAsync(splashViewModel);
      app.MainWindow.Closed += (_, __) => stop.Cancel();
      app.MainWindow.Show();
      app.MainWindow.Activate();
      splash.Close();
  }
  Start();
  app.Run(stop.Token);
}

static async Task<MainWindow> GetMainWindowAsync(SplashViewModel splashViewModel)
{
     // Initialize here
     return new MainWindow();
}

@Gillibald
Copy link
Contributor

Overriding OnStartup should be easier. This scenario is one reason why I implemented it.

@kekekeks
Copy link
Member

kekekeks commented Jun 21, 2019

It will be easier, no doubt, but will not work for the same reason that @colinparks's code doesn't: you can not have synchronous initialization.

@kekekeks
Copy link
Member

kekekeks commented Jun 21, 2019

@Gillibald if you have new ideas regarding the lifetime, please, write them down in #2564 tracking issue to keep everything in one place

@colinparks
Copy link
Author

@kekekeks The code worked perfectly. I created a ViewModel for my SplashWindow because I didn't have one before (my SplashWindow is just an image with no data bindings) and added a await Task.Delay(3000) in the // Initialize here to show the SplashWindow for 3 seconds. Thank you for your help everyone.

@mysteryx93
Copy link

@kekekeks if I initialize it that way, what happens to the standard initialization code, and where does your code go?

public static void Start<TApp>(string[] args) 
    where TApp : Application, new()
    => BuildAvaloniaApp<TApp>()
    .StartWithClassicDesktopLifetime(args);

// Avalonia configuration, don't remove; also used by visual designer.
private static AppBuilder BuildAvaloniaApp<TApp>()
    where TApp : Application, new()
    => AppBuilder.Configure<TApp>()
        .UsePlatformDetect()
        .LogToTrace()
        .UseReactiveUI();

@kekekeks
Copy link
Member

That snippet was written before application lifetimes were a thing. With the latest version you can declare OnFrameworkInitializationCompleted as async void and call your GetMainWindowAsync from there. Just make sure to switch the main window in IClassicDesktopApplicationLifetime when you hide your splash screen.

@mysteryx93
Copy link

OK. However, in WPF, I'd start the splash screen early in a separate thread while the rest loads, but in Avalonia, by the time OnFrameworkInitializationCompleted is called, it takes 2 seconds, and then both the splash and main window appear at the same time. Kind of defeating the purpose unless it's a larger application with more initialization to do.

@maxkatz6
Copy link
Member

I'd start the splash screen early in a separate thread while the rest loads

You can try it in Avalonia. But it won't work on macOS. Its windows must be on the main thread.

OnFrameworkInitializationCompleted is called, it takes 2 seconds

Something is wrong with your application. It shouldn't take so much time in release build with r2r enabled.

unless it's a larger application with more initialization to do

That's basically the only one reasonable case of having splash screen. Why would you have it if you don't need to heavy initialization before opening first window?

@llfab
Copy link

llfab commented Apr 20, 2023

See here for a sample app: #11083

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants