有没有办法为 WPF 应用程序显示启动屏幕?

发布于 2024-09-27 06:28:42 字数 215 浏览 4 评论 0原文

欺骗:WPF 动画启动屏幕

我想为我的 WPF 应用程序显示一个启动屏幕。 我想做的是在从文件加载字典时显示它(加载大约需要 5-6 秒)。在WPF中有没有办法实现这一点?我希望得到一些教程,因为这比我发布的其他问题要复杂一些。

Dupe: WPF animated splash screen

I would like to show a splash screen for my WPF application.
what I want to do is to show it while I load dictionary from a file (it takes about 5-6 seconds to load). Is there a way to achieve this in WPF? I would appreciate some tutorial, since this is a little bit more complicated then other questions I posted.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

饭团 2024-10-04 06:28:42

SplashScreen 实际上只是另一个没有边框的窗口,并且它不能调整大小(也不能以任何方式与其交互)。您可能希望将其从任务栏隐藏,将其置于屏幕中央等。尝试各种设置,直到获得所需的效果。

这是我在大约 5 分钟内快速完成的一个事件,以证明这一理论:

<Window x:Class="MyWhateverApp.MySplashScreen"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        ShowInTaskbar="False" 
        ResizeMode="NoResize" 
        WindowStartupLocation="CenterScreen"
        WindowStyle="None" 
        Background="Transparent" 
        AllowsTransparency="True"
        Title="Sandbox Splash Screen" 
        SizeToContent="Width" 
        Topmost="True" 
        Height="{Binding RelativeSource={RelativeSource Self}, 
                         Path=ActualWidth}">

    <Border CornerRadius="8" Margin="15">
        <Border.Background>
            <ImageBrush ImageSource="Resources\sandtexture.jpeg" 
                        Stretch="Fill" />
        </Border.Background>
        <Border.Effect>
            <DropShadowEffect Color="#894F3B" 
                              BlurRadius="10" 
                              Opacity="0.75" 
                              ShadowDepth="15" />
        </Border.Effect>

        <TextBlock FontSize="40"
                   FontFamily="Bauhaus 93"
                   Foreground="White"
                   Margin="10"
                   VerticalAlignment="Center"
                   HorizontalAlignment="Center"
                   Text="WPF 3.5 Sandbox">
            <TextBlock.Effect>
                <DropShadowEffect Color="Black" />
            </TextBlock.Effect>
        </TextBlock>        
    </Border>
</Window>

接下来,修改您的 App.xaml 文件以删除启动窗口,并引发 Startup 事件:

<Application x:Class="MyWhateverApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             Startup="Application_Startup">
    <Application.Resources>

    </Application.Resources>
</Application>

在代码隐藏中,以任何方式处理 Application_Startup 事件你认为最好的方式。例如:

Window1 mainWindow = null;

private void Application_Startup(object sender, StartupEventArgs e)
{
    MySplashScreen splash = new MySplashScreen();
    splash.Show();
    mainWindow = new Window1();
    mainWindow.Show();
    splash.Close();
}

A SplashScreen is really just another Window with no border, and it is not resizable (nor can you interact with it in any way). You'd probably want to hide it from the task bar, center it on the screen, etc. Play around with various settings until you get the effect that you want.

Here's a quick one I whipped up in about 5 minutes to prove the theory:

<Window x:Class="MyWhateverApp.MySplashScreen"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        ShowInTaskbar="False" 
        ResizeMode="NoResize" 
        WindowStartupLocation="CenterScreen"
        WindowStyle="None" 
        Background="Transparent" 
        AllowsTransparency="True"
        Title="Sandbox Splash Screen" 
        SizeToContent="Width" 
        Topmost="True" 
        Height="{Binding RelativeSource={RelativeSource Self}, 
                         Path=ActualWidth}">

    <Border CornerRadius="8" Margin="15">
        <Border.Background>
            <ImageBrush ImageSource="Resources\sandtexture.jpeg" 
                        Stretch="Fill" />
        </Border.Background>
        <Border.Effect>
            <DropShadowEffect Color="#894F3B" 
                              BlurRadius="10" 
                              Opacity="0.75" 
                              ShadowDepth="15" />
        </Border.Effect>

        <TextBlock FontSize="40"
                   FontFamily="Bauhaus 93"
                   Foreground="White"
                   Margin="10"
                   VerticalAlignment="Center"
                   HorizontalAlignment="Center"
                   Text="WPF 3.5 Sandbox">
            <TextBlock.Effect>
                <DropShadowEffect Color="Black" />
            </TextBlock.Effect>
        </TextBlock>        
    </Border>
