【发布时间】:2014-02-25 21:08:00
【问题描述】:
我有一个 WPF 项目,项目向导添加了启动画面。在同一个初始屏幕上,我想添加一个进度条样式的仪表。有人知道怎么做吗?
【问题讨论】:
标签: wpf splash-screen
我有一个 WPF 项目,项目向导添加了启动画面。在同一个初始屏幕上,我想添加一个进度条样式的仪表。有人知道怎么做吗?
【问题讨论】:
标签: wpf splash-screen
这是我的计划。我这样做的动机是我不想让初始化代码在 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; });
}
}
由于工作是在工作线程上完成的,进度条会很好地更新,并且您在初始屏幕上的任何动画都会让娱乐继续滚动。
【讨论】:
我使用了 .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());
}
【讨论】:
阿里的答案不适用于 .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();
}
}
闪屏窗口可以使用一些窗口属性:
<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));
}
}
【讨论】: