在有透明子窗覆盖的情况下如何透传事件到主窗口
======================

#### 问题现象

使用透明子窗口完全覆盖主窗口，如何实现子窗口不拦截操作主窗口页面的事件，具体表现有如下场景：

*   **场景一**：子窗口不响应任何事件，操作主窗口如同没有子窗口覆盖一样。
*   **场景二**：子窗口仅响应侧滑事件，第一次侧滑关闭透明子窗，第二次侧滑主窗返回桌面。

#### 背景知识

*   [createSubWindow](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-windowstage#createsubwindow9-1)创建该WindowStage实例下的子窗口。
*   [destroyWindow](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-window#destroywindow9-1)销毁当前窗口，支持系统窗口及应用子窗口，全局悬浮窗和模态窗。
*   [setWindowTouchable](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-window#setwindowtouchable9-1)设置窗口是否为可触状态，true表示可触；false表示不可触。
*   [setWindowFocusable](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-window#setwindowfocusable9-1)设置窗口是否具有获得焦点的能力，true表示支持；false表示不支持。
*   [setWindowBackgroundColor](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-window#setwindowbackgroundcolor9)设置窗口的背景色。Stage模型下，该接口需要在loadContent()或setUIContent()调用生效后使用。
*   [resize](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-window#resize9-1)基于窗口左上角顶点改变当前窗口大小，调用成功即返回，该接口返回后无法立即获取最终生效结果，如需立即获取，建议使用接口[resizeAsync](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-window#resizeasync12)。

#### 解决方案

使用createSubWindow创建子窗口，使用setWindowBackgroundColor设置子窗口透明背景色，并resize子窗口大小使其完全覆盖主窗口。

*   **场景一**：使子窗口不响应任何事件，须使用setWindowTouchable设置子窗口不可触，并使用setWindowFocusable设置子窗口不获焦，即可透传事件给主窗口。
    
    1.  在EntryAbility下的onWindowStageCreate方法中使用AppStorage存储windowStage实例，用于创建子窗口。
        
        onWindowStageCreate(windowStage: window.WindowStage): void {
         _ // Main window is created, set main page for this ability_
          hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
         _ // 需要加载哪个页面，此处需要改成对应页面名_
          windowStage.loadContent('pages/PageOne', (err) => {
            if (err.code) {
              hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
              return;
            }
            hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
          });
         _ // 存储windowStage实例_
          AppStorage.setOrCreate('windowStage', windowStage);
        }
        
    2.  PageOne.ets：创建透明子窗口，设置子窗口不可触、不可获焦，达到透传事件给子窗口的效果。
        
        import { display, window } from '@kit.ArkUI';
        
        @Entry
        @Component
        struct PageOne {
          @State message: string = 'Hello World';
        
          onPageShow(): void {
            this.createFloatWindow();
          }
        
          _// 自定义创建悬浮子窗方法_
          createFloatWindow() {
           _ // 获取windowStage实例_
            let windowStage = AppStorage.get('windowStage') as window.WindowStage;
         _   // 创建悬浮子窗_
            windowStage.createSubWindow('floatWindow', (err, win) => {
              if (err.code) {
                console.error('Failed to create the floatWindow. Cause: ' + JSON.stringify(err));
                return;
              }
              console.info('Succeeded in creating the floatWindow. Data: ' + JSON.stringify(win));
        
            _  // 设置悬浮子窗位置_
              win.moveWindowTo(0, 0, (err) => {
                if (err.code) {
                  console.error('Failed to move the window. Cause:' + JSON.stringify(err));
                  return;
                }
                console.info('Succeeded in moving the window.');
              });
           _   // 设置悬浮子窗大小_
              win.resize(display.getDefaultDisplaySync().width, display.getDefaultDisplaySync().height, (err) => {
                if (err.code) {
                  console.error('Failed to change the window size. Cause:' + JSON.stringify(err));
                  return;
                }
              });
            _  // 为悬浮子窗加载页面内容，这里可以设置在main\_pages.json中配置的页面_
              win.setUIContent('pages/SubWindow', (err: BusinessError) => {
                if (err.code) {
                  console.error('Failed to load the content. Cause:' + JSON.stringify(err));
                  return;
                }
                win.setWindowBackgroundColor('#00000000');
               _ // 显示悬浮子窗。_
                win.showWindow((err: BusinessError) => {
                  if (err.code) {
                    console.error('Failed to show the window. Cause: ' + JSON.stringify(err));
                    return;
                  }
                  console.info('Succeeded in showing the window.');
                });
              });
             _ // 设置子窗不可触摸_
              win.setWindowTouchable(false);
           _   // 设置子窗不获焦_
              win.setWindowFocusable(false);
            });
          }
        
          build() {
            RelativeContainer() {
              Text(this.message)
                .id('HelloWorld')
                .fontSize($r('app.float.page\_text\_font\_size'))
                .fontWeight(FontWeight.Bold)
                .alignRules({
                  center: { anchor: '\_\_container\_\_', align: 100 },
                  middle: { anchor: '\_\_container\_\_', align: HorizontalAlign.Center }
                })
                .onClick(() => {
                  this.message = 'Welcome';
                });
            }
            .height('100%')
            .width('100%');
          }
        }
        
    3.  SubWindow.ets：子窗口页面内容。
        
        @Entry
        @Component
        struct Index {
          build() {
            RelativeContainer() {
              Text('这是透明子窗口')
                .id('HelloWorld')
                .fontSize(30)
                .fontWeight(FontWeight.Bold)
                .fontColor(Color.Gray)
                .alignRules({
                  center: { anchor: '\_\_container\_\_', align: VerticalAlign.Center },
                  middle: { anchor: '\_\_container\_\_', align: HorizontalAlign.Center }
                });
            }
            .height('100%')
            .width('100%');
          }
        }
        
    
    效果预览：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/31/v3/hjaVfxOhR1CQZS-cXyuQqg/zh-cn_image_0000002658926413.png?HW-CC-KV=V1&HW-CC-Date=20260709T134011Z&HW-CC-Expire=86400&HW-CC-Sign=1AD11197D94EBA3C9FC2E8C37AC66BD71281C9D59CC2EC6DCA12BD30E9A3631C "点击放大")
    
*   **场景二**：实现子窗口仅响应侧滑事件，需要在主页面重写[onBackPress](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-custom-component-lifecycle#onbackpress)方法，实现子窗口的销毁。
    
    1.  在EntryAbility下的onWindowStageCreate方法中使用AppStorage存储windowStage实例，用于创建子窗口。参考场景一步骤一，加载PageTwo页面。
    2.  PageTwo.ets：创建透明子窗口，设置子窗口不可触、不可获焦并重写onBackPress，达到第一次侧滑时关闭子窗口的效果。
        
        import { display, window } from '@kit.ArkUI';
        
        @Entry
        @Component
        struct PageTwo {
          @State message: string = 'Hello World';
          private isShow: boolean = false;
        
          _// 重写侧滑事件，使子窗口响应侧滑关闭_
          onBackPress(): boolean | void {
            if (this.isShow) {
              this.closeSubWindow();
              this.isShow = false;
              return true;
            } else {
              return false;
            }
          }
        
          onPageShow(): void {
            this.createFloatWindow();
            this.isShow = true;
          }
        
         _ // 关闭子窗口_
          closeSubWindow() {
            window.findWindow('mySubWindow').destroyWindow((err) => {
              if (err.code) {
                console.error('Failed to destroy the subWindow. Cause: ' + JSON.stringify(err));
                return;
              }
              console.info('Succeeded in destroying the subWindow.');
            });
          }
        
         _ // 自定义创建悬浮子窗方法_
          createFloatWindow() {
          _  // 获取windowStage实例_
            let windowStage = AppStorage.get('windowStage') as window.WindowStage;
         _   // 创建悬浮子窗_
            windowStage.createSubWindow('mySubWindow', (err, win) => {
              if (err.code) {
                console.error('Failed to create the mySubWindow. Cause: ' + JSON.stringify(err));
                return;
              }
              console.info('Succeeded in creating the mySubWindow. Data: ' + JSON.stringify(win));
        
            _  // 设置悬浮子窗位置_
              win.moveWindowTo(0, 0, (err) => {
                if (err.code) {
                  console.error('Failed to move the window. Cause:' + JSON.stringify(err));
                  return;
                }
                console.info('Succeeded in moving the window.');
              });
           _   // 设置悬浮子窗大小_
              win.resize(display.getDefaultDisplaySync().width, display.getDefaultDisplaySync().height, (err) => {
                if (err.code) {
                  console.error('Failed to change the window size. Cause:' + JSON.stringify(err));
                  return;
                }
              });
            _  // 为悬浮子窗加载页面内容，这里可以设置在main\_pages.json中配置的页面_
              win.setUIContent('pages/SubWindow', (err: BusinessError) => {
                if (err.code) {
                  console.error('Failed to load the content. Cause:' + JSON.stringify(err));
                  return;
                }
              _  // 设置子窗口透明_
                win.setWindowBackgroundColor('#00000000');
               _ // 显示悬浮子窗。_
                win.showWindow((err: BusinessError) => {
                  if (err.code) {
                    console.error('Failed to show the window. Cause: ' + JSON.stringify(err));
                    return;
                  }
                  console.info('Succeeded in showing the window.');
                });
              });
            _  // 设置子窗不可触摸_
              win.setWindowTouchable(false);
           _   // 设置子窗不获焦_
              win.setWindowFocusable(false);
            });
          }
        
          build() {
            RelativeContainer() {
              Text(this.message)
                .id('HelloWorld')
                .fontSize($r('app.float.page\_text\_font\_size'))
                .fontWeight(FontWeight.Bold)
                .alignRules({
                  center: { anchor: '\_\_container\_\_', align: 100 },
                  middle: { anchor: '\_\_container\_\_', align: HorizontalAlign.Center }
                })
                .onClick(() => {
                  this.message = 'Welcome';
                });
            }
            .height('100%')
            .width('100%');
          }
        }
        
    3.  SubWindow.ets：子窗口页面内容。
        
        @Entry
        @Component
        struct Index {
          build() {
            RelativeContainer() {
              Text('这是透明子窗口')
                .id('HelloWorld')
                .fontSize(30)
                .fontWeight(FontWeight.Bold)
                .fontColor(Color.Gray)
                .alignRules({
                  center: { anchor: '\_\_container\_\_', align: VerticalAlign.Center },
                  middle: { anchor: '\_\_container\_\_', align: HorizontalAlign.Center }
                });
            }
            .height('100%')
            .width('100%');
          }
        }
        
    
    效果预览：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/7f/v3/mMdoa__sStWQNtI-pE5yMQ/zh-cn_image_0000002628407208.png?HW-CC-KV=V1&HW-CC-Date=20260709T134011Z&HW-CC-Expire=86400&HW-CC-Sign=F8E8568A0C13C0097175FA27B24FEC7E89DB1D35451B94852BE5B126B30F495B "点击放大")
    

#### 常见FAQ

Q：PC上创建子窗口为什么主窗会触发windowStageEvent监听的获焦和失焦事件？

A：应用创建子窗口使得主窗口生命周期变化、窗口属性刷新可见性变化及应用调用setWindowFocusable接口的时序有变化，从而导致主窗的失焦获焦变化。

Q：创建子窗口后，如果不主动点击主窗口需要手势返回两次才能返回到上一页面，如何做到子窗口不拦截侧滑事件？

A：[windowStage.getMainWindow](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-windowstage#getmainwindow9)获取主窗口window实例，[window.findWindow](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-f#windowfindwindow9)获取子窗口window实例，通过[window.shiftAppWindowFocus](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-f#windowshiftappwindowfocus11)将焦点从子窗口转移到主窗口上，即可一次操作主窗口返回上一页。

Q：如何感知窗口获焦失焦的原因？

A：切换焦点是系统行为，应用不需要感知。窗口获焦除了鼠标点击、Alt+Tab切换、任务栏缩略切换、四指滑动切换外，还包括窗口调showWindow接口、命令行或dock栏等启动应用以及系统调用请求焦点的接口。