1# 如何创建子窗口并与主窗口通信 2 3## 场景介绍 4应用开发过程中,经常需要创建弹窗(子窗口)用来承载跟当前内容相关的业务,比如电话应用的拨号弹窗;阅读应用中长按当前内容触发的编辑弹窗;购物应用经常出现的抽奖活动弹窗等。 5本文为大家介绍如何创建子窗口并实现子窗口与主窗口的数据通信。 6 7## 效果呈现 8本例最终效果如下: 9 10 11 12## 环境要求 13本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发: 14 15- IDE: DevEco Studio 4.0 Beta1 16- SDK: Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1) 17 18 19## 实现思路 20本例关键特性及实现方案如下: 21- 点击“创建子窗口”按钮创建子窗口:使用window模块的createSubWindow方法创建子窗口,在创建时设置子窗口的大小、位置、内容等。 22- 子窗口可以拖拽:通过gesture属性为子窗口绑定PanGesture拖拽事件,使用moveWindowTo方法将窗口移动到拖拽位置,呈现拖拽效果。 23- 点击主窗口的“子窗口数据+1”按钮,子窗口中的数据加1,反之亦然,即实现主窗口和子窗口间的数据通信:将数据变量存储在AppStorage中,在主窗口和子窗口中引用该数据,并通过@StorageLink与AppStorage中的数据进行双向绑定,从而实现主窗口和子窗口之间的数据联动。 24>  **说明:** 25> 本文使用AppStorage实现主窗口和子窗口之间的数据传递,除此之外,Emitter和EventHub等方式也可以实现,开发者可以根据实际业务需要进行选择。 26 27 28## 开发步骤 29由于本例重点讲解子窗口的创建以及主窗口和子窗口之间的通信,所以开发步骤会着重讲解相关内容的开发,其余内容不做赘述,全量代码可参考完整代码章节。 301. 创建子窗口。 31 使用createSubWindow方法创建名为“hiSubWindow”的子窗口,并设置窗口的位置、大小、显示内容。将创建子窗口的动作放在自定义成员方法showSubWindow()中,方便后续绑定到按钮上。具体代码如下: 32 ```ts 33 showSubWindow() { 34 // 创建应用子窗口。 35 this.windowStage.createSubWindow("hiSubWindow", (err, data) => { 36 if (err.code) { 37 console.error('Failed to create the subwindow. Cause: ' + JSON.stringify(err)); 38 return; 39 } 40 this.sub_windowClass = data; 41 console.info('Succeeded in creating the subwindow. Data: ' + JSON.stringify(data)); 42 // 子窗口创建成功后,设置子窗口的位置 43 this.sub_windowClass.moveWindowTo(300, 300, (err) => { 44 if (err.code) { 45 console.error('Failed to move the window. Cause:' + JSON.stringify(err)); 46 return; 47 } 48 console.info('Succeeded in moving the window.'); 49 }); 50 // 设置子窗口的大小 51 this.sub_windowClass.resize(350, 350, (err) => { 52 if (err.code) { 53 console.error('Failed to change the window size. Cause:' + JSON.stringify(err)); 54 return; 55 } 56 console.info('Succeeded in changing the window size.'); 57 }); 58 // 为子窗口加载对应的目标页面。 59 this.sub_windowClass.setUIContent("pages/SubWindow",(err) => { 60 if (err.code) { 61 console.error('Failed to load the content. Cause:' + JSON.stringify(err)); 62 return; 63 } 64 console.info('Succeeded in loading the content.'); 65 // 显示子窗口。 66 this.sub_windowClass.showWindow((err) => { 67 if (err.code) { 68 console.error('Failed to show the window. Cause: ' + JSON.stringify(err)); 69 return; 70 } 71 console.info('Succeeded in showing the window.'); 72 }); 73 this.sub_windowClass.setWindowBackgroundColor('#E8A027') 74 }); 75 }) 76 } 77 ``` 782. 实现子窗口可拖拽。 79 为页面内容绑定PanGesture拖拽事件,拖拽事件发生时获取到触摸点的位置信息,使用@Watch监听到位置变量的变化,然后调用窗口的moveWindowTo方法将窗口移动到对应位置,从而实现拖拽效果。 80 81 具体代码如下: 82 ```ts 83 import window from '@ohos.window'; 84 85 interface Position { 86 x: number, 87 y: number 88 } 89 90 @Entry 91 @Component 92 struct SubWindow{ 93 ... 94 // 创建位置变量,并使用@Watch监听,变量发生变化调用moveWindow方法移动窗口 95 @State @Watch("moveWindow") windowPosition: Position = { x: 0, y: 0 }; 96 private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All }); 97 private subWindow: window.Window 98 // 通过悬浮窗名称“hiSubWindow”获取到创建的悬浮窗 99 aboutToAppear() { 100 this.subWindow = window.findWindow("hiSubWindow") 101 } 102 // 将悬浮窗移动到指定位置 103 moveWindow() { 104 this.subWindow.moveWindowTo(this.windowPosition.x, this.windowPosition.y); 105 } 106 107 build(){ 108 Column(){ 109 Text(`AppStorage保存的数据:${this.storData}`) 110 .fontSize(12) 111 .margin({bottom:10}) 112 Button('主窗口数据+1') 113 .fontSize(12) 114 .backgroundColor('#A4AE77') 115 .onClick(()=>{ 116 this.storData += 1 117 }) 118 } 119 .height('100%') 120 .width('100%') 121 .alignItems(HorizontalAlign.Center) 122 .justifyContent(FlexAlign.Center) 123 .gesture( 124 PanGesture(this.panOption) 125 .onActionStart((event: GestureEvent) => { 126 console.info('Pan start'); 127 }) 128 // 发生拖拽时,获取到触摸点的位置,并将位置信息传递给windowPosition 129 .onActionUpdate((event: GestureEvent) => { 130 this.windowPosition.x += event.offsetX; 131 this.windowPosition.y += event.offsetY; 132 }) 133 .onActionEnd(() => { 134 console.info('Pan end'); 135 }) 136 ) 137 } 138 } 139 ``` 1403. 实现主窗口和子窗口间的数据通信。本例中即实现点击主窗口的“子窗口数据+1”按钮,子窗口中的数据加1,反之亦然。本例使用应用全局UI状态存储AppStorage来实现对应效果。 141 - 在创建窗口时触发的onWindowStageCreate回调中将自定义数据变量“data”存入AppStorage。 142 ```ts 143 onWindowStageCreate(windowStage: window.WindowStage) { 144 // 将自定义数据变量“data”存入AppStorage 145 AppStorage.SetOrCreate('data', 1); 146 ... 147 windowStage.loadContent('pages/Index', (err, data) => { 148 if (err.code) { 149 hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); 150 return; 151 } 152 hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? ''); 153 }); 154 } 155 ``` 156 - 在主窗口中定义变量“storData”,并使用@StorageLink将其与AppStorage中的变量“data”进行双向绑定,这样一来,“mainData”的变化可以传导至“data”,并且该变化可以被UI框架监听到,从而完成UI状态刷新。 157 ```ts 158 ... 159 // 使用@StorageLink将"mainData"与AppStorage中的变量"data"进行双向绑定 160 @StorageLink('data') mainData: number = 1; 161 ... 162 build() { 163 Row() { 164 Column() { 165 Text(`AppStorage保存的数据:${this.mainData}`) 166 .margin({bottom:30}) 167 Button('子窗口数据+1') 168 .backgroundColor('#A4AE77') 169 .margin({bottom:30}) 170 .onClick(()=>{ 171 // 点击,storData的值加1 172 this.mainData += 1 173 }) 174 ... 175 } 176 .width('100%') 177 } 178 .height('100%') 179 } 180 ``` 181 - 在子窗口中定义变量“subData”,并使用@StorageLink将其与AppStorage中的变量“data”进行双向绑定。由于主窗口的“mainData”也与“data”进行了绑定,因此,“mainData”的值可以通过“data”传递给“subData”,反之亦然。这样就实现了主窗口和子窗口之间的数据同步。 182 ```ts 183 ... 184 // 使用@StorageLink将"subData"与AppStorage中的变量"data"进行双向绑定 185 @StorageLink('data') subData: number = 1; 186 ... 187 build(){ 188 Column(){ 189 Text(`AppStorage保存的数据:${this.subData}`) 190 .fontSize(12) 191 .margin({bottom:10}) 192 Button('主窗口数据+1') 193 .fontSize(12) 194 .backgroundColor('#A4AE77') 195 .onClick(()=>{ 196 // 点击,subData的值加1 197 this.subData += 1 198 }) 199 } 200 ... 201 } 202 ``` 203 204## 完整代码 205本例完整代码如下: 206EntryAbility文件代码: 207```ts 208// EntryAbility.ts 209import AbilityConstant from '@ohos.app.ability.AbilityConstant'; 210import hilog from '@ohos.hilog'; 211import UIAbility from '@ohos.app.ability.UIAbility'; 212import Want from '@ohos.app.ability.Want'; 213import window from '@ohos.window'; 214 215let sub_windowClass = null; 216export default class EntryAbility extends UIAbility { 217 218 destroySubWindow() { 219 // 销毁子窗口。当不再需要子窗口时,可根据具体实现逻辑,使用destroy对其进行销毁。 220 sub_windowClass.destroyWindow((err) => { 221 if (err.code) { 222 console.error('Failed to destroy the window. Cause: ' + JSON.stringify(err)); 223 return; 224 } 225 console.info('Succeeded in destroying the window.'); 226 }); 227 } 228 229 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { 230 231 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); 232 } 233 234 onDestroy() { 235 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); 236 } 237 238 onWindowStageCreate(windowStage: window.WindowStage) { 239 // 将自定义数据变量“data”存入AppStorage 240 AppStorage.SetOrCreate('data', 1); 241 AppStorage.SetOrCreate('window', windowStage); 242 // 为主窗口添加加载页面 243 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); 244 245 windowStage.loadContent('pages/Index', (err, data) => { 246 if (err.code) { 247 hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); 248 return; 249 } 250 hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? ''); 251 }); 252 } 253 254 onWindowStageDestroy() { 255 this.destroySubWindow(); 256 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); 257 } 258 259 onForeground() { 260 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); 261 } 262 263 onBackground() { 264 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); 265 } 266} 267``` 268主窗口代码: 269```ts 270// Index.ets 271import window from '@ohos.window'; 272 273@Entry 274@Component 275struct Index { 276 // 使用@StorageLink将"mainData"与AppStorage中的变量"data"进行双向绑定 277 @StorageLink('data') mainData: number = 1; 278 @StorageLink('window') storWindow:window.WindowStage = null 279 private windowStage = this.storWindow 280 private sub_windowClass = null 281 282 showSubWindow() { 283 // 创建应用子窗口。 284 this.windowStage.createSubWindow("hiSubWindow", (err, data) => { 285 if (err.code) { 286 console.error('Failed to create the subwindow. Cause: ' + JSON.stringify(err)); 287 return; 288 } 289 this.sub_windowClass = data; 290 console.info('Succeeded in creating the subwindow. Data: ' + JSON.stringify(data)); 291 // 子窗口创建成功后,设置子窗口的位置、大小及相关属性等。 292 this.sub_windowClass.moveWindowTo(300, 300, (err) => { 293 if (err.code) { 294 console.error('Failed to move the window. Cause:' + JSON.stringify(err)); 295 return; 296 } 297 console.info('Succeeded in moving the window.'); 298 }); 299 this.sub_windowClass.resize(350, 350, (err) => { 300 if (err.code) { 301 console.error('Failed to change the window size. Cause:' + JSON.stringify(err)); 302 return; 303 } 304 console.info('Succeeded in changing the window size.'); 305 }); 306 // 为子窗口加载对应的目标页面。 307 this.sub_windowClass.setUIContent("pages/SubWindow",(err) => { 308 if (err.code) { 309 console.error('Failed to load the content. Cause:' + JSON.stringify(err)); 310 return; 311 } 312 console.info('Succeeded in loading the content.'); 313 // 显示子窗口。 314 this.sub_windowClass.showWindow((err) => { 315 if (err.code) { 316 console.error('Failed to show the window. Cause: ' + JSON.stringify(err)); 317 return; 318 } 319 console.info('Succeeded in showing the window.'); 320 }); 321 this.sub_windowClass.setWindowBackgroundColor('#E8A027') 322 }); 323 }) 324 } 325 326 build() { 327 Row() { 328 Column() { 329 Text(`AppStorage保存的数据:${this.mainData}`) 330 .margin({bottom:30}) 331 Button('子窗口数据+1') 332 .backgroundColor('#A4AE77') 333 .margin({bottom:30}) 334 .onClick(()=>{ 335 // 点击,storData的值加1 336 this.mainData += 1 337 }) 338 Button('创建子窗口') 339 .backgroundColor('#A4AE77') 340 .onClick(()=>{ 341 // 点击弹出子窗口 342 this.showSubWindow() 343 }) 344 } 345 .width('100%') 346 } 347 .height('100%') 348 } 349} 350``` 351子窗口代码: 352```ts 353// SubWindow.ets 354import window from '@ohos.window'; 355 356interface Position { 357 x: number, 358 y: number 359} 360 361@Entry 362@Component 363struct SubWindow{ 364 // 使用@StorageLink将"subData"与AppStorage中的变量"data"进行双向绑定 365 @StorageLink('data') subData: number = 1; 366 // 创建位置变量,并使用@Watch监听,变量发生变化调用moveWindow方法移动窗口 367 @State @Watch("moveWindow") windowPosition: Position = { x: 0, y: 0 }; 368 private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All }); 369 private subWindow: window.Window 370 // 通过悬浮窗名称“hiSubWindow”获取到创建的悬浮窗 371 aboutToAppear() { 372 this.subWindow = window.findWindow("hiSubWindow") 373 } 374 // 将悬浮窗移动到指定位置 375 moveWindow() { 376 this.subWindow.moveWindowTo(this.windowPosition.x, this.windowPosition.y); 377 } 378 379 build(){ 380 Column(){ 381 Text(`AppStorage保存的数据:${this.subData}`) 382 .fontSize(12) 383 .margin({bottom:10}) 384 Button('主窗口数据+1') 385 .fontSize(12) 386 .backgroundColor('#A4AE77') 387 .onClick(()=>{ 388 // 点击,subData的值加1 389 this.subData += 1 390 }) 391 } 392 .height('100%') 393 .width('100%') 394 .alignItems(HorizontalAlign.Center) 395 .justifyContent(FlexAlign.Center) 396 .gesture( 397 PanGesture(this.panOption) 398 .onActionStart((event: GestureEvent) => { 399 console.info('Pan start'); 400 }) 401 // 发生拖拽时,获取到触摸点的位置,并将位置信息传递给windowPosition 402 .onActionUpdate((event: GestureEvent) => { 403 this.windowPosition.x += event.offsetX; 404 this.windowPosition.y += event.offsetY; 405 }) 406 .onActionEnd(() => { 407 console.info('Pan end'); 408 }) 409 ) 410 } 411} 412``` 413## 参考 414- [窗口开发](../application-dev/windowmanager/application-window-stage.md) 415- [AppStorage:应用全局的UI状态存储](../application-dev/quick-start/arkts-appstorage.md)