6

dotnet 读 WPF 源代码笔记 为什么设置了SplashScreen会让Application.Current.Activat...

 3 years ago
source link: https://lindexi.gitee.io/post/dotnet-%E8%AF%BB-WPF-%E6%BA%90%E4%BB%A3%E7%A0%81%E7%AC%94%E8%AE%B0-%E4%B8%BA%E4%BB%80%E4%B9%88%E8%AE%BE%E7%BD%AE%E4%BA%86SplashScreen%E4%BC%9A%E8%AE%A9Application.Current.Activated%E4%BA%8B%E4%BB%B6%E4%B8%8D%E8%A7%A6%E5%8F%91.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
dotnet 读 WPF 源代码笔记 为什么设置了SplashScreen会让Application.Current.Activated事件不触发

在 WPF 应用中,可以非常方便将一张图片设置为 SplashScreen 启动界面欢迎图,但是如果有设置了启动界面欢迎界面,那么 Application.Current.Activated 事件就不会被触发。本文通过 WPF 框架开源的代码告诉大家这个原因

这是在 GitHub 上,一个小伙伴问的问题,详细请看 After adding a splashscreen Application.Current.Activated event is no longer fired · Issue #4316 · dotnet/wpf

设置某个图片作为 SplashScreen 启动图的方式很简单,只需要右击图片,设置属性,选择 SplashScreen 就可以。也可以在 csproj 中添加如下代码设置

  <ItemGroup>
    <SplashScreen Include="SplashScreen.png" />
  </ItemGroup>

尝试在 App 的构造函数里面添加如下代码用来监听 Activated 事件

    public partial class App : Application
    {
        public App()
        {
            Current.Activated += Current_Activated;
        }

        private void Current_Activated(object sender, EventArgs e)
        {
        }
    }

在没有设置 SplashScreen 时,你可以发现 Current_Activated 函数将会进入,而在设置 SplashScreen 之后,将不会进入此方法

原因是在设置 SplashScreen 启动界面,那么在生成的 App.g.cs 文件里面将会包含下面代码

        [System.STAThreadAttribute()]
        [System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "5.0.1.0")]
        public static void Main() 
        {
            SplashScreen splashScreen = new SplashScreen("SplashScreen.png");
            splashScreen.Show(true);
            App app = new App();
            app.InitializeComponent();
            app.Run();
        }

也就是说 SplashScreen 将会在 Main 函数里面最开始就执行,因此启动图显示的速度也比较快。在 SplashScreen 显示完成之后,再创建 App 出来,也就是说监听 Activated 事件是在启动图之后

那么 Activated 事件是由谁分发的?在 src\Microsoft.DotNet.Wpf\src\PresentationFramework\System\Windows\Application.cs 的 EnsureHwndSource 函数里面将是入口代码,而在 WmActivateApp 函数就是触发的逻辑,先看一下 WmActivateApp 的代码

        private bool WmActivateApp(Int32 wParam)
        {
            int temp = wParam;
            bool isActivated = (temp == 0? false : true);

            // Event handler exception continuality: if exception occurs in Activate/Deactivate event handlers, our state would not
            // be corrupted because no internal state are affected by Activate/Deactivate. Please check Event handler exception continuality
            // if a state depending on those events is added.
            if (isActivated == true)
            {
                OnActivated(EventArgs.Empty);
            }
            else
            {
                OnDeactivated(EventArgs.Empty);
            }
            return false;
        }

也就是说调用进入 WmActivateApp 的参数将决定是否调用 OnActivated 函数,在 OnActivated 函数里面就是事件触发

        protected virtual void OnActivated(EventArgs e)
        {
            VerifyAccess();
            if (Activated != null)
            {
                Activated(this, e);
            }
        }

