关闭启动屏后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逻辑
- 移除启动屏XAML中的
Topmost="True"属性。 - 在启动屏线程中,仅在显示阶段设置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
相关产品推荐
相关产品推荐