</Window>

Next, modify your App.xaml file to remove the startup window, and instead raise the Startup event:

<Application x:Class="MyWhateverApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             Startup="Application_Startup">
    <Application.Resources>

    </Application.Resources>
</Application>

And in the code-behind, handle the Application_Startup event in whatever way you think is best. For example:

Window1 mainWindow = null;

private void Application_Startup(object sender, StartupEventArgs e)
{
    MySplashScreen splash = new MySplashScreen();
    splash.Show();
    mainWindow = new Window1();
    mainWindow.Show();
    splash.Close();
}
几味少女 2024-10-04 06:28:42

请参阅WPF 3.5 SP1:启动屏幕

或者在 VS2010 中单击 解决方案资源管理器执行添加-> New Item,从已安装模板列表中选择WPFSplash Screen应该位于列表底部的中间。

注意:启动屏幕在构造函数之后和主窗口 Window_Loaded 回调之前/时被删除。我将所有初始化移至主窗口构造函数中,它非常有用,而且非常简单。

See WPF 3.5 SP1: Splash Screen

Or within VS2010 click on the Solution Explorer do Add -> New Item, select WPF from the list of installed templates and Splash Screen should be on the bottom of the list in the middle.

Note: The splash screen is removed after the constructor and before/when the main window Window_Loaded callback. I moved all of my initialisation into the main window constructor and it works a treat, and is very easy.

依 靠 2024-10-04 06:28:42

简短回答:添加->新项目 ->启动画面。它在项目中转储 PNG - 只需修改它即可。请注意,它支持完整的 alpha 透明度,因此可以包含投影等...

Short answer: Add -> New Item -> Splash Screen. It dumps a PNG in the project - just modify this. note that it supports full alpha transparencey so can contain drop-shadows, etc...

2024-10-04 06:28:42

将.png图片放入app目录,并将属性编译动作设置为SplashScreen

Place a .png image into the app directory and set the property compilation action as SplashScreen

友谊不毕业 2024-10-04 06:28:42

这是我对 2010 问题的 2024 看法。


对于这种目标是在主窗口之前显示动态启动画面(不仅仅是 .png)的场景,WPF 和 WinForms 的根本问题是相同的,所以我将移植一个早期的答案并使其适用于WPF。如果您愿意,可以克隆此 WPF 解决方案并从我的 GitHub 存储库尝试它看看这是否是您想要的行为,如果是,我将从如何开始,然后解释为什么 - 我这样做的理由这种方式。

之前启动主应用程序窗口


如果您有类似于下面所示的 Splash 窗口类,其中现在可以使用可等待的 public new async Task Show() 方法调用来代替正常的 Show() 方法,那么您需要在 MainWindow 中执行的所有操作都在 Loaded 事件处理程序中,位于 < code>MainWindow 构造函数。

public MainWindow()
{
    // Minimize the window
    WindowState = WindowState.Minimized;
    // Hide TaskBar icon so there's no temptation to un-minimizing it until we say so! 
    ShowInTaskbar = false;
    // This might be old habits, but I always feel better with a small visual footprint 
    // in case there's spurious flicker when the window creation occurs.
    Width = 0;
    Height = 0;
    WindowStyle = WindowStyle.None;


    InitializeComponent();
    Loaded += async(sender, e) =>
    {
        // NOW the native hWnd exists AND it's the first hWnd to come
        // into existence, making this class (MainWindow) the "official"
        // application main window. This means that when it closes, the
        // app will exit as long as we make our other window handles "behave".

        var splash = new Splash();
        await splash.Show();

        WindowState = WindowState.Normal;
        // Set the dimensions that you actually want here, and turn the TaskBar icon back on.
        Width = 500;
        Height = 300;
        ShowInTaskbar = true;
        localCenterToScreen();
        // ^^^^ Do all this BEFORE closing the splash. It's a smoke-and-
        //      mirrors trick that hides some of the ugly transient draws.
        splash.Close();

        #region L o c a l M e t h o d s
        void localCenterToScreen()
        {
            double screenWidth = SystemParameters.PrimaryScreenWidth;
            double screenHeight = SystemParameters.PrimaryScreenHeight;
            double left = (screenWidth - Width) / 2;
            double top = (screenHeight - Height) / 2;
            Left = left;
            Top = top;
        }
        #endregion L o c a l M e t h o d s
    };
}