而 WmActivateApp 从函数名就可以看出,这是一个由 Win32 的 Windows 消息触发的方法,在 AppFilterMessage 函数里面将会调用到 WmActivateApp 方法。而 AppFilterMessage 函数的命名意思是 App 类的 FilterMessage 方法,也就是说这是一个处理应用级的 Windows 消息的函数,代码如下

        private IntPtr AppFilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            IntPtr retInt = IntPtr.Zero;
            switch ((WindowMessage)msg)
            {
                case WindowMessage.WM_ACTIVATEAPP:
                    handled = WmActivateApp(NativeMethods.IntPtrToInt32(wParam));
                    break;
                case WindowMessage.WM_QUERYENDSESSION :
                    handled = WmQueryEndSession(lParam, ref retInt);
                    break;
                default:
                    handled = false;
                    break;
            }
            return retInt;
        }

这个 AppFilterMessage 方法是在 EnsureHwndSource 函数里面注册消息的,请看代码

        private void EnsureHwndSource()
        {
            if (_parkingHwnd == null)
            {
                // _appFilterHook needs to be member variable otherwise
                // it is GC'ed and we don't get messages from HwndWrapper
                // (HwndWrapper keeps a WeakReference to the hook)

                _appFilterHook = new HwndWrapperHook(AppFilterMessage);
                HwndWrapperHook[] wrapperHooks = {_appFilterHook};

                _parkingHwnd = new HwndWrapper(
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                "",
                                IntPtr.Zero,
                                wrapperHooks);
            }
        }

也就是说 Activated 事件的触发就是依靠 WindowMessage.WM_ACTIVATEAPP 消息的,这个消息详细请看 WM_ACTIVATEAPP 官方文档

因为 SplashScreen 本身将会创建窗口,也因为 SplashScreen 的速度足够快,因此在 Application 的 EnsureHwndSource 函数调用之前,系统发送了 WM_ACTIVATEAPP 消息给到应用了

所以在 App 的构造函数监听 Activated 事件将不会收到触发

如果想要使用欢迎界面,也想收到系统的消息,可以在创建 Application 之后,手动创建 SplashScreen 类,或者可以使用 lsj 提供的 kkwpsv/SplashImage: Fast splash Image with GDI+ in C# 库,当然了,这个库代码量特别少,我推荐大家可以抄抄代码。这个库提供的是高性能的版本,可以在另一个线程中执行,换句话说,就是使用 kkwpsv/SplashImage 作为欢迎界面,是可以做到不占用 WPF 主线程时间的,性能比 WPF 提供的好

我尝试修改 WPF 框架代码来支持在设置 SplashScreen 还能在构造函数添加事件,收到触发,请看 Try to create application before show SplashScreen by lindexi · Pull Request #4340 · dotnet/wpf

更多请看 dotnet 读 WPF 源代码笔记 启动欢迎界面 SplashScreen 的原理

当前的 WPF 在 https://github.com/dotnet/wpf 完全开源,使用友好的 MIT 协议,意味着允许任何人任何组织和企业任意处置,包括使用,复制,修改,合并,发表,分发,再授权,或者销售。在仓库里面包含了完全的构建逻辑,只需要本地的网络足够好(因为需要下载一堆构建工具),即可进行本地构建


本文会经常更新,请阅读原文: https://blog.lindexi.com/post/dotnet-%E8%AF%BB-WPF-%E6%BA%90%E4%BB%A3%E7%A0%81%E7%AC%94%E8%AE%B0-%E4%B8%BA%E4%BB%80%E4%B9%88%E8%AE%BE%E7%BD%AE%E4%BA%86SplashScreen%E4%BC%9A%E8%AE%A9Application.Current.Activated%E4%BA%8B%E4%BB%B6%E4%B8%8D%E8%A7%A6%E5%8F%91.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者前往 CSDN 关注我的主页

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系

无盈利,不卖课,做纯粹的技术博客

以下是广告时间

推荐关注 Edi.Wang 的公众号
lindexi%2F201985113622445

欢迎进入 Eleven 老师组建的 .NET 社区
lindexi%2F20209121930471745.jpg

以上广告全是友情推广,无盈利


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK