1# FrameNode
2
3## Overview
4
5For third-party frameworks with custom frontend definitions, converting specific DSL to ArkUI declarative descriptions is required. This process, which relies on additional data-driven bindings to the [Builder](../quick-start/arkts-builder.md), is complex and can be performance-intensive. Such frameworks typically leverage ArkUI's layout and event handling, as well as basic node operations and customization capabilities. While most components are customized, some built-in components are needed for mixed display. This is where [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md) comes into the picture.
6
7FrameNode represents an entity node in the component tree. When used with custom placeholder containers such as [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md), it allows for mounting and dynamically managing a custom node tree in the containers, including node addition, modification, and removal. Basic FrameNodes enable universal attribute setting, event callback setting, and full customization for measurement, layout, and rendering.
8
9Moreover, the ArkUI framework enables obtaining and traversing proxy FrameNode objects for built-in components, known as proxy nodes, which facilitate UI tree traversal and allow for obtaining specific information about built-in components or registering additional event listeners.
10
11## Creating and Removing Nodes
12
13You can create and remove nodes with **FrameNode**. You can create a custom instance of **FrameNode** using its constructor, and the instance thereby created corresponds to an entity node. You can use the [dispose](../reference/apis-arkui/js-apis-arkui-frameNode.md#dispose12) API in **FrameNode** to break the binding with the entity node.
14
15> **NOTE**
16>
17> - A valid **UIContext** object is required for creating a FrameNode. If no **UIContext** object is provided or if the provided one is invalid, an exception will be thrown during node creation.
18>
19> - Maintain UI context consistency for custom placeholder components to prevent display issues.
20>
21> - **FrameNode** objects are subject to garbage collection (GC) if not retained.
22
23## Checking Whether a Node is Modifiable
24
25Use [isModifiable](../reference/apis-arkui/js-apis-arkui-frameNode.md#ismodifiable12) to check whether the current node is a proxy for a built-in component. If a FrameNode serves as a proxy, it cannot be modified, which means you cannot change its properties or the structure of its child nodes.
26
27## Obtaining the Corresponding RenderNode
28
29Use the [getRenderNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#getrendernode) API to obtain the RenderNode associated with the FrameNode. You can then perform operations on the obtained RenderNode object to dynamically modify the drawing-related properties of the FrameNode. For details about the properties that can be modified, see the RenderNode API documentation.
30
31> **NOTE**
32>
33> - You cannot obtain the RenderNode for a built-in component's proxy FrameNode.
34> 
35> - In **BuilderNode**, you can use [getFrameNode](../reference/apis-arkui/js-apis-arkui-builderNode.md#getframenode) to get the FrameNode object, and then use **getRenderNode** to obtain the RenderNode object of the corresponding root node.
36
37## Operating the Node Tree
38
39With **FrameNode**, you can add, delete, query, and modify nodes, thereby changing the subtree structure of non-proxy nodes; you can also query the parent-child relationships to obtain the results.
40
41> **NOTE**
42>
43> Illegal operations for adding, deleting, or modifying nodes result in exceptions.
44>
45> Proxy nodes obtained through queries can only be used to obtain node information and cannot modify node properties; they do not hold the component entity nodes, meaning they do not affect the lifecycle of the corresponding nodes.
46>
47> Node queries only return UI-related nodes and do not include syntax nodes.
48>
49> In scenarios using custom components, you may query and obtain newly added nodes of the custom components, with the node type being **__Common__**.
50
51```ts
52import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI';
53import { BusinessError } from '@kit.BasicServicesKit';
54
55const TEST_TAG: string = "FrameNode"
56
57class Params {
58  text: string = "this is a text"
59}
60
61@Builder
62function buttonBuilder(params: Params) {
63  Column({ space: 10 }) {
64    Button(params.text)
65      .fontSize(12)
66      .borderRadius(8)
67      .borderWidth(2)
68      .backgroundColor(Color.Orange)
69
70    Button(params.text)
71      .fontSize(12)
72      .borderRadius(8)
73      .borderWidth(2)
74      .backgroundColor(Color.Pink)
75  }
76}
77
78class MyNodeController extends NodeController {
79  public buttonNode: BuilderNode<[Params]> | null = null;
80  public frameNode: FrameNode | null = null;
81  public childList: Array<FrameNode> = new Array<FrameNode>();
82  public rootNode: FrameNode | null = null;
83  private uiContext: UIContext | null = null;
84  private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder);
85
86  makeNode(uiContext: UIContext): FrameNode | null {
87    this.uiContext = uiContext;
88    if (this.rootNode == null) {
89      this.rootNode = new FrameNode(uiContext);
90      this.rootNode.commonAttribute
91        .width("50%")
92        .height(100)
93        .borderWidth(1)
94        .backgroundColor(Color.Gray)
95    }
96
97    if (this.frameNode == null) {
98      this.frameNode = new FrameNode(uiContext);
99      this.frameNode.commonAttribute
100        .width("100%")
101        .height(50)
102        .borderWidth(1)
103        .position({ x: 200, y: 0 })
104        .backgroundColor(Color.Pink);
105      this.rootNode.appendChild(this.frameNode);
106    }
107    if (this.buttonNode == null) {
108      this.buttonNode = new BuilderNode<[Params]>(uiContext);
109      this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" })
110      this.rootNode.appendChild(this.buttonNode.getFrameNode())
111    }
112    return this.rootNode;
113  }
114
115  operationFrameNodeWithFrameNode(frameNode: FrameNode | undefined | null) {
116    if (frameNode) {
117      console.log(TEST_TAG + " get ArkTSNode success.")
118      console.log(TEST_TAG + " check rootNode whether is modifiable " + frameNode.isModifiable());
119    }
120    if (this.uiContext) {
121      let frameNode1 = new FrameNode(this.uiContext);
122      let frameNode2 = new FrameNode(this.uiContext);
123      frameNode1.commonAttribute.size({ width: 50, height: 50 })
124        .backgroundColor(Color.Black)
125        .position({ x: 50, y: 60 })
126      frameNode2.commonAttribute.size({ width: 50, height: 50 })
127        .backgroundColor(Color.Orange)
128        .position({ x: 120, y: 60 })
129      try {
130        frameNode?.appendChild(frameNode1);
131        console.log(TEST_TAG + " appendChild success ");
132      } catch (err) {
133        console.log(TEST_TAG + " appendChild fail :" + (err as BusinessError).code + " : " + (err as BusinessError).message);
134      }
135      try {
136        frameNode?.insertChildAfter(frameNode2, null);
137        console.log(TEST_TAG + " insertChildAfter success ");
138      } catch (err) {
139        console.log(TEST_TAG + " insertChildAfter fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message);
140      }
141      setTimeout(() => {
142        try {
143          frameNode?.removeChild(frameNode?.getChild(0))
144          console.log(TEST_TAG + " removeChild success ");
145        } catch (err) {
146          console.log(TEST_TAG + " removeChild fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message);
147        }
148      }, 2000)
149      setTimeout(() => {
150        try {
151          frameNode?.clearChildren();
152          console.log(TEST_TAG + " clearChildren success ");
153        } catch (err) {
154          console.log(TEST_TAG + " clearChildren fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message);
155        }
156      }, 4000)
157    }
158  }
159
160  testInterfaceAboutSearch(frameNode: FrameNode | undefined | null): string {
161    let result: string = "";
162    if (frameNode) {
163      result = result + `current node is ${frameNode.getNodeType()} \n`;
164      result = result + `parent node is ${frameNode.getParent()?.getNodeType()} \n`;
165      result = result + `child count is ${frameNode.getChildrenCount()} \n`;
166      result = result + `first child node is ${frameNode.getFirstChild()?.getNodeType()} \n`;
167      result = result + `second child node is ${frameNode.getChild(1)?.getNodeType()} \n`;
168      result = result + `previousSibling node is ${frameNode.getPreviousSibling()?.getNodeType()} \n`;
169      result = result + `nextSibling node is ${frameNode.getNextSibling()?.getNodeType()} \n`;
170    }
171    return result;
172  }
173
174  checkAppendChild(parent: FrameNode | undefined | null, child: FrameNode | undefined | null) {
175    try {
176      if (parent && child) {
177        parent.appendChild(child);
178        console.log(TEST_TAG + " appendChild success ");
179      }
180    } catch (err) {
181      console.log(TEST_TAG + " appendChild fail : " + (err as BusinessError).code + " : " + (err as BusinessError).message);
182    }
183  }
184}
185
186@Entry
187@Component
188struct Index {
189  @State index: number = 0;
190  @State result: string = ""
191  private myNodeController: MyNodeController = new MyNodeController();
192
193  build() {
194    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
195      List({ space: 20, initialIndex: 0 }) {
196        ListItem() {
197          Column({ space: 5 }) {
198            Text("Verify the add, delete, and modify features of the FrameNode")
199            Button("Operate Custom FrameNode")
200              .fontSize(16)
201              .width(400)
202              .onClick(() => {
203                // Add, delete, and modify FrameNode child nodes, which is properly implemented.
204                this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.frameNode);
205              })
206            Button("Operate Proxy Node in BuilderNode")
207              .fontSize(16)
208              .width(400)
209              .onClick(() => {
210                // Add, delete, and modify the BuilderNode proxy nodes, which results in an exception.
211                this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.buttonNode?.getFrameNode());
212              })
213            Button("Operate Proxy Node in Built-in Component")
214              .fontSize(16)
215              .width(400)
216              .onClick(() => {
217                // Add, delete, and modify the proxy nodes, which results in an exception.
218                this.myNodeController.operationFrameNodeWithFrameNode(this.myNodeController?.rootNode?.getParent());
219              })
220          }
221        }
222
223        ListItem() {
224          Column({ space: 5 }) {
225            Text("Verify the feature to add subnodes to FrameNode")
226            Button("Add BuilderNode Proxy Node")
227              .fontSize(16)
228              .width(400)
229              .onClick(() => {
230                let buttonNode = new BuilderNode<[Params]>(this.getUIContext());
231                buttonNode.build(wrapBuilder<[Params]>(buttonBuilder), { text: "BUTTON" })
232                this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, buttonNode?.getFrameNode());
233              })
234            Button("Add Built-in Component Proxy Node")
235              .fontSize(16)
236              .width(400)
237              .onClick(() => {
238                this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, this.myNodeController?.rootNode?.getParent());
239              })
240            Button("Add Custom Node with Existing Parent Node")
241              .fontSize(16)
242              .width(400)
243              .onClick(() => {
244                this.myNodeController.checkAppendChild(this.myNodeController?.frameNode, this.myNodeController?.rootNode);
245              })
246          }
247        }
248
249        ListItem() {
250          Column({ space: 5 }) {
251            Text("Verify the query feature of the FrameNode")
252            Button("Operate Custom FrameNode")
253              .fontSize(16)
254              .width(400)
255              .onClick(() => {
256                // Query the FrameNode. The current node is a child of the NodeContainer.
257                this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode);
258                setTimeout(() => {
259                  // Query the FrameNode. The current node is the first child node under rootNode.
260                  this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.frameNode);
261                }, 2000)
262              })
263            Button("Operate Proxy Node in BuilderNode")
264              .fontSize(16)
265              .width(400)
266              .onClick(() => {
267                // Query the BuilderNode proxy nodes. The current node is the Column node within BuilderNode.
268                this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.buttonNode?.getFrameNode());
269              })
270            Button("Operate Proxy Node in Built-in Component")
271              .fontSize(16)
272              .width(400)
273              .onClick(() => {
274                // Query the proxy node. The current node is the NodeContainer.
275                this.result = this.myNodeController.testInterfaceAboutSearch(this.myNodeController?.rootNode?.getParent());
276              })
277          }
278        }
279      }.height("50%")
280
281      Text(`Result: \n${this.result}`)
282        .fontSize(16)
283        .width(400)
284        .height(200)
285        .padding(30)
286        .borderWidth(1)
287      Column() {
288        Text("This is a NodeContainer.")
289          .textAlign(TextAlign.Center)
290          .borderRadius(10)
291          .backgroundColor(0xFFFFFF)
292          .width('100%')
293          .fontSize(16)
294        NodeContainer(this.myNodeController)
295          .borderWidth(1)
296          .width(400)
297          .height(150)
298      }
299    }
300    .padding({ left: 35, right: 35, top: 35, bottom: 35 })
301    .width("100%")
302    .height("100%")
303  }
304}
305```
306
307## Setting Universal Attributes and Event Callbacks
308
309Use the [commonAttribute](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonattribute12) and [commonEvent](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonevent12) objects to set the [universal attributes](../reference/apis-arkui/arkui-ts/ts-universal-attributes-size.md) and [event callbacks](../reference/apis-arkui/arkui-ts/ts-uicommonevent.md), respectively.
310
311> **NOTE**
312> 
313> - Proxy node attributes are immutable. Therefore, **commonAttribute** is ineffective on proxy nodes.
314> 
315> - The custom basic events that you define run in parallel with the events predefined in the built-in components, without overriding them. When two event callbacks are set, the built-in component event callback is prioritized.
316
317```ts
318import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI'
319
320class Params {
321  text: string = "this is a text"
322}
323
324@Builder
325function buttonBuilder(params: Params) {
326  Button(params.text)
327    .fontSize(12)
328    .borderRadius(8)
329    .borderWidth(2)
330    .backgroundColor(Color.Orange)
331    .onClick((event: ClickEvent) => {
332      console.log(`Button ${JSON.stringify(event)}`);
333    })
334}
335
336class MyNodeController extends NodeController {
337  public buttonNode: BuilderNode<[Params]> | null = null;
338  public frameNode: FrameNode | null = null;
339  public rootNode: FrameNode | null = null;
340  private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(buttonBuilder);
341
342  makeNode(uiContext: UIContext): FrameNode | null {
343    if (this.rootNode == null) {
344      this.rootNode = new FrameNode(uiContext);
345      // Modify the attributes of rootNode, which is a custom FrameNode, and the changes take effect.
346      this.rootNode.commonAttribute
347        .width("100%")
348        .height(100)
349        .borderWidth(1)
350        .backgroundColor(Color.Gray)
351    }
352
353    if (this.frameNode == null) {
354      this.frameNode = new FrameNode(uiContext);
355      // Modify the attributes of frameNode, which is a custom FrameNode, and the changes take effect.
356      this.frameNode.commonAttribute
357        .width("50%")
358        .height(50)
359        .borderWidth(1)
360        .backgroundColor(Color.Pink);
361      this.rootNode.appendChild(this.frameNode);
362    }
363    if (this.buttonNode == null) {
364      this.buttonNode = new BuilderNode<[Params]>(uiContext);
365      this.buttonNode.build(this.wrapBuilder, { text: "This is a Button" })
366      // Modify the attributes of the FrameNode obtained from BuilderNode, which is not a custom FrameNode, and the changes do not take effect.
367      this.buttonNode?.getFrameNode()?.commonAttribute.position({ x: 100, y: 100 })
368      this.rootNode.appendChild(this.buttonNode.getFrameNode())
369    }
370    return this.rootNode;
371  }
372
373  modifyNode(frameNode: FrameNode | null | undefined, sizeValue: SizeOptions, positionValue: Position) {
374    if (frameNode) {
375      frameNode.commonAttribute.size(sizeValue).position(positionValue);
376    }
377  }
378
379  addClickEvent(frameNode: FrameNode | null | undefined) {
380    if (frameNode) {
381      frameNode.commonEvent.setOnClick((event: ClickEvent) => {
382        console.log(`FrameNode ${JSON.stringify(event)}`);
383      })
384    }
385  }
386}
387
388@Entry
389@Component
390struct Index {
391  private myNodeController: MyNodeController = new MyNodeController();
392
393  build() {
394    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
395      Column({ space: 10 }) {
396        Text("Modify the universal node attributes: width and height.")
397        Button("modify ArkTS-FrameNode")
398          .onClick(() => {
399            // The object obtained is the FrameNode created on the current page, which can be modified. That is, the size and position of the node can be changed.
400            console.log("Check whether the node can be modified " + this.myNodeController?.frameNode
401            ?.isModifiable());
402            this.myNodeController.modifyNode(this.myNodeController?.frameNode, { width: 150, height: 100 }, {
403              x: 100,
404              y: 0
405            })
406          })
407        Button("modify FrameNode get by BuilderNode")
408          .onClick(() => {
409            // The object obtained is the root node of the BuilderNode on the current page, which cannot be modified. That is, the size and position of the node remain unchanged.
410            console.log("Check the weather the node can be modified " + this.myNodeController?.buttonNode?.getFrameNode()
411            ?.isModifiable());
412            this.myNodeController.modifyNode(this.myNodeController?.buttonNode?.getFrameNode(), {
413              width: 100,
414              height: 100
415            }, { x: 50, y: 50 })
416          })
417        Button("modify proxyFrameNode get by search")
418          .onClick(() => {
419            // The rootNode object calling getParent() obtains the NodeContainer node on the current page, which cannot be modified. That is, the size and position of the node remain unchanged.
420            console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent()
421            ?.isModifiable());
422            this.myNodeController.modifyNode(this.myNodeController?.rootNode?.getParent(), {
423              width: 500,
424              height: 500
425            }, {
426              x: 0,
427              y: 0
428            })
429          })
430      }.padding({ left: 35, right: 35, top: 35, bottom: 35 })
431
432      Column({ space: 10 }) {
433        Text("Modify the node click event.")
434        Button("add click event to ArkTS-FrameNode")
435          .onClick(() => {
436            // The object obtained is the FrameNode created on the current page, to which click events can be added.
437            // The added click event participates in event competition, meaning the click event will be consumed by this node and will no longer bubble up to the parent component.
438            console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent()
439            ?.isModifiable());
440            this.myNodeController.addClickEvent(this.myNodeController?.frameNode)
441          })
442        Button("add click event to FrameNode get by BuilderNode")
443          .onClick(() => {
444            // The object obtained is the root node of the BuilderNode on the current page, to which click events can be added.
445            // When the button is clicked, the click event callback set through the built-in component API is called first, followed by the click listener added through commonEvent.
446            console.log("Check the weather the node can be modified " + this.myNodeController?.buttonNode?.getFrameNode()
447            ?.isModifiable());
448            this.myNodeController.addClickEvent(this.myNodeController?.buttonNode?.getFrameNode())
449          })
450        Button("add click event to proxyFrameNode get by search")
451          .onClick(() => {
452            // The rootNode object calling getParent() obtains the NodeContainer node on the current page, to which click events can be added.
453            console.log("Check the weather the node can be modified " + this.myNodeController?.rootNode?.getParent()
454            ?.isModifiable());
455            this.myNodeController.addClickEvent(this.myNodeController?.rootNode?.getParent());
456          })
457      }.padding({ left: 35, right: 35, top: 35, bottom: 35 })
458
459      NodeContainer(this.myNodeController)
460        .borderWidth(1)
461        .width("100%")
462        .height(100)
463        .onClick((event: ClickEvent) => {
464          console.log(`NodeContainer ${JSON.stringify(event)}`);
465        })
466    }
467    .padding({ left: 35, right: 35, top: 35, bottom: 35 })
468    .width("100%")
469    .height("100%")
470  }
471}
472```
473
474## Implementing Custom Measurement, Layout, and Drawing
475
476By overriding the [onDraw](../reference/apis-arkui/js-apis-arkui-frameNode.md#ondraw12) API, you can customize the drawing content of the FrameNode. Use the [invalidate](../reference/apis-arkui/js-apis-arkui-frameNode.md#invalidate12) API to manually trigger a redraw of the node.
477
478By overriding the [onMeasure](../reference/apis-arkui/js-apis-arkui-frameNode.md#onmeasure12) API, you can customize how the FrameNode measures its size. Use [measure](../reference/apis-arkui/js-apis-arkui-frameNode.md#measure12) to proactively pass layout constraints to initiate a remeasurement.
479
480By overriding the [onLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#onlayout12) API, you can customize the layout of the FrameNode. Use [layout](../reference/apis-arkui/js-apis-arkui-frameNode.md#layout12) to proactively pass position information and initiate a re-layout.
481
482Use [setNeedsLayout](../reference/apis-arkui/js-apis-arkui-frameNode.md#setneedslayout12) to mark the current node and trigger a re-layout in the next frame.
483
484> **NOTE**
485> 
486> - After a node is disposed and unbound, the FrameNode no longer represents an entity node. In this case, the **invalidate** call cannot update the previously bound node.
487> 
488> - Custom drawings made through the **onDraw** API cannot exceed the component's size.
489
490```ts
491import { DrawContext, FrameNode, NodeController, Position, Size, UIContext, LayoutConstraint } from '@kit.ArkUI';
492import { drawing } from '@kit.ArkGraphics2D';
493
494function GetChildLayoutConstraint(constraint: LayoutConstraint, child: FrameNode): LayoutConstraint {
495  const size = child.getUserConfigSize();
496  const width = Math.max(
497    Math.min(constraint.maxSize.width, size.width.value),
498    constraint.minSize.width
499  );
500  const height = Math.max(
501    Math.min(constraint.maxSize.height, size.height.value),
502    constraint.minSize.height
503  );
504  const finalSize: Size = { width, height };
505  const res: LayoutConstraint = {
506    maxSize: finalSize,
507    minSize: finalSize,
508    percentReference: finalSize
509  };
510
511  return res;
512}
513
514class MyFrameNode extends FrameNode {
515  public width: number = 100;
516  public offsetY: number = 0;
517  private space: number = 1;
518
519  onMeasure(constraint: LayoutConstraint): void {
520    let sizeRes: Size = { width: vp2px(100), height: vp2px(100) };
521    for (let i = 0;i < this.getChildrenCount(); i++) {
522      let child = this.getChild(i);
523      if (child) {
524        let childConstraint = GetChildLayoutConstraint(constraint, child);
525        child.measure(childConstraint);
526        let size = child.getMeasuredSize();
527        sizeRes.height += size.height + this.space;
528        sizeRes.width = Math.max(sizeRes.width, size.width);
529      }
530    }
531    this.setMeasuredSize(sizeRes);
532  }
533
534  onLayout(position: Position): void {
535    let y = 0;
536    for (let i = 0;i < this.getChildrenCount(); i++) {
537      let child = this.getChild(i);
538      if (child) {
539        child.layout({
540          x: vp2px(100),
541          y: vp2px(this.offsetY)
542        });
543        y += child.getMeasuredSize().height + this.space;
544      }
545    }
546    this.setLayoutPosition(position);
547  }
548
549  onDraw(context: DrawContext) {
550    const canvas = context.canvas;
551    const pen = new drawing.Pen();
552    pen.setStrokeWidth(15);
553    pen.setColor({ alpha: 255, red: 255, green: 0, blue: 0 });
554    canvas.attachPen(pen);
555    canvas.drawRect({  
556      left: 50, 
557      right: this.width + 50, 
558      top: 50, 
559      bottom: this.width + 50,
560    });
561    canvas.detachPen();
562  }
563
564  addWidth() {
565    this.width = (this.width + 10) % 50 + 100;
566  }
567}
568
569class MyNodeController extends NodeController {
570  public rootNode: MyFrameNode | null = null;
571
572  makeNode(context: UIContext): FrameNode | null {
573    this.rootNode = new MyFrameNode(context);
574    this.rootNode?.commonAttribute?.size({ width: 100, height: 100 }).backgroundColor(Color.Green);
575    let frameNode: FrameNode = new FrameNode(context);
576    this.rootNode.appendChild(frameNode);
577    frameNode.commonAttribute.width(10).height(10).backgroundColor(Color.Pink);
578    return this.rootNode;
579  }
580}
581
582@Entry
583@Component
584struct Index {
585  private nodeController: MyNodeController = new MyNodeController();
586
587  build() {
588    Row() {
589      Column() {
590        NodeContainer(this.nodeController)
591          .width('100%')
592          .height(200)
593          .backgroundColor('#FFF0F0F0')
594        Button('Invalidate')
595          .margin(10)
596          .onClick(() => {
597            this.nodeController?.rootNode?.addWidth();
598            this.nodeController?.rootNode?.invalidate();
599          })
600        Button('UpdateLayout')
601          .onClick(() => {
602            let node = this.nodeController.rootNode;
603            node!.offsetY = (node!.offsetY + 10) % 110;
604            this.nodeController?.rootNode?.setNeedsLayout();
605          })
606      }
607      .width('100%')
608      .height('100%')
609    }
610    .height('100%')
611  }
612}
613```
614
615## Searching for Nodes and Obtaining Basic Information
616
617**FrameNode** provides APIs for obtaining basic information about an entity node. For details about the returned information, see the FrameNode API documentation.
618
619To obtain a FrameNode, use any of the following methods:
620
6211. Use [getFrameNodeById](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyid12).
622
6232. Use [getFrameNodeByUniqueId](../reference/apis-arkui/js-apis-arkui-UIContext.md#getframenodebyuniqueid12).
624
6253. Use an [observer](../reference/apis-arkui/js-apis-arkui-observer.md).
626
627> **NOTE**
628>
629> Currently, the following information can be obtained:
630>
631> - Node size: **getMeasuredSize**, **getUserConfigSize**
632> 
633> - Layout information: **getPositionToWindow**, **getPositionToParent**, **getLayoutPosition**, **getUserConfigBorderWidth**, **getUserConfigPadding**, **getUserConfigMargin**
634> 
635> - Node information: **getId**, **getUniqueId**, **getNodeType**, **getOpacity**, **isVisible**, **isClipToFrame**, **isAttached**, **getInspectorInfo**, **getCustomProperty**
636