我有一个带有后台工作程序的多线程应用程序,我用它来显示一个启动画面来处理主窗口的创建。
我想更新帖子中的进度条' u'在我的程序中,所以每次我想从线程“u”中更新它时,我都必须调用进度条控件。 这意味着我不必使用" backgroundWorker_DoWork"尤其
我遇到的问题是,当" backgroundWorker_RunWorkerCompleted"时,我无法显示主窗口(form2)。事件被召唤。
我认为问题在于调度员。
public partial class App : Application
{
public BackgroundWorker backgroundWorker;
private SplashScreenWindow splashScreen;
public static EventWaitHandle initWaitHandle = new AutoResetEvent(false);
public MainWindow Form2 { get; set; }
[DllImport("kernel32.dll")]
private static extern bool AllocConsole();
protected override void OnStartup(StartupEventArgs e)
{
backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
splashScreen = new SplashScreenWindow();
splashScreen.ShowInTaskbar = false;
splashScreen.ResizeMode = ResizeMode.NoResize;
splashScreen.WindowStyle = WindowStyle.None;
splashScreen.Topmost = true;
splashScreen.Width = (SystemParameters.PrimaryScreenWidth) / 2.5;
splashScreen.Height = (SystemParameters.PrimaryScreenHeight) / 2.5;
splashScreen.Show();
base.OnStartup(e);
backgroundWorker.RunWorkerAsync();
Thread u = new Thread(new ThreadStart(interface_process));
u.SetApartmentState(ApartmentState.STA);
u.Start();
}
public void interface_process()
{
MainWindow form2 = new MainWindow();
this.Form2 = form2;
System.Windows.Threading.Dispatcher.Run();
}
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
splashScreen.Close();
this.Form2.Invoke((Action)delegate()
{
this.Form2.Show(); // does not work
System.Windows.Threading.Dispatcher.Run();
});
}
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
splashScreen.ValueProgressBar = e.ProgressPercentage;
}
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 100; i += 10)
{
backgroundWorker.ReportProgress(i, "Chargement en cours : " + i);
Thread.Sleep(500);
}
}
}
答案 0 :(得分:0)
我不清楚你发布的代码是如何编译的,因为WPF Window
类没有Invoke()
方法。这将导致代码中的编译时错误:
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
splashScreen.Close();
this.Form2.Invoke((Action)delegate()
{
this.Form2.Show(); // does not work
System.Windows.Threading.Dispatcher.Run();
});
}
如果上面的代码被更改,以便方法中的第二个语句读取this.Form2.Dispatcher.Invoke((Action)delegate()
- 也就是说,使用拥有Dispatcher
对象的Form2
对象 - 不仅应该是代码编译,但调用this.Form2.Show()
也应该有效。请注意,不需要第二次调用Dispatcher.Run()
,实际上应该避免。
&#34;正确&#34;因此,该方法的实现如下:
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
splashScreen.Close();
this.Form2.Dispatcher.Invoke((Action)delegate()
{
this.Form2.Show(); // works fine
});
}
现在,那说......在我看来,整个方法都存在缺陷。你真的应该在你的程序中拥有一个UI线程。如果您希望在显示启动画面时首先发生某些事情,请将启动画面窗口设置为显示的第一个窗口,运行后台任务,然后在同一个主题窗口中显示主窗口。已完成。
以下是您似乎要尝试做的一个示例,但编写的仅使用主UI线程和正常的WPF启动机制......
假设我们从Visual Studio中的普通WPF项目开始。这将包含App
类和MainWindow
类。我们只需要编辑它就可以做你想做的事。
首先,我们需要一个闪屏窗口。大多数配置都可以在XAML中完成;因为你想要根据屏幕大小计算宽度和高度,所以将它放在构造函数中是最简单的(对我来说)。这看起来像这样:
<强> SplashScreenWindow.xaml:强>
<Window x:Class="TestSingleThreadSplashScreen.SplashScreenWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
ShowInTaskbar="False"
ResizeMode="NoResize"
WindowStyle="None"
Topmost="True"
Title="SplashScreenWindow">
<Grid>
<ProgressBar HorizontalAlignment="Left" Height="10" Margin="10,10,0,0"
VerticalAlignment="Top" Width="100"
Value="{Binding ValueProgressBar}"/>
</Grid>
</Window>
<强> SplashScreenWindow.xaml.cs:强>
public partial class SplashScreenWindow : Window
{
public readonly static DependencyProperty ValueProgressBarProperty = DependencyProperty.Register(
"ValueProgressBar", typeof(double), typeof(SplashScreenWindow));
public double ValueProgressBar
{
get { return (double)GetValue(ValueProgressBarProperty); }
set { SetValue(ValueProgressBarProperty, value); }
}
public SplashScreenWindow()
{
InitializeComponent();
Width = SystemParameters.PrimaryScreenWidth / 2.5;
Height = SystemParameters.PrimaryScreenHeight / 2.5;
}
}
现在上面的类是我们想要首先显示的类。因此,我们通过更改App
属性来编辑StartupUri
类的XAML来执行此操作:
<Application x:Class="TestSingleThreadSplashScreen.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="SplashScreenWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
最后,我们需要App
课程来运行BackgroundWorker
,在不同时间执行相应的操作:
public partial class App : Application
{
private SplashScreenWindow SplashScreen { get { return (SplashScreenWindow)this.MainWindow; } }
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
BackgroundWorker backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
backgroundWorker.RunWorkerAsync();
}
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
new MainWindow().Show();
SplashScreen.Close();
}
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
SplashScreen.ValueProgressBar = e.ProgressPercentage;
}
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker backgroundWorker = (BackgroundWorker)sender;
for (int i = 0; i <= 100; i += 10)
{
backgroundWorker.ReportProgress(i, "Chargement en cours : " + i);
Thread.Sleep(500);
}
}
}
唯一棘手的问题是,在显示主窗口后,必须关闭启动画面窗口。否则,WPF会认为你关闭了最后一个窗口(好吧,技术上你会有:))并将关闭程序。通过在关闭启动画面窗口之前显示主窗口,程序继续运行。
在我看来,这是很多更好的做事方式,因为它与正常的WPF机制一起工作,而不是试图颠覆和/或解决他们。它利用了程序启动时自动创建的Dispatcher
,不需要额外的UI线程等等。哦,而且...... 工作。所以就是这样。 :)