Delphiのコンポーネント読み取りメカニズム(i)
1.ストリーミングオブジェクト(ストリーム)と読み取りワイトオブジェクト(ファイラー)の紹介
オブジェクト指向プログラミングでは、オブジェクトベースのデータ管理は非常に重要な位置を占めています。 Delphiでは、オブジェクトベースのデータ管理のサポート方法は、その主要な機能の1つです。
Delphiは、オブジェクト指向の視覚設計とオブジェクト指向の言語を組み合わせた統合開発環境です。 Delphiのコアはコンポーネントです。コンポーネントはオブジェクトの一種です。 Delphiアプリケーションは完全にコンポーネントによって構築されているため、高性能Delphiアプリケーションの開発には、オブジェクトベースのデータ管理技術が必然的に含まれます。
オブジェクトベースのデータ管理には、次の2つの側面が含まれます。
●オブジェクトを使用してデータを管理します
●さまざまなデータオブジェクトの管理(オブジェクトとコンポーネントを含む)
Delphiは、オブジェクトベースのデータ管理クラスをストリーミングオブジェクト(ストリーム)およびファイラーオブジェクト(ファイラー)に属性し、Visual Componentクラスライブラリ(VCL)のすべての側面に属性します。それらは、メモリ、外部メモリ、およびWindowsリソースのオブジェクトを管理するための豊富な機能を提供します。
ストリーミングオブジェクトとも呼ばれるストリームオブジェクトは、TSTREAM、THANDLESTREAM、TFILESTREAM、TMEMORYSTREAM、TRESOURCESTREAM、TBLOBSTREAMの一般用語です。これらは、さまざまなメディアにデータを保存し、メモリ、メモリ、データベースフィールドのさまざまなデータ型(オブジェクトやコンポーネントを含む)の管理操作をオブジェクトメソッドに抽象化する機能を表し、オブジェクト指向のテクノロジーの利点を最大限に活用することができます。これのうち、アプリケーションはさまざまなストリームオブジェクトのデータをかなり簡単にコピーできます。
読み取りと書き込みオブジェクト(ファイラー)には、tfilerオブジェクト、トレッダーオブジェクト、Twriterオブジェクトが含まれます。 TFILERオブジェクトは、ファイルの読み取りと書き込みの基本オブジェクトであり、アプリケーションのTreaderとTwriterの主な用途です。 TreaderオブジェクトとTwriterオブジェクトはどちらも、tfilerオブジェクトから直接継承されます。 TFILERオブジェクトは、ファイラーオブジェクトの基本的なプロパティとメソッドを定義します。
ファイラーオブジェクトは、主に2つの主要な機能を実行します。
●フォームファイルのフォームファイルとコンポーネントにアクセスします
●データバッファリングを提供して、データの読み取りと書き込み操作を高速化する
ストリーミングオブジェクトを知覚的に理解し、オブジェクトを読み書きするために、まず例を見てみましょう。
a)ファイルを書きます
手順tfomr1.writedata(sender:tobject);
var
FileStream:tfilestream;
MyWriter:Twriter;
I:整数
始める
fileStream:= tfilestream.create( 'c:/test.txt'、fmopenwrite); //ファイルストリームオブジェクトを作成します
MyWriter:= Twriter.Create(FileStream、1024);
mywriter.writeListbegin; //書き込みフラグ
i:= 0 to memo1.lines.count-1 do
mywriter.writestring(memo1.lines [);
mywriter.writeListend; //リストエンドフラグ
fileStream.seek(0、sofrombegining);
mywriter.free; // mywriterオブジェクトをリリースします
FileStream.Free; // Filestreamオブジェクトをリリースします
終わり;
b)ファイルを読み取ります
手順tform1.readdata(sender:tobject);
var
FileStream:tfilestream;
MyReader:Treader;
始める
fileStream:= tfilestream.create( 'c:/test.txt'、fmopenread);
myReader:= treader.create(FileStream、1024);
myreader.readlistbegin;
memo1.lines.clear; // memo1コンポーネントのテキストコンテンツをクリアします
myReader.EndofListではありませんが、Treader:Endoflistの方法に注意してください
始める
memo1.lines.add(myreader.readstring);
終わり;
myreader.readlistend;
myreader.free; // myReaderオブジェクトをリリースします
FileStream.Free; // Filestreamオブジェクトをリリースします
終わり;
上記の2つのプロセスは、執筆プロセス用の1つ、もう1つは読み取りプロセス用です。ライティングプロセスでは、Twriterを使用して、Tfilestreamを使用してディスクに保存されたバイナリファイルとしてメモにコンテンツ(テキスト情報)を保存します。読み取りプロセスは、Treaderを介して執筆プロセスとは正反対です。プログラムを実行すると、読み取りプロセスが執筆プロセスで保存された情報を忠実に復元することがわかります。
次の図は、データオブジェクト(オブジェクトとコンポーネントを含む)、ストリーミングオブジェクト、および読み取りおよび書き込みオブジェクト間の関係について説明しています。
図(1)
TFILERオブジェクト、トレッダーオブジェクト、Twriterオブジェクトなどの読み取りおよび書き込みオブジェクトは、通常、コンポーネントの状態を読み書きするために使用されることはほとんどありません。コンポーネントメカニズムを書きます。
ストリーミングオブジェクトストリームの場合、多くの参照資料が詳細に導入されていますが、TFILERオブジェクト、トレッダーオブジェクト、TWRITERオブジェクトの参照資料、特にコンポーネントの読み取りメカニズムはまれです。 。
2。オブジェクト(ファイラー)とコンポーネントの読み取りメカニズムの読み取りと書き込みメカニズム
ファイラーオブジェクトは、主にフォームファイルのフォームファイルとコンポーネントにアクセスするために使用されます。ファイラーオブジェクトを明確に理解するには、Delphiフォームファイル(DFMファイル)の構造について明確にする必要があります。
DFMファイルは、Delphiストレージフォームに使用されます。フォームは、Delphi Visualプログラミングの中核です。フォームは、Delphiアプリケーションのウィンドウに対応し、フォームの視覚コンポーネントはウィンドウ内のインターフェイス要素に対応し、TtimerやTapendialogなどの非視覚コンポーネントはDelphiアプリケーションの特定の関数に対応します。 Delphiアプリケーションの設計は、実際にはフォームの設計に集中しています。したがって、DFMファイルは、Delphiアプリケーションの設計において非常に重要な位置も占めています。フォームの独自のプロパティを含むフォームのすべての要素は、DFMファイルに含まれています。
Delphiアプリケーションウィンドウでは、インターフェイス要素は所有権関係によって相互に接続されているため、ツリーの構造もDFMファイルで編成されています。 DFMファイルは、テキストに物理的に保存されています(以前はDelphi2.0のバイナリファイルとして保存されていました)、論理的には、各コンポーネントの関係をツリー構造に配置します。このテキストから、フォームのツリー構造を見ることができます。 DFMファイルのコンテンツは次のとおりです。
オブジェクトフォーム1:tform1
左= 197
TOP = 124
...
pixelsperinch = 96
textheight = 13
オブジェクトボタン:Tbutton
左= 272
...
キャプション= 'button1'
taborder = 0
終わり
オブジェクトパネル:Tpanel
左= 120
...
キャプション= 'panel1'
taborder = 1
オブジェクトチェックボックス:tcheckbox
左= 104
...
キャプション= 'Checkbox1'
taborder = 0
終わり
終わり
終わり
このDFMファイルは、ストリーミングオブジェクトストリームを介してTwriterによって生成されます。また、この記事では、この回心プロセスはテキスト情報ではありません。 。
プログラムの実行を開始すると、トレダーはストリームオブジェクトストリームを介してフォームとコンポーネントを読み取ります。デルフィがプログラムをコンパイルすると、コンパイル{$ r *.dfm}を使用してコンパイルを使用して実行可能ファイルにdfmファイル情報をコンパイルするため命令{$ r *.dfm}。
TreaderとTwriterは、オブジェクトPascalのほとんどの標準データ型を読み書きするだけでなく、リストやバリアントなどの高度なタイプ、さらにはPerpertiesやコンポーネントの読み取りや書き込みを読み書きすることもできます。ただし、トレッダーとトゥリッター自体は実際には非常に限られた機能を提供し、実際の作業のほとんどは非常に強力なクラスであるTSTREAMによって行われます。言い換えれば、TreaderとTwriterは実際には単なるツールであり、特定の読み取り操作の読み取りと書き込みの方法のみを担当しています。
TfiLerはトレッダーとTwriterの公的祖先クラスであるため、TreaderとTwriterを理解するために、Tfilerから始めます。
tfiler
まず、tfilerクラスの定義を見てみましょう。
tfiler = class(tobject)
プライベート
fstream:tstream;
fbuffer:ポインター;
fbufsize:整数;
fbufpos:整数;
fbufend:整数;
froot:tcomponent;
flookuproot:tcomponent;
Fancestor:tpersistent;
fignorechildren:boolean;
保護されています
手順SetRoot(値:Tomponent);
公共
Constructor create(stream:tstream; bufsize:integer);
破壊者はオーバーライドします。
手順DefineProperty(const name:string;
readdata:treaderproc;
hasdata:boolean);
手順DefineBinaryProperty(const name:string;
readdata、writedata:tstreamproc;
hasdata:boolean);
手順フラッシュバッファー。
プロパティルート:tcomponent read read froot write setroot;
プロパティLookupRoot:TCOMPONENT READ FLOOKUPROOT;
プロパティの祖先:Tpersistent読み取りFancestor Write Fancestor;
財産Ingorechildren:boolean read fignorechildren write fignorechildren;
終わり;
TFILERオブジェクトは、コンポーネントストレージに使用される基本的なプロパティとメソッドを定義するTreaderとTwriterの抽象クラスです。ルート属性を定義します。ストリームオブジェクトによって作られます。したがって、ストリームオブジェクトにアクセスできるメディアである限り、コンポーネントにファイラーオブジェクトからアクセスできます。
TFILERオブジェクトには、プロパティを定義する2つのパブリックメソッドも提供します。PropertyとDefineBinaryPropertyを定義します。これにより、コンポーネントの公開された部分で定義されていないプロパティを読み取りおよび書き込むことができます。以下のこれら2つの方法に焦点を当てましょう。
defineProperty()メソッドは、文字列、整数、ブール人、文字、フローティングポイント、列挙などの標準データ型を持続するために使用されます。
DefinePropertyメソッドで。名前パラメーターは、DFMファイルに書き込まれる属性の名前を指定します。これは、クラスの公開されている部分では定義されていません。
ReadDataおよびWritedataパラメーターは、オブジェクトにアクセスするときに必要なデータを読み書きする方法を指定します。 ReadDataパラメーターの種類と手書きパラメーターは、それぞれTreaderProcとTwriterProcです。これらの2つのタイプは次のように宣言されています。
TreaderProc = Objectの手順(Reader:Treader);
TwriterProc = Objectの手順(writer:twriter);
Hasdataパラメーターは、プロパティに実行時に保存するデータがあるかどうかを決定します。
DefineBinaryPropertyメソッドは、definePropertyと多くの類似点を持っています。
以下のこれら2つの方法の使用について説明しましょう。
フォームにTtimerなどの非視覚コンポーネントを配置します。Ttimerはまだ元の場所にあることがわかりましたが、Ttimerには左属性がありません。 ?
このフォームのDFMファイルを開くと、次のようないくつかの行が表示されます。
オブジェクトタイマー1:ttimer
左= 184
TOP = 149
終わり
Delphiのストリーミングシステムは公開されたデータのみを保存できますが、Ttimerは左属性と最上位属性を公開していません。これらのデータはどのように保存されますか?
TTIMERは、tcomponentの派生クラスです。そのような機能があることがわかりました。
手順tcomponent.defineProperties(ファイラー:tfiler);
var
祖先:tcomponent;
情報:Longint;
始める
情報:= 0;
祖先:= tcomponent(filer.ancestor);
祖先<> nilの場合、情報:= ancestor.fdesigninfo;
filer.defineproperty( 'left'、readleft、writeleft、
longrec(fdesigninfo).lo <> longrec(info).lo);
filer.defineProperty( 'top'、readtop、writetop、
longrec(fdesigninfo).hi <> longrec(info).hi);
終わり;
TomponentのDefinePropertiesは、先祖クラスを上書きする仮想方法であり、TPERSISTENTクラスでは、この方法は空の仮想方法です。
DefinePropertiesメソッドでは、ファイラーオブジェクトがプロパティを定義する場合、祖先のみを参照しています。価値。 tfiLerの定義プロパティ法を呼び出し、左と上位のプロパティを読み取り、書き留めるために、readleft、writeleft、readtop、writeTopメソッドを定義します。
したがって、Tomponentから派生したコンポーネントは、たとえ左属性と上位属性がない場合でも、DFMファイルにストリーミングするときに2つのそのようなプロパティを持ちます。
データを検索する過程で、コンポーネントの読み取りメカニズムを含むデータはほとんどありませんでした。コンポーネントライティングプロセスは、デザイン段階でDelphiのIDEによって完了するため、実行プロセスのために追跡することはできません。したがって、著者は、プログラム操作中に元のVCLコードを追跡することにより、コンポーネントの読みメカニズムを理解し、読み取りメカニズムとTwriterを介してコンポーネントのライティングメカニズムを分析します。したがって、以下では、この思考プロセスに従ってコンポーネントの読み取りメカニズムを説明し、最初にトレダー、次にTwriterについて話します。
トレッダー
まず、Delphiのプロジェクトファイルを見てみると、次のようなコードの数行があります。
始める
Application.Initialize;
application.createform(tform1、form1);
application.run;
終わり。
これはデルファイプログラムの入り口です。これらのコードの意味を配置するだけです。InitializeApplications.createform(tform1、form1)を実行します。 。
私たちが今最も気にしているのは、フォームを作成するという文です。フォームのフォームとコンポーネントはどのように作成されていますか?前述のように、フォーム自体のプロパティを含む形式のすべてのコンポーネントは、DFMファイルに含まれています。実行可能ファイルに。したがって、フォームを作成するときは、DFM情報を読む必要があります。
プログラムを段階的にフォローすることで、プログラムがフォームの作成中にReadRootComponentメソッドをTreaderのメソッドと呼ぶことがわかります。この方法の目的は、ルートコンポーネントとそれが持っているすべてのコンポーネントを読み取ることです。この方法の実装を見てみましょう。
function treader.readRootComponent(root:tcomponent):tcomponent;
...
始める
readsignature;
結果:= nil;
GlobalNamesPace.beginWrite;
試す
試す
readprefix(フラグ、i);
root = nilの場合
始める
結果:= tcomponentclass(findclass(readstr))。create(nil);
result.name:= readstr;
ENSEを終了します
始める
結果:= root;
readstr;
result.componentStateでcsdesingingの場合
readstr else
始める
含める(result.fcomponentState、csloading);
含める(result.fcomponentState、csreading);
result.name:= induniquename(readstr);
終わり;
終わり;
froot:= results;
ffinder:= tclassfinder.create(tpersistentclass(result.classtype)、true);
試す
flookuproot:= results;
g:= globalloaded;
g <> nilの場合
フロード:= g else
フロード:= tlist.create;
試す
floaded.indexof(froot)<0の場合
floaded.add(froot);
Fowner:= Froot;
含める(froot.fcomponentState、csloading);
含める(froot.fcomponentState、csreading);
froot.readstate(self);
除外(froot.fcomponentState、csreading);
g = nilの場合
i:= 0 to floaded.count -1 do tcomponent(floaded [i])。ロード;
ついに
g = nilの場合、フローしてフリー;
フロード:= nil;
終わり;
ついに
ffinder.free;
終わり;
...
ついに
GlobalNamesPace.Endwrite;
終わり;
終わり;
ReadRootComponentは、最初にreadsignatureを呼び出して、Fileer Objectタグ( 'TPF0')を読み取ります。オブジェクトをロードする前にタグを検出すると、過失や効果のないまたは時代遅れのデータ読み取りを防ぐことができます。
ReadPrefix(フラグ、i)を見てみましょう。書き込みオブジェクトは、コンポーネントの前に2つの値を記録します2番目の値は、祖先形式で作成された順序を示します。
次に、ルートパラメーターがゼロの場合、readStrによってクラス名が読み取られ、その他のコンポーネントがストリームから読み取られ、名前のプロパティの一意性が判断されます。 。
froot.readstate(self);
これは非常に重要な文です。このReadStateメソッドはTomponentメソッドですが、実際には、この方法の実装が最終的に次のようになりました。
手順treader.readdatainner(instance:tcomponent);
var
古い、オールドオーナー:tcomponent;
始める
endoflistではありませんが、readproperty(instance);
readListend;
OldParent:=親;
オールドオーナー:=所有者;
親:= instance.getChildParent;
試す
所有者:= instance.getChildowner;
割り当てられていない場合(所有者)、所有者:= root;
endoflistではありませんが、readComponent(nil);
readListend;
ついに
親:= oldParent;
所有者:= Oldowner;
終わり;
終わり;
このコード行があります:
endoflistではありませんが、readproperty(instance);
これは、ルートコンポーネントのプロパティを読み取るために使用されます。前述のように、コンポーネント自体の公開されたプロパティと、TTIMERの左と上部などの公開されていないプロパティがあります。これらの2つの異なるプロパティについては、このアイデアを検証するために、2つの異なる読み取り方法が必要です。
手順treader.readproperty(ainstance:tpersistent);
...
始める
...
propinfo:= getPropinfo(instance.classinfo、fpropname);
propinfo <> nilの場合、readpropvalue(instance、propinfo)をreadpropvalue(propinfo)
始める
{定義されたプロパティのエラーから確実に回復することはできません}
fcanhandleExcepts:= false;
instance.defineProperties(self);
fcanHandleExcepts:= true;
fpropname <> ''の場合
PropertyError(fpropname);
終わり;
...
終わり;
スペースを保存するために、いくつかのコードが省略されています。
propinfo:= getPropinfo(instance.classinfo、fpropname);
このコードは、公開されているプロパティfpropnameの情報を取得するためです。次のコードから、属性情報が空でない場合、属性値はReadPropValueメソッドを介して読み取り、ReadPropValueメソッドはRTTI関数を介して属性値を読み取ることがわかります。属性情報が空の場合、属性fpropNameが公開されていないことを意味し、別のメカニズムを通して読み取る必要があります。これは、次のように上記のDefinePropertiesメソッドです。
instance.defineProperties(self);
この方法は、実際にはトレッダーのdefinePropertyメソッドを呼び出します。
手順treader.defineProperty(const name:string;
ReadData:TreaderProc;
始める
sametext(name、fpropname)と割り当て(readdata)の場合
始める
readdata(self);
fpropname:= '';
終わり;
終わり;
最初に、読み取り属性名がプリセット属性名と同じかどうかを比較し、読み取り方法readDataが空でない場合は、readDataメソッドを呼び出して属性値を読み取ります。
OK、ルートコンポーネントが読み取られており、次のステップはルートコンポーネントが所有するコンポーネントを読むことです。もう一度この方法を見てみましょう:
手順treader.readdatainner(instance:tcomponent);
この方法に従ってコードがあります。
endoflistではありませんが、readComponent(nil);
これはまさに子供のコンポーネントを読むために使用されるものです。子コンポーネントの読み取りメカニズムは、上記で紹介したルートコンポーネントの読み取りと同じであり、これは木の深いトラバーサルです。
これまでのところ、コンポーネントリーディングメカニズムが導入されています。
コンポーネントライティングメカニズムを見てみましょう。フォームにコンポーネントを追加すると、その関連プロパティがDFMファイルに保存され、このプロセスはTwriterによって行われます。
ØTwriter
Twriterオブジェクトは、データをストリームに書き込む瞬時のファイラーオブジェクトです。 Twriterオブジェクトは、tfilerから継承されたメソッドを上書きすることに加えて、さまざまなデータタイプ(整数、文字列、コンポーネントなど)を追加することに直接継承されます。
TWRITERオブジェクトは、さまざまなタイプのデータをストリームに記述するための多くの方法を提供します。 したがって、Twriterオブジェクトの実装とアプリケーションの方法をマスターするには、データを保存するライターオブジェクトの形式を理解する必要があります。
最初に注意すべきことは、各ファイラーオブジェクトストリームにファイラーオブジェクトタグが含まれていることです。このタグは4バイトを占有し、その値は「TPF0」です。ファイラーオブジェクトは、WriteSignatureおよびReadSignatureメソッドのタグにアクセスします。このタグは、主に読者オブジェクトがデータ(コンポーネントなど)を読み取るときに読み取り操作をガイドするために使用されます。
第二に、ライターオブジェクトは、データを保存する前にバイトフラグビットを残して、後で保存されるデータの種類を示す必要があります。このバイトは、タイプtValueetypeの値です。 TValueTypeは、1つのバイトスペースを占める列挙タイプであり、その定義は次のとおりです。
tvalueetype =(バヌル、バリスト、vaint8、vaint16、vaint32、vaentended、vastring、vaident、
Vafalse、Vatrue、Vabinary、Vaset、Valstring、Vanil、Vacollection);
したがって、ライターオブジェクトの各データ作成方法の実装では、最初にフラグビットを書き、次に対応するデータを記述する必要があります。読み取りデータ。 Valistロゴには、同じタイプの一連のアイテムを識別するために使用されます。したがって、ライターオブジェクトにいくつかの連続したアイテムを書くとき、最初にヴァリストフラグを書き、データ項目を書いた後、これらのデータを読むとき、readlistbeginで開始し、readlistendで終了します。中央のendoflist関数を使用して、バヌルフラグがあるかどうかを判断します。
Twriter Writedataの非常に重要な方法を見てみましょう。
手順twriter.writedata(instance:tcomponent);
...
始める
...
writeprefix(flags、fchildpos);
UsequalifiedNamesの場合
writestr(gettypedata(ptypeinfo(instance.classtype.classinfo))。unitname + '。' + instance.classname)
それ以外
writestr(instance.classname);
writestr(instance.name);
プロパティポジション:=位置;
if(fanceStorlist <> nil)および(fanceStorpos <fanceStorlist.count)then
始める
祖先<> nil then inc(FanceStorpos)の場合;
Inc(fchildpos);
終わり;
WriteProperties(インスタンス);
WriteListend;
...
終わり;
writedataメソッドから、DFMファイル情報を生成する一般的な写真を見ることができます。最初にコンポーネントの前にフラグ(プレフィックス)を書き、次にクラス名とインスタンス名を書きます。次に、次のような文があります。
WriteProperties(インスタンス);
これは、コンポーネントのプロパティの書き込みに使用されます。前述のように、DFMファイルには、公開された属性と非出版方法があります。 writepropertiesの実装を見てみましょう。
手順twriter.writeproperties(instance:tpersistent);
...
始める
count:= gettypedata(instance.classinfo)^。propcount;
カウント> 0の場合
始める
getMem(proplist、count * sizeof(pointer));
試す
getPropinfos(instance.classinfo、proplist);
i:= 0 to count -1 do
始める
propinfo:= proplist^[i];
propinfo = nilの場合
壊す;
ifsstoredProp(instance、propinfo)の場合
writeproperty(instance、propinfo);
終わり;
ついに
Freemem(Proplist、count * sizeof(pointer));
終わり;
終わり;
instance.defineProperties(self);
終わり;
次のコードをご覧ください。
ifsstoredProp(instance、propinfo)の場合
writeproperty(instance、propinfo);
この関数は、資産を保存する必要がある場合は、プロパティを保存する必要があるかどうかを決定し、プロパティを保存する必要があります。
公開されたプロパティを保存した後、公開されていないプロパティを保存する必要があります。
instance.defineProperties(self);
DefinePropertiesの実装は以前に言及されています。
OK、これまでのところ、まだ質問があります。ルートコンポーネントが所有する子コンポーネントはどのように保存されますか? writedataメソッドを見てみましょう(この方法は前に言及しました):
手順twriter.writedata(instance:tcomponent);
...
始める
...
無知でない場合
試す
if(fancestor <> nil)および(Fancestorはtcomponent)
始める
if(fancestorはtcomponent)と(tcomponentのcsinline(Fancestor).componentState)
Frootancestor:= tcomponent(Fancestor);
FanceStorlist:= tlist.create;
tcomponent(Fancestor).getChildren(addancestor、frootancestor);
終わり;
instance.componentStateのcsinlineの場合
froot:= instance;
instance.getChildren(writecomponent、froot);
ついに
FanceStorlist.Free;
終わり;
終わり;
IgnoreChildrenプロパティにより、作家オブジェクトは、コンポーネントが所有する子コンポーネントを保存せずにコンポーネントを保存できます。 IgnoreChildrenプロパティが真である場合、Writerオブジェクトは、コンポーネントを保存するときに持つ子コンポーネントを保存しません。それ以外の場合、サブコンポーネントが保存されます。
instance.getChildren(writecomponent、froot);
これは、サブコンポーネントを作成するための最も重要な文です。これは、writecomponentメソッドをコールバック関数として使用します。ルートコンポーネントがサブコンポーネントを持っている場合、writecomponentを使用してサブコンポーネントを使用します。このように、DFMファイルに表示されるのは、ツリーのようなコンポーネント構造です。