WPF进度条启动屏幕

7

我有一个WPF项目,由项目向导添加了一个启动屏幕。在同一个启动屏幕中,我想添加一个进度条样式的计量器。有人知道如何实现吗?

3个回答

12

这是我的方案。我之所以这样做的动机是,我不想让初始化代码在UI线程上运行,并且通常我希望将初始化代码放在我的App类中(而不是启动屏幕)。

基本上,我将应用程序的StartupUri设置为我的启动屏幕,从而开始推动进程。

在启动屏幕上,我调用委托返回到应用程序。这在一个工作线程上运行。在启动屏幕上,我处理EndInvoke并关闭窗口。

在应用程序初始化委托中,我完成工作,并在最后创建和打开正常的主窗口。在工作负载期间,我还有一个在Splash上允许更新进度的方法。

好的,代码相当简短,不包括主窗口代码(对所有这些都没有影响),但它使用匿名委托来躲闪,因此请仔细阅读,最好在调试器中测试。

以下是代码....

<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>

应用程序代码后台...

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();
        });           
    }
}

闪屏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>

闪屏代码后端...

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; });           
    }
}

由于工作是在工作线程上完成的,进度条将会很好地更新,并且您在启动画面上拥有的任何动画都将保持娱乐性。


那么你初始化的所有内容都存储在全局变量中吗? - Vladimir Kocjancic
2
(Invoker) 对我没用,但将其替换为 "new Action(delegate()" 就可以了。 - Dave Friedel
4
一个以下划线开头的方法声明……真的吗? - Slugart
创建真实工作量的问题是什么?我们不能有一个显示真实进度的进度条吗?我的意思是,如果应用程序加载了20%,进度条应该显示20%,然后对于50%的加载,它应该显示50%,依此类推。为什么我们不能有这样的东西呢? - Somanna

0

Ali的答案在.NET Core上不起作用。然而,有一个解决方法,我认为更加直接。我相信它适用于所有“最近”的.NET版本(可能自Task.Run被添加以来)。

我使用了类似的方法,使用另一个窗口作为启动画面。

我的App.xaml使用Startup事件而不是StartupUri。我相信这没有任何区别,但是。

<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>

我不为初始化声明新的委托类型,而是直接使用一个方法。 创建和显示主窗口可能需要一些时间,因此我认为最好只在窗口加载完成时返回,通过调整其Loaded事件。我们使用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();        
    }
}

启动画面窗口可以使用一些窗口属性:

  • WindowStartupLocation="CenterScreen" 自我解释
  • WindowStyle="None"以避免有标题栏
  • ResizeMode="NoResize"防止用户移动或调整大小
  • 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>

启动画面的代码变得更易于理解,我们只需异步调用init方法。在某些应用程序中,在关闭启动画面之前,我们还可以将焦点设置为主窗口。
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));           
    }
}

0
我使用了.NET Core,当有一个新的线程时,Application.OnExit()被触发。所以我唯一成功让它工作的方法是:

Application.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());
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接