启动画面模拟 10 状态应用程序启动

Xaml
<Window x:Class="wpf_splash.Splash"
        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"
        xmlns:local="clr-namespace:wpf_splash"
        mc:Ignorable="d"
        Title="Splash"
        Height="300" 
        Width="460"
        WindowStyle="None"
        WindowStartupLocation="CenterScreen"
        ShowInTaskbar="False">
    <Grid>
        <Image Source="pack://application:,,,/Resources/Splash.png" Stretch="Fill" />
        <StackPanel VerticalAlignment="Bottom" Background="#AAFFFFFF" >
            <!-- State Progress Grid -->
            <Grid Margin="0,0,0,5">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Label Content="{Binding CurrentState}"  VerticalAlignment="Center" Grid.Column="0"/>
                <ProgressBar x:Name="StateProgressBar" Maximum="100" Height="20" Margin="5,0,0,0" Grid.Column="1" />
            </Grid>
            <!-- Overall Progress Grid -->
            <Grid Margin="0,5,0,0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Label Content="Total Progress:" FontWeight="Bold" VerticalAlignment="Center" Grid.Column="0"/>
                <ProgressBar x:Name="OverallProgressBar" Maximum="100" Height="20" Margin="5,0,0,0" Grid.Column="1"/>
            </Grid>
        </StackPanel>
    </Grid>
</Window>
代码背后
public enum StartupState
{
    InitializingComponents, LoadingConfiguration, CheckingForUpdates, ContactingServer, AuthenticatingUser, 
    LoadingResources, LoadingAvatars, EstablishingConnections, PreparingUserInterface, FinalizingSetup
}
public partial class Splash : Window, INotifyPropertyChanged
{
    public Splash()
    {
        InitializeComponent();
        DataContext = this;
    }
    public new async Task Show()
    {
        base.Show();
        StartupState[] states = Enum.GetValues<StartupState>();
        var stopwatch = new Stopwatch();
        TimeSpan randoTimeSpan = TimeSpan.MaxValue;
        Task taskSpinState;

        var progress = new Progress<double>(percent =>
        {
            StateProgressBar.Value = percent;
        });
        bool isCancelled = false;
        taskSpinState = Task.Run(() =>
        {
            while(!isCancelled)
            {
                ((IProgress<double>)progress).Report(100 * (stopwatch.ElapsedMilliseconds / (double)randoTimeSpan.TotalMilliseconds));
                Thread.Sleep(_rando.Next(100, 200));
            }
        });

        for (int i = 0; i < states.Length; i++)
        {
            CurrentState = states[i].ToString().CamelCaseToSpaces();
            stopwatch.Restart();
            randoTimeSpan = TimeSpan.FromSeconds(0.5 + (_rando.NextDouble() * 2d));  
            await Task.Delay(randoTimeSpan);
            OverallProgressBar.Value = 0.5 + 100 * (i / (double)states.Length);
        }
        OverallProgressBar.Value = 100;
        isCancelled = true;
        await taskSpinState;
    }
    Random _rando = new Random(Seed:1);
    public string CurrentState
    {
        get => _currentState;
        set
        {
            if (!Equals(_currentState, value))
            {
                _currentState = value;
                OnPropertyChanged();
            }
        }
    }
    string _currentState = string.Empty;
    protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    public event PropertyChangedEventHandler? PropertyChanged;
}
static partial class Extensions
{
    public static string CamelCaseToSpaces(this string @string)
    {
        string pattern = "(?<![A-Z])([A-Z][a-z]|(?<=[a-z])[A-Z])";
        string replacement = " $1";
        return Regex.Replace(@string, pattern, replacement).Trim();
    }
}

的操作理论

确实可能有其他方法可以实现这一点,但根据我的理解,这是这种方法的基本原理。

