diff --git a/I18N Commander/UI WinForms/Components/SectionTree.Designer.cs b/I18N Commander/UI WinForms/Components/SectionTree.Designer.cs
new file mode 100644
index 0000000..9229662
--- /dev/null
+++ b/I18N Commander/UI WinForms/Components/SectionTree.Designer.cs
@@ -0,0 +1,129 @@
+namespace UI_WinForms.Components
+{
+ partial class SectionTree
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.tableLayout = new System.Windows.Forms.TableLayoutPanel();
+ this.flowLayoutBottom = new System.Windows.Forms.FlowLayoutPanel();
+ this.buttonAdd = new System.Windows.Forms.Button();
+ this.buttonRemove = new System.Windows.Forms.Button();
+ this.treeView = new System.Windows.Forms.TreeView();
+ this.tableLayout.SuspendLayout();
+ this.flowLayoutBottom.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // tableLayout
+ //
+ this.tableLayout.ColumnCount = 1;
+ this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
+ this.tableLayout.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F));
+ this.tableLayout.Controls.Add(this.flowLayoutBottom, 0, 1);
+ this.tableLayout.Controls.Add(this.treeView, 0, 0);
+ this.tableLayout.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.tableLayout.Location = new System.Drawing.Point(0, 0);
+ this.tableLayout.Name = "tableLayout";
+ this.tableLayout.RowCount = 2;
+ this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
+ this.tableLayout.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 66F));
+ this.tableLayout.Size = new System.Drawing.Size(296, 511);
+ this.tableLayout.TabIndex = 0;
+ //
+ // flowLayoutBottom
+ //
+ this.flowLayoutBottom.Controls.Add(this.buttonAdd);
+ this.flowLayoutBottom.Controls.Add(this.buttonRemove);
+ this.flowLayoutBottom.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.flowLayoutBottom.Location = new System.Drawing.Point(0, 445);
+ this.flowLayoutBottom.Margin = new System.Windows.Forms.Padding(0);
+ this.flowLayoutBottom.Name = "flowLayoutBottom";
+ this.flowLayoutBottom.Size = new System.Drawing.Size(296, 66);
+ this.flowLayoutBottom.TabIndex = 0;
+ //
+ // buttonAdd
+ //
+ this.buttonAdd.AutoSize = true;
+ this.buttonAdd.Image = global::UI_WinForms.Resources.Icons.icons8_add_folder_512;
+ this.buttonAdd.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;
+ this.buttonAdd.Location = new System.Drawing.Point(3, 3);
+ this.buttonAdd.Name = "buttonAdd";
+ this.buttonAdd.Size = new System.Drawing.Size(138, 60);
+ this.buttonAdd.TabIndex = 0;
+ this.buttonAdd.Text = "Add";
+ this.buttonAdd.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText;
+ this.buttonAdd.UseVisualStyleBackColor = true;
+ this.buttonAdd.Click += new System.EventHandler(this.buttonAdd_Click);
+ //
+ // buttonRemove
+ //
+ this.buttonRemove.AutoSize = true;
+ this.buttonRemove.Enabled = false;
+ this.buttonRemove.Image = global::UI_WinForms.Resources.Icons.icons8_delete_folder_512;
+ this.buttonRemove.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft;
+ this.buttonRemove.Location = new System.Drawing.Point(147, 3);
+ this.buttonRemove.Name = "buttonRemove";
+ this.buttonRemove.Size = new System.Drawing.Size(138, 60);
+ this.buttonRemove.TabIndex = 1;
+ this.buttonRemove.Text = "Remove";
+ this.buttonRemove.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText;
+ this.buttonRemove.UseVisualStyleBackColor = true;
+ //
+ // treeView
+ //
+ this.treeView.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+ this.treeView.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.treeView.HideSelection = false;
+ this.treeView.ItemHeight = 50;
+ this.treeView.Location = new System.Drawing.Point(3, 3);
+ this.treeView.Name = "treeView";
+ this.treeView.Size = new System.Drawing.Size(290, 439);
+ this.treeView.TabIndex = 1;
+ //
+ // SectionTree
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
+ this.Controls.Add(this.tableLayout);
+ this.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
+ this.Name = "SectionTree";
+ this.Size = new System.Drawing.Size(296, 511);
+ this.tableLayout.ResumeLayout(false);
+ this.flowLayoutBottom.ResumeLayout(false);
+ this.flowLayoutBottom.PerformLayout();
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private TableLayoutPanel tableLayout;
+ private FlowLayoutPanel flowLayoutBottom;
+ private Button buttonAdd;
+ private Button buttonRemove;
+ private TreeView treeView;
+ }
+}
diff --git a/I18N Commander/UI WinForms/Components/SectionTree.cs b/I18N Commander/UI WinForms/Components/SectionTree.cs
new file mode 100644
index 0000000..5858830
--- /dev/null
+++ b/I18N Commander/UI WinForms/Components/SectionTree.cs
@@ -0,0 +1,140 @@
+using DataModel.Database;
+using DataModel.Database.Common;
+using Microsoft.Extensions.DependencyInjection;
+using Processor;
+using UI_WinForms.Dialogs;
+using UI_WinForms.Resources;
+
+namespace UI_WinForms.Components;
+
+public partial class SectionTree : UserControl
+{
+ private readonly DataContext db;
+
+ public SectionTree()
+ {
+ this.InitializeComponent();
+
+ // Get the DI context from the main form:
+ this.db = Program.SERVICE_PROVIDER.GetService()!;
+
+ // Dispose of the context when the control is disposed:
+ this.Disposed += (_, _) => this.db.Dispose();
+
+ // Create an image list from a resource:
+ var imgList = new ImageList();
+ imgList.ImageSize = new Size(45, 45);
+ imgList.ColorDepth = ColorDepth.Depth32Bit;
+ imgList.Images.Add(Icons.icons8_documents_folder_512);
+
+ // Set the image list to the tree view:
+ this.treeView.ImageList = imgList;
+
+ // Subscribe to the load event:
+ this.Load += this.OnLoad;
+ }
+
+ private async void OnLoad(object? sender, EventArgs e)
+ {
+ // A dictionary to cache all known tree nodes:
+ var treeNodes = new Dictionary();
+
+ // Get the max. depth of the tree:
+ var maxDepth = await SectionProcessor.GetDepth(this.db);
+
+ // Store nodes, where we cannot find the parents:
+ var missingParents = new List();
+
+ // Populate the tree view out of the database, layer by layer:
+ for (var i = 0; i <= maxDepth; i++)
+ {
+ await foreach (var section in SectionProcessor.LoadLayer(this.db, i))
+ {
+ // Create the tree node:
+ var node = new TreeNode
+ {
+ Name = section.DataKey, // [sic] name is the key
+ Text = section.Name,
+ StateImageIndex = 1,
+ };
+
+ // Cache the node:
+ treeNodes.Add(section.DataKey, node);
+
+ // Is this a root node?
+ if (section.Depth is 0)
+
+ // Set the root node:
+ this.treeView.Nodes.Add(node);
+
+ // Otherwise, attach this section to its parent node:
+ else
+ {
+ // Get the parent from our cache within O(1):
+ treeNodes.TryGetValue(section.Parent?.DataKey ?? string.Empty, out var parent);
+
+ // If the parent node is not found, skip this section:
+ if (parent is null)
+ {
+ missingParents.Add(node);
+ continue;
+ }
+
+ // Add the node to the parent:
+ parent.Nodes.Add(node);
+ }
+ }
+ }
+
+ // If we found any missing parents, show a dialog:
+ if (missingParents.Any())
+ {
+ MessageBox.Show($"In {missingParents.Count} case(s) we could not found the matching parent. We added these nodes to a special root node, though.", "Parent not found", MessageBoxButtons.OK, MessageBoxIcon.Error);
+
+ // Create a root node for all missing parents:
+ var rootMissedParents = new TreeNode
+ {
+ Name = "MISSING_PARENTS",
+ Text = "Missing Parents",
+ StateImageIndex = 1,
+ };
+
+ // Add the root node to the tree:
+ this.treeView.Nodes.Add(rootMissedParents);
+
+ // Add all missing parents to the root node:
+ foreach (var node in missingParents)
+ rootMissedParents.Nodes.Add(node);
+ }
+ }
+
+ private async void buttonAdd_Click(object sender, EventArgs e)
+ {
+ var result = InputDialog.Show("Please type the desired section name.", "Add a section", "My next section");
+ if(result.DialogResult == DialogResult.Cancel)
+ return;
+
+ // Get the currently selected section as parent:
+ var selectedNode = this.treeView.SelectedNode;
+
+ // Add the new section to the database:
+ var addedSection = await SectionProcessor.AddSection(this.db, result.Text, result.IsRoot ? null : selectedNode?.Name);
+
+ // Add the new section to the tree control:
+ var node = new TreeNode
+ {
+ Name = addedSection.DataKey, // [sic] name is the key
+ Text = addedSection.Name,
+ StateImageIndex = 1,
+ };
+
+ if(!result.IsRoot && selectedNode is not null)
+ selectedNode.Nodes.Add(node);
+ else
+ this.treeView.Nodes.Add(node);
+
+ // Ensure, that the added node is visible and gets the focus:
+ node.EnsureVisible();
+ this.treeView.SelectedNode = node;
+ }
+}
\ No newline at end of file
diff --git a/I18N Commander/UI WinForms/Components/SectionTree.resx b/I18N Commander/UI WinForms/Components/SectionTree.resx
new file mode 100644
index 0000000..b5ae26c
--- /dev/null
+++ b/I18N Commander/UI WinForms/Components/SectionTree.resx
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file