在启动屏幕关闭后将主窗口置于前面

时间:2011-11-10 11:17:34

标签: wpf splash-screen

我遇到了自定义wpf启动画面实现的问题。 问题是在加载完成并且应该显示MainWindow之后,它有时不会被带到前面,即Activate()调用失败。它可能发生1/10倍。应用程序在Windows7 / 64上运行。

这是implmentation(完整来源sample

public partial class App : Application
{
    private Splash _splash;
    private SplashVM _viewModel;

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

        // starts splash in separate GUI thread
        StartSplash();

        // continues on loading main application in main gui thread
        LoadMainAppFakeSteps(1000, 3);

        // tells splash screen to start shutting down
        Stop();

        // Creates mainwindow for application
        // The problem is that the mainwindow sometimes fails to activate, 
        // even when user has not touched mouse or keyboard (i.e has not given any other programs or components focus)
        MainWindow = new Shell();
        MainWindow.Show();
        MainWindow.Activate();
    }

    private void StartSplash()
    {
        _viewModel = new SplashVM();
        var thread = new Thread(SplashThread);
        thread.SetApartmentState(ApartmentState.STA);
        thread.IsBackground = true;
        thread.Start(_viewModel);
    }

    private void SplashThread(object vm)
    {
        _splash = new Splash();
        _splash.DataContext = vm;
        _splash.Show();

        System.Windows.Threading.Dispatcher.Run();

        _splash = null;
        _viewModel = null;
    }

    private void LoadMainAppFakeSteps(int stepDelayMs, int numSteps)
    {
        for (int i = 1; i <= numSteps; i++)
        {
            _viewModel.Text = i.ToString();
            Thread.Sleep(stepDelayMs);
        }
    }

    private void Stop()
    {
        if (_splash == null) throw new InvalidOperationException("Not showing splash screen");

        _splash.Dispatcher.BeginInvokeShutdown(DispatcherPriority.Normal);
    }
}

我试过了:

MainWindow = new Shell();
MainWindow.Topmost = true;
MainWindow.Show();
MainWindow.Activate();
MainWindow.Topmost = false;

它似乎有效,感谢您的所有建议

3 个答案:

答案 0 :(得分:4)

Windows具有一些内置保护功能,可防止Windows从用户当前活动的线程中窃取焦点。这意味着您只能从另一个当前具有焦点的窗口成功Activate()表单。

例如,如果您的主应用程序窗口显示一个对话框,Windows很高兴让它具有焦点,因为主应用程序只是将焦点放在同一个线程中,因此可以传递它。

在启动画面的情况下,由于多种原因,您的主窗体可能不是聚焦窗口。要么是因为您的启动画面有焦点,要么是因为最终用户开始加载您的应用程序然后开始做其他事情(可能正在使用他们的电子邮件或浏览器),这意味着您的应用程序根本没有焦点。 (您可能会在任务栏中看到窗口闪烁,试图获得焦点?)

在第一种情况下,您希望启动画面激活主窗体然后关闭。 (因为首先关闭启动画面然后尝试激活主窗体)。如果你搞乱了线程,你也可以使用一些win32 pinvoke来使用窗口句柄来调用SetForegroundWindow()。您可以跨线程传递窗口句柄。这将确保窗口适合您的焦点请求,因为当前关注的表单正在发出请求。

这是一篇关于窃取焦点主题的简洁文章:

  

Windows 2000和Windows XP让你带来的唯一方法   应用程序的窗口直到前面是它正在运行的线程   是当时前景窗口的线程。所以,你必须这样做   将应用程序的线程附加到前台的线程   窗口然后将您的应用程序窗口移到前面。后   您需要分离您的线程(这一切只有在用户发生时才会发生)   将另一个应用程序的窗口作为前台活动窗口)。 来源: Force Window to Front

有人在此另一篇StackOverflow帖子中将此文章的代码转换为C#:Steal Focus in C# on StackOverflow(看起来PInvoke调用的某些定义缺失,但您可以在google上使用“pinvoke”轻松找到这些,并且名称为你想要的Win32 API。)

答案 1 :(得分:3)

MainWindow = new Shell();
MainWindow.Topmost = true;
MainWindow.Show();
MainWindow.Activate();
MainWindow.Topmost = false;

答案 2 :(得分:1)

除了hkon的回答,你应该在通过TopMost = true窃取焦点之前检查你的启动画面是否是当前的前景窗口。

    [DllImport("user32.dll")]
    private static extern IntPtr GetForegroundWindow();

    public static bool IsForeground(this Window window)
    {
        var windowHandle = new WindowInteropHelper(window).Handle;
        var foregroundWindow = GetForegroundWindow();
        return windowHandle == foregroundWindow;
    }