Afficher un arbre dans une fenêtre en C#

Nous avons vu comment récupérer les informations d'une classe par réflexion, et comment peupler un arbre avec ces données, et nous avons vu comment afficher ces arbres dans la console. Nous avons aussi vu comment créer une fenêtre pour notre application, et comment sélectionner le fichier à analyser.

Maintenant, nous allons voir comment afficher les arbres dans notre fenêtre.

Ajouter un TreeView

Nous allons utiliser un composant TreeView, qui appartient à System.Windows.Forms.

Dans l'onglet Solution Explorer de Visual Studio, nous devons sélectionner notre fichier Form1.cs, qui s'ouvrira par défaut en mode design.

Dans l'onglet Toolbox, nous pouvons sélectionner un composant TreeView, et le glisser à l'aide de la souris (drag and drop) directement sur la représentation de la fenêtre. Comme dans le cas d'un éditeur graphique, nous pouvons déplacer le TreeView pour le positionner correctement sur la fenêtre de notre application, puis sélectionner un des coins du TreeView, et l'étendre jusqu'au moment ou sa taille nous convient.

Nous pouvons modifier le nom du composant pour lui donner un nom significatif dans le code (par exemple classInfoTreeView).

Contents Haut

Construire le TreeView

Nous avons donc une fenêtre avec un composant TreeView, mais ce dernier est vide. Nous pouvons doublecliquer sur notre menu File/Open de notre fenêtre en mode design et Visual Studio nous place directement dans notre méthode fileToolStripMenuItem_Click (qui jusqu'ici permettait l'affichage du nom du fichier sélectionné).

A cet endroit, nous allons faire appel à une méthode (que nous pouvons appeler fillTreeView()) qui permettra de peupler le TreeView avec les TreeNodes que nous récupérons gràce à notre classe ClassInfo.


Code c# (Form1.cs fileToolStripMenuItem_Click) (6 lignes) :
  1. private void fileToolStripMenuItem_Click(object sender, EventArgs e)
  2. {
  3.   openFileDialog.ShowDialog();
  4.   fillAndDisplayPathLabel();
  5.   fillTreeView();
  6. }

Code c# (Form1.cs fillTreeView) (6 lignes) :
  1. private void fillTreeView()
  2. {
  3.   classInfoTreeView.Nodes.Clear();
  4.   classInfoTreeView.Nodes.Add(ClassInfo.getFileName(openFileDialog.FileName));
  5.   classInfoTreeView.Nodes[0].Nodes.AddRange(ClassInfo.getInfos(openFileDialog.FileName));
  6. }

Ce n'est pas plus compliqué que ça.

Arbres en GUI avec le treeview

Les différents nœuds de l'arbre sont automatiquement représentés en couleur, car la couleur est définie dans la classe ClassInfo au moment de la création des TreeNodes.

Cette manière de procéder est pratique et rapide, mais nous pouvons toutefois douter de la pertinence de notre choix de laisser la responsabilité de la présentation (le choix des couleurs) dans la classe qui appartient plus au modèle qu'à la vue.


Contents Haut

