简体   繁体   English

带进度条的 WPF 启动画面

[英]WPF SplashScreen with ProgressBar

I have a WPF project, with a splash screen added by the Project Wizard.我有一个 WPF 项目,项目向导添加了一个启动画面。 On the same splash screen, I want to add a progress bar style meter.在同一个启动画面上,我想添加一个进度条样式表。 Does someone have any idea how to do this?有人知道如何做到这一点吗?

Here is my scheme for doing this.这是我这样做的方案。 My motivation for doing it this way is that I don't want to have the initialization code running on the UI Thread, and normally I want the initialization code on my App class (not a Splash screen).我这样做的动机是我不想让初始化代码在 UI 线程上运行,通常我希望在我的 App 类(而不是启动画面)上运行初始化代码。

Basically, I set the App StartupUri to my splash screen, which gets the ball rolling.基本上,我将 App StartupUri设置为我的启动画面,这让球滚动起来。

On the splash screen, I invoke a delegate back on the application.在初始屏幕上,我在应用程序上调用了一个委托。 This is run on a worker thread.这是在工作线程上运行的。 In the splash screen, I handle the EndInvoke , and close the Window.在启动画面中,我处理EndInvoke并关闭窗口。

In the application initialization delegate, I do the work, and at the end, create and open the normal main Window.在应用程序初始化委托中,我完成了工作,最后,创建并打开了正常的主窗口。 During the work load, I also have a method on Slash that allows me to update progress.在工作负载期间,我还有一个关于 Slash 的方法,可以让我更新进度。

OK,the code is fairly short, and doesn't include the main window code (which is unaffected by all this), but it ducks and dives with anonymous delegates, so read it carefully, and ideally play with it in the debugger.好的,代码相当短,并且不包括主窗口代码(不受所有这些影响),但是它与匿名代表一起躲避和潜水,因此请仔细阅读,最好在调试器中使用它。

Here is the code....这是代码....

<Application x:Class="SplashScreenDemo.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Splash.xaml">
    <Application.Resources>

    </Application.Resources>
</Application>

app code behind...应用程序代码背后...

internal delegate void Invoker();
public partial class App : Application
{
    public App()
    {
        ApplicationInitialize = _applicationInitialize;
    }
    public static new App Current
    {
        get { return Application.Current as App; }
    }
    internal delegate void ApplicationInitializeDelegate(Splash splashWindow);
    internal ApplicationInitializeDelegate ApplicationInitialize;
    private void _applicationInitialize(Splash splashWindow)
    {
        // fake workload, but with progress updates.
        Thread.Sleep(500);
        splashWindow.SetProgress(0.2);

        Thread.Sleep(500);
        splashWindow.SetProgress(0.4);

        Thread.Sleep(500);
        splashWindow.SetProgress(0.6);

        Thread.Sleep(500);
        splashWindow.SetProgress(0.8);

        Thread.Sleep(500);
        splashWindow.SetProgress(1);

        // Create the main window, but on the UI thread.
        Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Invoker)delegate
        {
            MainWindow = new Window1();
            MainWindow.Show();
        });           
    }
}

splash xaml (actually, nothing too interesting here...)飞溅xaml (实际上,这里没什么有趣的......)

<Window x:Class="SplashScreenDemo.Splash"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Splash" Height="300" Width="300">
    <Grid>
        <TextBlock Height="21" Margin="91,61,108,0" VerticalAlignment="Top">Splash Screen</TextBlock>
        <ProgressBar Name="progBar" Margin="22,122,16,109" Minimum="0" Maximum="1"/>
    </Grid>
</Window>

splash code-behind...飞溅代码隐藏...

public partial class Splash : Window
{
    public Splash()
    {
        InitializeComponent();
        this.Loaded += new RoutedEventHandler(Splash_Loaded);
    }

    void Splash_Loaded(object sender, RoutedEventArgs e)
    {
        IAsyncResult result = null;

        // This is an anonymous delegate that will be called when the initialization has COMPLETED
        AsyncCallback initCompleted = delegate(IAsyncResult ar)
        {
            App.Current.ApplicationInitialize.EndInvoke(result);

            // Ensure we call close on the UI Thread.
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Invoker)delegate { Close(); });
        };

        // This starts the initialization process on the Application
        result = App.Current.ApplicationInitialize.BeginInvoke(this, initCompleted, null);
    }

    public void SetProgress(double progress)
    {
        // Ensure we update on the UI Thread.
        Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Invoker)delegate { progBar.Value = progress; });           
    }
}

As the work is done on a worker thread, the progress bar will update nicely, and any animation you have on the splash screen will keep the entertainment rolling.由于工作是在工作线程上完成的,进度条会很好地更新,并且您在启动屏幕上的任何动画都将保持娱乐滚动。

I used .NET Core and when there was a new Thread Application.OnExit() was fired.我使用了 .NET Core,当有一个新的 Thread Application.OnExit()被触发时。 So the only way how I managed to make it work is:所以我设法让它工作的唯一方法是:

