Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bringing mainwindow to front after splash-screen shuts down

I have run into a problem with a custom wpf splash-screen implementation. The problem is that after the loading is finished and the MainWindow should be shown, it sometimes is not brought to front i.e Activate() call fails. It happens maybe 1/10 times. Application is run on Windows7/64.

Here is the implmentation (full source 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);
    }
}

I tried this:

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

and it seems to work, thanks all your suggestions

like image 457
hkon Avatar asked Nov 10 '11 11:11

hkon


3 Answers

Windows has some built in protection to prevent windows from stealing focus from the user's currently active thread. This means you can only successfully Activate() a form from another window which currently has the focus to give.

For example if your main app window shows a dialog, windows is happy to make it have focus because the main app just had the focus in the same thread and can therefor pass that along.

In the case of the splash screen your main form may not be the focused window for several reasons. Either because your splash screen has focus or because the end user started loading your app and then started doing something else (maybe playing with their email or browser) which means your app doesn't have focus at all. (You might see the window flash in the taskbar trying to get focus?)

In the first case, you want your splash screen to do the activating of the main form and then close down. (As apposed to closing the splash screen first and then trying to activate the main form). If your messing with threads, you may also fall back on using some win32 pinvoke to call SetForegroundWindow() with the window handle. You can pass the window handle around across threads. This will ensure that windows is amenable to your request for focus as a currently focused form is making the request.

Here's a neat article on the subject of stealing focus:

The only way Windows 2000 and Windows XP let you bring your application's window up to the front is if the thread it is running on is the thread of the foreground window at the time. So, you have to attach the thread of your application to the thread of the foreground window and then bring your application's window to the front. After that you need to detach your thread (this all only happens if the user has another application's window as the foreground active window). Source: Force Window to Front

Someone converted this article's code to C# over in this other StackOverflow post: Steal Focus in C# on StackOverflow (Looks like some definitions for PInvoke calls are missing, but you can find these easily on google with "pinvoke" and the name of the Win32 API you want.)

like image 50
BenSwayne Avatar answered Nov 02 '22 04:11

BenSwayne


MainWindow = new Shell();
MainWindow.Topmost = true;
MainWindow.Show();
MainWindow.Activate();
MainWindow.Topmost = false;
like image 32
hkon Avatar answered Nov 02 '22 04:11

hkon


In addition to hkon's answer you should check if your splash screen is the current foreground window before stealing the focus via 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;
    }
like image 45
batzen Avatar answered Nov 02 '22 04:11

batzen