当启动屏幕在主窗口之前创建并且是第一个创建本机 HWND 时,它可能会无意中成为应用程序上下文中的主窗口。显然,这可能会让事情发生翻天覆地的变化,无论是在启动时,因为您现在试图关闭一个主窗口伪装者(飞溅),同时保持打开一个意外的子窗口(主窗口),而且在关闭时,现在很容易因以下原因而挂起奇怪的是未处理的手柄。

因此,我的 WPF 解决方案和 WinForms 解决方案都通过确保创建 win 32 本机 HWND(WPF 中的 Window 和 WinForms 中的 Control 中抽象的东西)来工作。第一的。这一切都很好,但问题是创建 HWND 通常是使其可见的结果,而我们不希望主窗体可见。显然,我们想要的是让飞溅可见,并且看不到一堆闪烁或伪像,例如,如果我们尝试先显示然后隐藏主窗口来解决这个问题。

WPF 的具体挑战是(据我所知)您无法像在 WinForms 中那样通过在 Control< 中调用 varforceCreate = Handle 来创建带外窗口句柄。 /code> 或 表单。相反,我采取的方法是创建最小化的主窗口,暂时隐藏可用于取消最小化的任务栏图标,然后等待主窗口触发其 Loading 事件(该事件是 HWND 现在存在的明确标志)。

所有这些都是为了说明:我的经验(比我想的更多)是,这是一种可靠的闪屏方式,但它没有额外的保修。

Here's my 2024 take on a 2010 question.


For this scenario where the goal is to display a dynamic (more than just a .png) splash screen that is shown before the main window, the underlying issue is the same for WPF as it is for WinForms, so I'm going to port an earlier answer and make it work for WPF. If you like, you can clone this WPF solution and try it from my GitHub repo to see if this looks like the kind of behavior you want, and if so I'll start with the how, and then explain the why - my rationale for going about it in this manner.

splash before main app window


If you have something similar to the Splash window class shown below, where an awaitable public new async Task Show() method can now be called in place of the normal Show() method, then everything you need to do in MainWindow is in the Loaded event handler here in the MainWindow constructor.

public MainWindow()
{
    // Minimize the window
    WindowState = WindowState.Minimized;
    // Hide TaskBar icon so there's no temptation to un-minimizing it until we say so! 
    ShowInTaskbar = false;
    // This might be old habits, but I always feel better with a small visual footprint 
    // in case there's spurious flicker when the window creation occurs.
    Width = 0;
    Height = 0;
    WindowStyle = WindowStyle.None;


    InitializeComponent();
    Loaded += async(sender, e) =>
    {
        // NOW the native hWnd exists AND it's the first hWnd to come
        // into existence, making this class (MainWindow) the "official"
        // application main window. This means that when it closes, the
        // app will exit as long as we make our other window handles "behave".

        var splash = new Splash();
        await splash.Show();

        WindowState = WindowState.Normal;
        // Set the dimensions that you actually want here, and turn the TaskBar icon back on.
        Width = 500;
        Height = 300;
        ShowInTaskbar = true;
        localCenterToScreen();
        // ^^^^ Do all this BEFORE closing the splash. It's a smoke-and-
        //      mirrors trick that hides some of the ugly transient draws.
        splash.Close();

        #region L o c a l M e t h o d s
        void localCenterToScreen()
        {
            double screenWidth = SystemParameters.PrimaryScreenWidth;
            double screenHeight = SystemParameters.PrimaryScreenHeight;
            double left = (screenWidth - Width) / 2;
            double top = (screenHeight - Height) / 2;
            Left = left;
            Top = top;
        }
        #endregion L o c a l M e t h o d s
    };
}

Splash Screen Simulates a 10-state Application Boot

Xaml
<Window x:Class="wpf_splash.Splash"
        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"
        xmlns:local="clr-namespace:wpf_splash"
        mc:Ignorable="d"
        Title="Splash"
        Height="300" 
        Width="460"
        WindowStyle="None"
        WindowStartupLocation="CenterScreen"
        ShowInTaskbar="False">
    <Grid>
        <Image Source="pack://application:,,,/Resources/Splash.png" Stretch="Fill" />
        <StackPanel VerticalAlignment="Bottom" Background="#AAFFFFFF" >
            <!-- State Progress Grid -->
            <Grid Margin="0,0,0,5">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Label Content="{Binding CurrentState}"  VerticalAlignment="Center" Grid.Column="0"/>
                <ProgressBar x:Name="StateProgressBar" Maximum="100" Height="20" Margin="5,0,0,0" Grid.Column="1" />
            </Grid>
            <!-- Overall Progress Grid -->
            <Grid Margin="0,5,0,0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Label Content="Total Progress:" FontWeight="Bold" VerticalAlignment="Center" Grid.Column="0"/>
                <ProgressBar x:Name="OverallProgressBar" Maximum="100" Height="20" Margin="5,0,0,0" Grid.Column="1"/>
            </Grid>
        </StackPanel>
    </Grid>