Tous les codes

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Windows.Forms;
  4. namespace ClassExplorer
  5. {
  6. static class Program
  7. {
  8. /// <summary>
  9. /// The main entry point for the application.
  10. /// </summary>
  11. [STAThread]
  12. static void Main()
  13. {
  14. Application.EnableVisualStyles();
  15. Application.SetCompatibleTextRenderingDefault(false);
  16. Application.Run(new Form1());
  17. }
  18. }
  19. }
  1. using System;
  2. using System.Collections;
  3. using System.Text;
  4. using System.Reflection;
  5. using System.IO;
  6. using System.Windows.Forms;
  7. using System.Drawing;
  8. namespace ClassExplorer
  9. {
  10. class ClassInfo
  11. {
  12. public static TreeNode[] getInfos(String classPath)
  13. {
  14. TreeNode classesTreeNode = new TreeNode("Classes");
  15. TreeNode enumsTreeNode = new TreeNode("Enums");
  16. TreeNode interfacesTreeNode = new TreeNode("Interfaces");
  17. try
  18. {
  19. Assembly assembly = Assembly.LoadFrom(classPath);
  20. Type[] types = assembly.GetTypes();
  21. foreach (Type type in types)
  22. {
  23. if (type.IsClass) classesTreeNode.Nodes.Add(getClassNode(type));
  24. else if (type.IsEnum) enumsTreeNode.Nodes.Add(type.Name);
  25. else if (type.IsInterface) interfacesTreeNode.Nodes.Add(type.Name);
  26. }
  27. }
  28. catch (Exception e)
  29. {
  30. }
  31. TreeNode[] infos = { classesTreeNode, enumsTreeNode, interfacesTreeNode };
  32. return infos;
  33. }
  34. public static String getFileInfos(String classPath)
  35. {
  36. FileSystemInfo f = new FileInfo(classPath);
  37. StringBuilder str = new StringBuilder(f.Name);
  38. str.Append("\n\nInfos :");
  39. str.Append("\nCreation time : ");
  40. str.Append(f.CreationTime);
  41. str.Append("\nLast access : ");
  42. str.Append(f.LastAccessTime);
  43. str.Append("\nAttributes : ");
  44. str.Append(f.Attributes);
  45. str.Append("\nFullName : ");
  46. str.Append(f.FullName);
  47. return str.ToString();
  48. }
  49. public static String getFileName(String classPath)
  50. {
  51. FileSystemInfo f = new FileInfo(classPath);
  52. return f.Name;
  53. }
  54. private static TreeNode getClassNode(Type type)
  55. {
  56. TreeNode classNode = new TreeNode(type.Name);
  57. MethodInfo[] methodsInfos = type.GetMethods(
  58. BindingFlags.NonPublic |
  59. BindingFlags.Instance |
  60. BindingFlags.Public |
  61. BindingFlags.Static |
  62. BindingFlags.DeclaredOnly
  63. );
  64. if (methodsInfos.Length > 0)
  65. {
  66. TreeNode methodsNode = new TreeNode("Methods (" + methodsInfos.Length +")");
  67. methodsNode.ForeColor = System.Drawing.Color.Blue;
  68. foreach (MethodInfo methodInfo in methodsInfos)
  69. {
  70. methodsNode.Nodes.Add(getMethodNode(methodInfo));
  71. }
  72. classNode.Nodes.Add(methodsNode);
  73. }
  74. else classNode.Nodes.Add("No methods available");
  75. ConstructorInfo[] constructorsInfos = type.GetConstructors(
  76. BindingFlags.NonPublic |
  77. BindingFlags.Instance |
  78. BindingFlags.Public |
  79. BindingFlags.Static |
  80. BindingFlags.DeclaredOnly
  81. );
  82. if (constructorsInfos.Length > 0)
  83. {
  84. TreeNode constructorsNode = new TreeNode("Constructors (" + constructorsInfos.Length + ")");
  85. constructorsNode.ForeColor = System.Drawing.Color.Green;
  86. foreach (ConstructorInfo constructorInfo in constructorsInfos)
  87. {
  88. constructorsNode.Nodes.Add(getConstructorNode(constructorInfo));
  89. }
  90. classNode.Nodes.Add(constructorsNode);
  91. }
  92. else classNode.Nodes.Add("No constructors available");
  93. FieldInfo[] fieldsInfos = type.GetFields(
  94. BindingFlags.NonPublic |
  95. BindingFlags.Instance |
  96. BindingFlags.Public |
  97. BindingFlags.Static |
  98. BindingFlags.DeclaredOnly
  99. );
  100. if (fieldsInfos.Length > 0)
  101. {
  102. TreeNode fieldsNode = new TreeNode("Fields (" + fieldsInfos.Length + ")");
  103. fieldsNode.ForeColor = System.Drawing.Color.Navy;
  104. foreach (FieldInfo fieldInfo in fieldsInfos)
  105. {
  106. fieldsNode.Nodes.Add(getFieldNode(fieldInfo));
  107. }
  108. classNode.Nodes.Add(fieldsNode);
  109. }
  110. else classNode.Nodes.Add("No fields available");
  111. PropertyInfo[] propsInfos = type.GetProperties(
  112. BindingFlags.NonPublic |
  113. BindingFlags.Instance |
  114. BindingFlags.Public |
  115. BindingFlags.Static |
  116. BindingFlags.DeclaredOnly
  117. );
  118. if (propsInfos.Length > 0)
  119. {
  120. TreeNode propsNode = new TreeNode("Properties (" + propsInfos.Length + ")");
  121. foreach (PropertyInfo propInfo in propsInfos)
  122. {
  123. propsNode.Nodes.Add(propInfo.Name);
  124. }
  125. classNode.Nodes.Add(propsNode);
  126. }
  127. else classNode.Nodes.Add("No properties available");
  128. return classNode;
  129. }
  130. private static TreeNode getConstructorNode(ConstructorInfo constructorInfo)
  131. {
  132. StringBuilder constrStr = new StringBuilder();
  133. if (constructorInfo.IsPrivate) constrStr.Append("Private ");
  134. else if (constructorInfo.IsStatic) constrStr.Append("Static ");
  135. else if (constructorInfo.IsPublic) constrStr.Append("Public ");
  136. constrStr.Append(constructorInfo.Name);
  137. TreeNode constructorNode = new TreeNode(constrStr.ToString());
  138. constructorNode.ForeColor = System.Drawing.Color.GreenYellow;
  139. ParameterInfo[] paramInfos = constructorInfo.GetParameters();
  140. foreach (ParameterInfo paramInfo in paramInfos)
  141. {
  142. constructorNode.Nodes.Add(
  143. String.Format("{0} {1}", paramInfo.ParameterType.ToString(), paramInfo.Name)
  144. );
  145. }
  146. return constructorNode;
  147. }
  148. private static TreeNode getMethodNode(MethodInfo methodInfo)
  149. {
  150. StringBuilder methodStr = new StringBuilder();
  151. if (methodInfo.IsPrivate) methodStr.Append("Private ");
  152. else if (methodInfo.IsStatic) methodStr.Append("Static ");
  153. else if (methodInfo.IsPublic) methodStr.Append("Public ");
  154. methodStr.Append(methodInfo.Name);
  155. TreeNode methodNode = new TreeNode(methodStr.ToString());
  156. methodNode.ForeColor = System.Drawing.Color.Red;
  157. ParameterInfo[] paramInfos = methodInfo.GetParameters();
  158. foreach (ParameterInfo paramInfo in paramInfos)
  159. {
  160. methodNode.Nodes.Add(
  161. String.Format("{0} {1}", paramInfo.ParameterType.ToString(), paramInfo.Name)
  162. );
  163. }
  164. return methodNode;
  165. }
  166. private static TreeNode getFieldNode(FieldInfo fieldInfo)
  167. {
  168. StringBuilder fieldStr = new StringBuilder();
  169. if (fieldInfo.IsPrivate) fieldStr.Append("Private ");
  170. else if (fieldInfo.IsStatic) fieldStr.Append("Static ");
  171. else if (fieldInfo.IsPublic) fieldStr.Append("Public ");
  172. fieldStr.Append(fieldInfo.Name);
  173. TreeNode fieldNode = new TreeNode(fieldStr.ToString());
  174. fieldNode.ForeColor = System.Drawing.Color.DarkGreen;
  175. return fieldNode;
  176. }
  177. }
  178. }
  1. using System.Windows.Forms;
  2. namespace ClassExplorer
  3. {
  4. partial class Form1
  5. {
  6. /// <summary>
  7. /// Required designer variable.
  8. /// </summary>
  9. private System.ComponentModel.IContainer components = null;
  10. /// <summary>
  11. /// Clean up any resources being used.
  12. /// </summary>
  13. /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
  14. protected override void Dispose(bool disposing)
  15. {
  16. if (disposing && (components != null))
  17. {
  18. components.Dispose();
  19. }
  20. base.Dispose(disposing);
  21. }
  22. #region Windows Form Designer generated code
  23. /// <summary>
  24. /// Required method for Designer support - do not modify
  25. /// the contents of this method with the code editor.
  26. /// </summary>
  27. private void InitializeComponent()
  28. {
  29. System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
  30. this.toolStrip1 = new System.Windows.Forms.ToolStrip();
  31. this.fileToolStripButton = new System.Windows.Forms.ToolStripDropDownButton();
  32. this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
  33. this.filePathLabel = new System.Windows.Forms.Label();
  34. this.openFileDialog = new System.Windows.Forms.OpenFileDialog();
  35. this.classInfoTreeView = new System.Windows.Forms.TreeView();
  36. this.toolStrip1.SuspendLayout();
  37. this.SuspendLayout();
  38. //
  39. // toolStrip1
  40. //
  41. this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
  42. this.fileToolStripButton});
  43. this.toolStrip1.Location = new System.Drawing.Point(0, 0);
  44. this.toolStrip1.Name = "toolStrip1";
  45. this.toolStrip1.Size = new System.Drawing.Size(292, 25);
  46. this.toolStrip1.TabIndex = 0;
  47. this.toolStrip1.Text = "toolStrip1";
  48. //
  49. // fileToolStripButton
  50. //
  51. this.fileToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
  52. this.fileToolStripButton.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
  53. this.fileToolStripMenuItem});
  54. this.fileToolStripButton.Image = ((System.Drawing.Image)(resources.GetObject("fileToolStripButton.Image")));
  55. this.fileToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta;
  56. this.fileToolStripButton.Name = "fileToolStripButton";
  57. this.fileToolStripButton.Size = new System.Drawing.Size(36, 22);
  58. this.fileToolStripButton.Text = "File";
  59. //
  60. // fileToolStripMenuItem
  61. //
  62. this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
  63. this.fileToolStripMenuItem.Size = new System.Drawing.Size(123, 22);
  64. this.fileToolStripMenuItem.Text = "Open...";
  65. this.fileToolStripMenuItem.Click += new System.EventHandler(this.fileToolStripMenuItem_Click);
  66. //
  67. // filePathLabel
  68. //
  69. this.filePathLabel.AutoSize = true;
  70. this.filePathLabel.Location = new System.Drawing.Point(12, 40);
  71. this.filePathLabel.Name = "filePathLabel";
  72. this.filePathLabel.Size = new System.Drawing.Size(0, 13);
  73. this.filePathLabel.TabIndex = 1;
  74. this.filePathLabel.Visible = false;
  75. //
  76. // openFileDialog
  77. //
  78. this.openFileDialog.Filter = "Executable files (.exe)|*.exe|Dynamic librairies (.dll)|*.dll";
  79. //
  80. // classInfoTreeView
  81. //
  82. this.classInfoTreeView.Location = new System.Drawing.Point(0, 80);
  83. this.classInfoTreeView.Name = "classInfoTreeView";
  84. this.classInfoTreeView.Size = new System.Drawing.Size(292, 201);
  85. this.classInfoTreeView.TabIndex = 2;
  86. //
  87. // Form1
  88. //
  89. this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
  90. this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
  91. this.ClientSize = new System.Drawing.Size(292, 273);
  92. this.Controls.Add(this.classInfoTreeView);
  93. this.Controls.Add(this.filePathLabel);
  94. this.Controls.Add(this.toolStrip1);
  95. this.Name = "Form1";
  96. this.Text = "My Class Explorer";
  97. this.toolStrip1.ResumeLayout(false);
  98. this.toolStrip1.PerformLayout();
  99. this.ResumeLayout(false);
  100. this.PerformLayout();
  101. }
  102. #endregion
  103. private ToolStrip toolStrip1;
  104. private ToolStripDropDownButton fileToolStripButton;
  105. private ToolStripMenuItem fileToolStripMenuItem;
  106. private Label filePathLabel;
  107. private OpenFileDialog openFileDialog;
  108. private TreeView classInfoTreeView;
  109. }
  110. }
  1. using System;
  2. using System.Collections;
  3. using System.Text;
  4. using System.Reflection;
  5. using System.IO;
  6. using System.Windows.Forms;
  7. using System.Drawing;
  8. namespace ClassExplorer
  9. {
  10. class ClassInfo
  11. {
  12. public static TreeNode[] getInfos(String classPath)
  13. {
  14. TreeNode classesTreeNode = new TreeNode("Classes");
  15. TreeNode enumsTreeNode = new TreeNode("Enums");
  16. TreeNode interfacesTreeNode = new TreeNode("Interfaces");
  17. try
  18. {
  19. Assembly assembly = Assembly.LoadFrom(classPath);
  20. Type[] types = assembly.GetTypes();
  21. foreach (Type type in types)
  22. {
  23. if (type.IsClass) classesTreeNode.Nodes.Add(getClassNode(type));
  24. else if (type.IsEnum) enumsTreeNode.Nodes.Add(type.Name);
  25. else if (type.IsInterface) interfacesTreeNode.Nodes.Add(type.Name);
  26. }
  27. }
  28. catch (Exception e)
  29. {
  30. }
  31. TreeNode[] infos = { classesTreeNode, enumsTreeNode, interfacesTreeNode };
  32. return infos;
  33. }
  34. public static String getFileInfos(String classPath)
  35. {
  36. FileSystemInfo f = new FileInfo(classPath);
  37. StringBuilder str = new StringBuilder(f.Name);
  38. str.Append("\n\nInfos :");
  39. str.Append("\nCreation time : ");
  40. str.Append(f.CreationTime);
  41. str.Append("\nLast access : ");
  42. str.Append(f.LastAccessTime);
  43. str.Append("\nAttributes : ");
  44. str.Append(f.Attributes);
  45. str.Append("\nFullName : ");
  46. str.Append(f.FullName);
  47. return str.ToString();
  48. }
  49. public static String getFileName(String classPath)
  50. {
  51. FileSystemInfo f = new FileInfo(classPath);
  52. return f.Name;
  53. }
  54. private static TreeNode getClassNode(Type type)
  55. {
  56. TreeNode classNode = new TreeNode(type.Name);
  57. MethodInfo[] methodsInfos = type.GetMethods(
  58. BindingFlags.NonPublic |
  59. BindingFlags.Instance |
  60. BindingFlags.Public |
  61. BindingFlags.Static |
  62. BindingFlags.DeclaredOnly
  63. );
  64. if (methodsInfos.Length > 0)
  65. {
  66. TreeNode methodsNode = new TreeNode("Methods (" + methodsInfos.Length +")");
  67. methodsNode.ForeColor = System.Drawing.Color.Blue;
  68. foreach (MethodInfo methodInfo in methodsInfos)
  69. {
  70. methodsNode.Nodes.Add(getMethodNode(methodInfo));
  71. }
  72. classNode.Nodes.Add(methodsNode);
  73. }
  74. else classNode.Nodes.Add("No methods available");
  75. ConstructorInfo[] constructorsInfos = type.GetConstructors(
  76. BindingFlags.NonPublic |
  77. BindingFlags.Instance |
  78. BindingFlags.Public |
  79. BindingFlags.Static |
  80. BindingFlags.DeclaredOnly
  81. );
  82. if (constructorsInfos.Length > 0)
  83. {
  84. TreeNode constructorsNode = new TreeNode("Constructors (" + constructorsInfos.Length + ")");
  85. constructorsNode.ForeColor = System.Drawing.Color.Green;
  86. foreach (ConstructorInfo constructorInfo in constructorsInfos)
  87. {
  88. constructorsNode.Nodes.Add(getConstructorNode(constructorInfo));
  89. }
  90. classNode.Nodes.Add(constructorsNode);
  91. }
  92. else classNode.Nodes.Add("No constructors available");
  93. FieldInfo[] fieldsInfos = type.GetFields(
  94. BindingFlags.NonPublic |
  95. BindingFlags.Instance |
  96. BindingFlags.Public |
  97. BindingFlags.Static |
  98. BindingFlags.DeclaredOnly
  99. );
  100. if (fieldsInfos.Length > 0)
  101. {
  102. TreeNode fieldsNode = new TreeNode("Fields (" + fieldsInfos.Length + ")");
  103. fieldsNode.ForeColor = System.Drawing.Color.Navy;
  104. foreach (FieldInfo fieldInfo in fieldsInfos)
  105. {
  106. fieldsNode.Nodes.Add(getFieldNode(fieldInfo));
  107. }
  108. classNode.Nodes.Add(fieldsNode);
  109. }
  110. else classNode.Nodes.Add("No fields available");
  111. PropertyInfo[] propsInfos = type.GetProperties(
  112. BindingFlags.NonPublic |
  113. BindingFlags.Instance |
  114. BindingFlags.Public |
  115. BindingFlags.Static |
  116. BindingFlags.DeclaredOnly
  117. );
  118. if (propsInfos.Length > 0)
  119. {
  120. TreeNode propsNode = new TreeNode("Properties (" + propsInfos.Length + ")");
  121. foreach (PropertyInfo propInfo in propsInfos)
  122. {
  123. propsNode.Nodes.Add(propInfo.Name);
  124. }
  125. classNode.Nodes.Add(propsNode);
  126. }
  127. else classNode.Nodes.Add("No properties available");
  128. return classNode;
  129. }
  130. private static TreeNode getConstructorNode(ConstructorInfo constructorInfo)
  131. {
  132. StringBuilder constrStr = new StringBuilder();
  133. if (constructorInfo.IsPrivate) constrStr.Append("Private ");
  134. else if (constructorInfo.IsStatic) constrStr.Append("Static ");
  135. else if (constructorInfo.IsPublic) constrStr.Append("Public ");
  136. constrStr.Append(constructorInfo.Name);
  137. TreeNode constructorNode = new TreeNode(constrStr.ToString());
  138. constructorNode.ForeColor = System.Drawing.Color.GreenYellow;
  139. ParameterInfo[] paramInfos = constructorInfo.GetParameters();
  140. foreach (ParameterInfo paramInfo in paramInfos)
  141. {
  142. constructorNode.Nodes.Add(
  143. String.Format("{0} {1}", paramInfo.ParameterType.ToString(), paramInfo.Name)
  144. );
  145. }
  146. return constructorNode;
  147. }
  148. private static TreeNode getMethodNode(MethodInfo methodInfo)
  149. {
  150. StringBuilder methodStr = new StringBuilder();
  151. if (methodInfo.IsPrivate) methodStr.Append("Private ");
  152. else if (methodInfo.IsStatic) methodStr.Append("Static ");
  153. else if (methodInfo.IsPublic) methodStr.Append("Public ");
  154. methodStr.Append(methodInfo.Name);
  155. TreeNode methodNode = new TreeNode(methodStr.ToString());
  156. methodNode.ForeColor = System.Drawing.Color.Red;
  157. ParameterInfo[] paramInfos = methodInfo.GetParameters();
  158. foreach (ParameterInfo paramInfo in paramInfos)
  159. {
  160. methodNode.Nodes.Add(
  161. String.Format("{0} {1}", paramInfo.ParameterType.ToString(), paramInfo.Name)
  162. );
  163. }
  164. return methodNode;
  165. }
  166. private static TreeNode getFieldNode(FieldInfo fieldInfo)
  167. {
  168. StringBuilder fieldStr = new StringBuilder();
  169. if (fieldInfo.IsPrivate) fieldStr.Append("Private ");
  170. else if (fieldInfo.IsStatic) fieldStr.Append("Static ");
  171. else if (fieldInfo.IsPublic) fieldStr.Append("Public ");
  172. fieldStr.Append(fieldInfo.Name);
  173. TreeNode fieldNode = new TreeNode(fieldStr.ToString());
  174. fieldNode.ForeColor = System.Drawing.Color.DarkGreen;
  175. return fieldNode;
  176. }
  177. }
  178. }

Contents Haut

English translation

You have asked to visit this site in English. For now, only the interface is translated, but not all the content yet.

If you want to help me in translations, your contribution is welcome. All you need to do is register on the site, and send me a message asking me to add you to the group of translators, which will give you the opportunity to translate the pages you want. A link at the bottom of each translated page indicates that you are the translator, and has a link to your profile.

Thank you in advance.

Document created the 02/10/2006, last modified the 31/10/2018
Source of the printed document:https://www.gaudry.be/en/csharp-gui-tree.html

The infobrol is a personal site whose content is my sole responsibility. The text is available under CreativeCommons license (BY-NC-SA). More info on the terms of use and the author.