【问题标题】:WPF SplashScreen with ProgressBar带有 ProgressBar 的 WPF SplashScreen
【发布时间】:2014-02-25 21:08:00
【问题描述】:

我有一个 WPF 项目,项目向导添加了启动画面。在同一个初始屏幕上,我想添加一个进度条样式的仪表。有人知道怎么做吗?

【问题讨论】:

    标签: wpf splash-screen


    【解决方案1】:

    这是我的计划。我这样做的动机是我不想让初始化代码在 UI 线程上运行,通常我希望在我的 App 类(不是启动画面)上运行初始化代码。

    基本上,我将应用程序StartupUri 设置为我的启动画面,这让球滚动起来。

    在初始屏幕上,我在应用程序上调用了一个委托。这是在工作线程上运行的。在启动画面中,我处理EndInvoke,然后关闭窗口。

    在应用程序初始化委托中,我完成了这项工作,最后,创建并打开了普通的主窗口。在工作负载期间,我还有一个关于 Slash 的方法,可以让我更新进度。

    好的,代码很短,并且不包括主窗口代码(不受所有这些影响),但是它会与匿名代表一起躲避和潜水,所以请仔细阅读,最好在调试器。

    这里是代码......

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

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

    由于工作是在工作线程上完成的,进度条会很好地更新,并且您在初始屏幕上的任何动画都会让娱乐继续滚动。

    【讨论】:

    • 所以你初始化的所有东西都存储在全局变量中?
    • (Invoker)对我不起作用,但用“ new Action(delegate() ”代替它确实
    • 以下划线开头的方法声明...真的吗?
    • 创建真正的工作负载有什么问题?难道我们不能有一个显示实际进度的进度条。我的意思是,如果应用程序加载了 20%,进度条应该显示 20%,那么对于 50% 的加载,它应该显示 50%,依此类推。为什么我们不能有这样的东西?
    【解决方案2】:

    我使用了 .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());
    }
    

    【讨论】:

      【解决方案3】:

      阿里的答案不适用于 .NET 核心。但是,有一种解决方法,在我看来,它更简单。我相信它适用于所有“最新”版本的 .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));           
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-02-21
        • 1970-01-01
        • 1970-01-01
        • 2011-03-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多