You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关闭启动屏后WPF主窗口显示于焦点窗口后方的技术求助

主窗口在启动屏关闭后始终位于后台的WPF问题解决方案

问题背景

我们开发的WPF应用启动时会显示启动屏,启动屏通过单独STA线程创建并调用ShowDialog()。主应用完成配置加载后,通过设置SplashViewModel.DialogResult关闭启动屏,再显示主窗口,但主窗口总是出现在当前焦点窗口后方,严重影响使用体验。

现有实现代码

启动屏创建逻辑

_splashViewModel = new SplashViewModel(versionTrimmed);
var t = new Thread(() =>
{
    var splash = ShowSplashScreen(_splashViewModel);
    splash.ShowDialog();
});
t.SetApartmentState(ApartmentState.STA);
t.Start();

主窗口启动逻辑

public void StartSystemControl(
    ICardMasterOneViewModel mainViewModel, 
    SplashViewModel splashViewModel,
    Thread thread)
{
    var mainWindow = new CMOMainWindow { DataContext = mainViewModel };
    System.Windows.Application.Current.MainWindow = mainWindow;
    splashViewModel.DialogResult = true;

    thread.Join();
    mainWindow.Show();
    mainWindow.Activate();
}

启动屏相关定义

SplashViewModel

public class SplashViewModel : ViewModelBase
{
    private bool? dialogResult;
    private string machineFamily;

    public string MachineFamily
    {
        get => machineFamily;
        set { Set(() => MachineFamily, ref machineFamily, value); }
    }

    public string VersionInfo { get; }

    public bool? DialogResult
    {
        get => dialogResult;
        set { Set(() => DialogResult, ref dialogResult, value); }
    }

    public SplashViewModel(string versionInfo)
    {
        VersionInfo = versionInfo;
    }
}

启动屏XAML

<Window x:Class="IAI.UserInterface.Views.Core.SplashScreen"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:behaviors="clr-namespace:IAI.UserInterface.Views.Core.Behaviors"
        xmlns:splash="clr-namespace:IAI.UserInterface.ViewModels.Core"
        mc:Ignorable="d"
        Title="SplashScreen"
        Height="480" Width="640"
        WindowStyle="None"
        Topmost="True"
        ShowInTaskbar="False"
        ResizeMode="NoResize"
        MouseDown="SplashScreen_OnMouseDown"
        behaviors:DialogCloseBehavior.DialogResult="{Binding DialogResult}"
        AllowsTransparency="True"
        d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False, Type=splash:SplashViewModel}">

    <Window.Background>
        <ImageBrush ImageSource="../Resources/SplashBackground.png"/>
    </Window.Background>

    <Border BorderThickness="1" BorderBrush="Gray">
        <StackPanel VerticalAlignment="Center">
            <TextBlock FontFamily="Verdana"
                       FontSize="48"
                       HorizontalAlignment="Center"
                       TextAlignment="Center"
                       Opacity="0.7"
                       Text="{Binding MachineFamily}" Margin="0,0,0,16">
            </TextBlock>

            <TextBlock FontFamily="Verdana"
                       FontWeight="Light"
                       FontSize="16"
                       HorizontalAlignment="Center"
                       TextAlignment="Center"
                       Opacity="0.7"
                       Text="{Binding VersionInfo, StringFormat=Version: {0}}">
            </TextBlock>
        </StackPanel>
    </Border>
</Window>

App.xaml.cs的OnStartup方法

private void App_OnStartup(object sender, StartupEventArgs e)
{
    // DispatcherHelper should be initialized from the UI thread for which the Static Main is appropriate
    DispatcherHelper.Initialize();
    Directory.SetCurrentDirectory(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));

    var application = Current;
    string machineNumber = "some identifier";
    try
    {
        var version = "some version";

        var splashViewModel = new SplashViewModel(versionTrimmed);
        var t = new Thread(() =>
        {
            var splash = ShowSplashScreen(splashViewModel);
            splash.ShowDialog();
        });
        t.SetApartmentState(ApartmentState.STA);
        t.Start();

        // Removed actual startup behavior (loading config etc)...

        var mainWindow = new MainWindow { DataContext = new MainViewModel() };
        System.Windows.Application.Current.MainWindow = mainWindow;
        splashViewModel.DialogResult = true;

        mainWindow.Show();
    }
    catch (Exception ex)
    {
         // removed for simplicity
    }
}

已尝试的无效方法

  • 方法1:设置TopMost后再取消

    mainWindow.Topmost = true;
    mainWindow.Show();
    mainWindow.Topmost = false;
    

    虽然暂时解决了层级问题,但取消TopMost后应用仍会一直保持顶层显示,不符合需求。

  • 方法2:调用Activate()

    mainWindow.Show();
    mainWindow.Activate();
    

    完全无效,主窗口依然在焦点窗口后方。

解决方案

问题根源在于启动屏的Topmost="True"属性,以及跨线程关闭启动屏后,主窗口的激活逻辑未正确获取前台焦点。以下两种方案可解决该问题:

方案1:通过Win32 API强制主窗口获取前台焦点

修改主窗口启动代码,在启动屏线程结束后,调用Win32 API强制主窗口置顶激活:

public void StartSystemControl(
    ICardMasterOneViewModel mainViewModel, 
    SplashViewModel splashViewModel,
    Thread thread)
{
    var mainWindow = new CMOMainWindow { DataContext = mainViewModel };
    System.Windows.Application.Current.MainWindow = mainWindow;
    
    // 关闭启动屏并等待线程结束
    splashViewModel.DialogResult = true;
    thread.Join();

    // 显示并强制激活主窗口
    mainWindow.Show();
    SetForegroundWindow(new WindowInteropHelper(mainWindow).Handle);
}

需添加Win32 API声明:

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetForegroundWindow(IntPtr hWnd);

方案2:调整启动屏的Topmost逻辑

  1. 移除启动屏XAML中的Topmost="True"属性。
  2. 在启动屏线程中,仅在显示阶段设置Topmost,关闭前取消:
var t = new Thread(() =>
{
    var splash = ShowSplashScreen(_splashViewModel);
    splash.Topmost = true;
    splash.ShowDialog();
    splash.Topmost = false;
});
t.SetApartmentState(ApartmentState.STA);
t.Start();

之后主窗口启动时,正常调用Show()和Activate()即可:

mainWindow.Show();
mainWindow.Activate();

说明

方案1适用于必须保持启动屏全程Topmost的场景,通过系统API直接干预窗口焦点;方案2更贴合WPF原生行为,通过调整启动屏的层级逻辑避免影响主窗口。

内容的提问来源于stack exchange,提问作者bas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 09:17:06