</Window>
Code behind
public enum StartupState
{
    InitializingComponents, LoadingConfiguration, CheckingForUpdates, ContactingServer, AuthenticatingUser, 
    LoadingResources, LoadingAvatars, EstablishingConnections, PreparingUserInterface, FinalizingSetup
}
public partial class Splash : Window, INotifyPropertyChanged
{
    public Splash()
    {
        InitializeComponent();
        DataContext = this;
    }
    public new async Task Show()
    {
        base.Show();
        StartupState[] states = Enum.GetValues<StartupState>();
        var stopwatch = new Stopwatch();
        TimeSpan randoTimeSpan = TimeSpan.MaxValue;
        Task taskSpinState;

        var progress = new Progress<double>(percent =>
        {
            StateProgressBar.Value = percent;
        });
        bool isCancelled = false;
        taskSpinState = Task.Run(() =>
        {
            while(!isCancelled)
            {
                ((IProgress<double>)progress).Report(100 * (stopwatch.ElapsedMilliseconds / (double)randoTimeSpan.TotalMilliseconds));
                Thread.Sleep(_rando.Next(100, 200));
            }
        });

        for (int i = 0; i < states.Length; i++)
        {
            CurrentState = states[i].ToString().CamelCaseToSpaces();
            stopwatch.Restart();
            randoTimeSpan = TimeSpan.FromSeconds(0.5 + (_rando.NextDouble() * 2d));  
            await Task.Delay(randoTimeSpan);
            OverallProgressBar.Value = 0.5 + 100 * (i / (double)states.Length);
        }
        OverallProgressBar.Value = 100;
        isCancelled = true;
        await taskSpinState;
    }
    Random _rando = new Random(Seed:1);
    public string CurrentState
    {
        get => _currentState;
        set
        {
            if (!Equals(_currentState, value))
            {
                _currentState = value;
                OnPropertyChanged();
            }
        }
    }
    string _currentState = string.Empty;
    protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    public event PropertyChangedEventHandler? PropertyChanged;
}
static partial class Extensions
{
    public static string CamelCaseToSpaces(this string @string)
    {
        string pattern = "(?<![A-Z])([A-Z][a-z]|(?<=[a-z])[A-Z])";
        string replacement = " $1";
        return Regex.Replace(@string, pattern, replacement).Trim();
    }
}

Theory of Operation

There may indeed be other ways to go about it, but here's the rationale for this approach based on my understanding.

When a splash screen is created before the main window and is the first to create the native HWND, it may inadvertently become the main window in the application's context. Obviously this can turn things on their head, both at startup because you're now trying to close a main window pretender (the splash) while keeping open an unintended child window (main window), and also at shutdown now prone to hangs due to weirdly undisposed handles.

So, both my WPF solution and my WinForms solution work by making sure the win 32 native HWND (the thing that is abstracted in Window in WPF and Control in WinForms) gets created FIRST. Which is all well and good, but the problem is that creation of the HWND is usually a consequence of making it visible, and we don't want the main form visible. What we want, obviously, is for the splash to be visible, and to not see a bunch of flickering or artifacts, for example if we try and show-then-hide the main window in an effort to get around this.

The specific challenge of WPF is that (to my knowledge) you can't create the window handle out of band the same way you can in WinForms by calling var forceCreate = Handle in your Control or Form. Instead, I take the approach of creating the main window minimized, temporarily hiding the task bar icon that could be used to un-minimize it, and then waiting for the main window to fire its Loading event (which is a sure sign that the HWND now exists).

All this to say: my experience (more than I care to think about) is that this is a reliable way to do splash screens, but it comes with no additional warranty.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文