Application.cs:应用程序.cs:

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);
    _mySplashScreenWindow.Show();


    Task.Run(async () =>
    {
        for (var i = 1; i <= 20; i++)
        {
            _mySplashScreenWindow.Dispatcher.Invoke(() =>
            {
                _mySplashScreenWindow.SomeTextBlock.Text = i.ToString();
                _mySplashScreenWindow.Progress = i; // or i / 20 for %
            });
            await Task.Delay(250);
        }
    })
    .ContinueWith(_ =>
    {
        MainWindow = null;

        var mainWindow = new MainWindow();
        // Or if you have access to the SplashScreen in the MainWindow, you can subscribe there
        mainWindow.Loaded += (sender, args) =>
        {
            _mySplashScreenWindow.Close();
            _mySplashScreenWindow = null;
        }

        MainWindow = mainWindow;
        mainWindow.ShowDialog();
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

The answer by Ali doesn't work on .NET core.阿里的答案不适用于 .NET 核心。 However, there is a workaround and it is, in my opinion, more straightforward.但是,有一种解决方法,在我看来,它更直接。 I believe it works on all "recent" versions of .NET (probably since Task.Run was added).我相信它适用于所有“最近”版本的 .NET(可能是因为添加了 Task.Run)。

I used a similar approach, with another Window to use as a splash screen.我使用了类似的方法,将另一个 Window 用作启动画面。

My App.xaml uses the Startup event instead of StartupUri.我的 App.xaml 使用 Startup 事件而不是 StartupUri。 I believe this doesn't make any difference, though.不过,我相信这没有任何区别。

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

I do not declare a new delegate type for the initialization.我没有为初始化声明一个新的委托类型。 Instead, i simply use a method.相反,我只是使用一种方法。 Creating and displaying the main window could take some time, so i think it's better to only return when the window is done loading, by tuning in to its Loaded event.创建和显示主窗口可能需要一些时间,所以我认为最好只在窗口加载完成后返回,通过调整到它的 Loaded 事件。 We use an EventWaitHandle for that.为此,我们使用 EventWaitHandle。

public partial class App : Application
{
    public App()
    {
    }
    
    public static new App Current { get => Application.Current as App; }

    private void OnStartup(object sender, StartupEventArgs e)
    {
        var splashScreen = new SplashScreenDemo.Splash();
        splashScreen.Show();
    }

    internal void InitializeApplication(Splash splashWindow)
    {
        // fake workload, but with progress updates.
        Thread.Sleep(500);
        splashWindow.SetProgress(0.25);

        Thread.Sleep(500);
        splashWindow.SetProgress(0.5);

        Thread.Sleep(500);
        splashWindow.SetProgress(0.75);

        EventWaitHandle mainWindowLoaded = new ManualResetEvent(false);

        // Create the main window, but on the UI thread.
        Dispatcher.BeginInvoke((Action)(() =>
        {
            MainWindow = new Window1();
            MainWindow.Loaded += (sender, e) =>
            {
                mainWindowLoaded.Set();
            };
            splashWindow.SetProgress(0.9);
            MainWindow.Show();
            splashWindow.SetProgress(1);
        }));
        
        // Wait until the Main window has finished initializing and loading
        mainWindowLoaded.WaitOne();        
    }
}

The splash screen window could use some window attributes :启动画面窗口可以使用一些窗口属性:

  • WindowStartupLocation="CenterScreen" self explanatory WindowStartupLocation="CenterScreen" 不言自明
  • WindowStyle="None" to avoid having a title bar WindowStyle="None" 以避免出现标题栏
  • ResizeMode="NoResize" to prevent the user from moving or resizing it ResizeMode="NoResize" 以防止用户移动或调整其大小
  • Topmost="True" to make it always appear on top of other windows Topmost="True" 使其始终显示在其他窗口的顶部
<Window x:Class="SplashScreenDemo.Splash"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Splash" Height="300" Width="300" WindowStartupLocation="CenterScreen"
    WindowStyle="None" ResizeMode="NoResize" Topmost="True">
    <Grid>
        <TextBlock Height="21" VerticalAlignment="Top" TextAlignment="Center" Text="Loading"/>
        <ProgressBar Name="progBar" Margin="20,100" Minimum="0" Maximum="1"/>
    </Grid>
</Window>

The splash screen's code becomes easier to understand, we simply call the init method asynchronously.启动画面的代码变得更容易理解,我们只需异步调用 init 方法。 We could also set focus to the main window before closing the splash screen, in some applications.在某些应用程序中,我们还可以在关闭启动画面之前将焦点设置到主窗口。

public partial class Splash : Window
{
    public Splash()
    {
        InitializeComponent();
        this.Loaded += Splash_Loaded;
    }

    private async void Splash_Loaded(object sender, RoutedEventArgs e)
    {
        await Task.Run(() =>
        {
            App.Current.InitializeApplication(this);
            App.Current.Dispatcher.BeginInvoke((Action)(() =>
            {
                Close();
            }));
        });
    }

    public void SetProgress(double progress)
    {
        Dispatcher.BeginInvoke((Action)(() => progBar.Value = progress));           
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM