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.LoadNodes; } private async void LoadNodes(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); } // Expand the tree: this.treeView.ExpandAll(); } private async void buttonAdd_Click(object sender, EventArgs e) { var result = InputDialog.Show(new InputDialog.Options( Message: "Please type the desired section name.", Title: "Add a section", Placeholder: "My next section", ShowQuestionCheckbox: true, QuestionCheckboxText: "Add a root node (i.e. ignoring the selected node)" )); if(result.DialogResult == DialogResult.Cancel) return; var addRootNode = result.AnswerToQuestion; // 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, addRootNode ? 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(!addRootNode && 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; } private async void buttonRemove_Click(object sender, EventArgs e) { // Get the currently selected section, which will be removed: var selectedNode = this.treeView.SelectedNode; // Get the number of children: // (notice, that the node's name is its key) var numberChildren = await SectionProcessor.NumberChildren(this.db, selectedNode.Name); // Ask the user, if he really wants to remove the section: if(MessageBox.Show(numberChildren > 0 ? $"Are you sure, you want to remove the section '{selectedNode.Text}', its {numberChildren} children and so on?" : $"Are you sure, you want to remove the section '{selectedNode.Text}'?", "Remove section", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.No) return; // Remove the section from the database: // (notice, that the node's name is its key) await SectionProcessor.RemoveSection(this.db, selectedNode.Name); // Remove all nodes from the tree control: this.treeView.Nodes.Clear(); // Reload the tree: this.LoadNodes(this, EventArgs.Empty); } private void treeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) { // Get the currently selected section: var selectedNode = this.treeView.SelectedNode; // If the selected node is not null, enable the remove button: this.buttonRemove.Enabled = selectedNode is not null; } }