diff --git a/InteractiveAutomationToolkit/Components/Generics/GenericTreeView.cs b/InteractiveAutomationToolkit/Components/Generics/GenericTreeView.cs
new file mode 100644
index 0000000..17ab95b
--- /dev/null
+++ b/InteractiveAutomationToolkit/Components/Generics/GenericTreeView.cs
@@ -0,0 +1,654 @@
+namespace Skyline.DataMiner.Utils.InteractiveAutomationScript
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+
+ using Skyline.DataMiner.Automation;
+ using Skyline.DataMiner.Net.AutomationUI.Objects;
+
+ ///
+ /// A generic tree view structure that allows attaching custom metadata to each item.
+ ///
+ /// The type of the value associated with each tree view item.
+ public class TreeView : InteractiveWidget, IIsReadonlyWidget
+ {
+ private Dictionary checkedItemCache;
+ private Dictionary collapsedItemCache;
+ private Dictionary> lookupTable;
+
+ private bool itemsChanged = false;
+ private List> changedItems = new List>();
+
+ private bool itemsChecked = false;
+ private List> checkedItems = new List>();
+
+ private bool itemsUnchecked = false;
+ private List> uncheckedItems = new List>();
+
+ private bool itemsExpanded = false;
+ private List> expandedItems = new List>();
+
+ private bool itemsCollapsed = false;
+ private List> collapsedItems = new List>();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TreeView() : this(Enumerable.Empty>())
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Root nodes of the tree view.
+ public TreeView(IEnumerable> treeViewItems)
+ {
+ Type = UIBlockType.TreeView;
+ Items = treeViewItems;
+ IsReadOnly = false;
+ }
+
+ ///
+ /// Triggered when a different item is selected or no longer selected.
+ /// WantsOnChange will be set to true when this event is subscribed to.
+ ///
+ public event EventHandler>> Changed
+ {
+ add
+ {
+ OnChanged += value;
+ BlockDefinition.WantsOnChange = true;
+ }
+
+ remove
+ {
+ OnChanged -= value;
+ if (OnChanged == null || !OnChanged.GetInvocationList().Any())
+ {
+ BlockDefinition.WantsOnChange = false;
+ }
+ }
+ }
+
+ ///
+ /// Triggered whenever an item is selected.
+ /// WantsOnChange will be set to true when this event is subscribed to.
+ ///
+ public event EventHandler>> Checked
+ {
+ add
+ {
+ OnChecked += value;
+ BlockDefinition.WantsOnChange = true;
+ }
+
+ remove
+ {
+ OnChecked -= value;
+ if (OnChecked == null || !OnChecked.GetInvocationList().Any())
+ {
+ BlockDefinition.WantsOnChange = false;
+ }
+ }
+ }
+
+ ///
+ /// Triggered whenever an item is no longer selected.
+ /// WantsOnChange will be set to true when this event is subscribed to.
+ ///
+ public event EventHandler>> Unchecked
+ {
+ add
+ {
+ OnUnchecked += value;
+ BlockDefinition.WantsOnChange = true;
+ }
+
+ remove
+ {
+ OnUnchecked -= value;
+ if (OnUnchecked == null || !OnUnchecked.GetInvocationList().Any())
+ {
+ BlockDefinition.WantsOnChange = false;
+ }
+ }
+ }
+
+ ///
+ /// Triggered whenever an item is expanded.
+ /// Can be used for lazy loading.
+ /// Will be triggered whenever a node with SupportsLazyLoading set to true is expanded.
+ ///
+ public event EventHandler>> Expanded
+ {
+ add
+ {
+ OnExpanded += value;
+ }
+
+ remove
+ {
+ OnExpanded -= value;
+ }
+ }
+
+ ///
+ /// Triggered whenever an item is collapsed.
+ /// Will be triggered whenever a node with SupportsLazyLoading set to true is collapsed.
+ ///
+ public event EventHandler>> Collapsed
+ {
+ add
+ {
+ OnCollapsed += value;
+ }
+
+ remove
+ {
+ OnCollapsed -= value;
+ }
+ }
+
+ private event EventHandler>> OnChanged;
+
+ private event EventHandler>> OnChecked;
+
+ private event EventHandler>> OnUnchecked;
+
+ private event EventHandler>> OnExpanded;
+
+ private event EventHandler>> OnCollapsed;
+
+ ///
+ /// Gets or sets the top-level items in the tree view.
+ /// The TreeViewItemOption.Item.ChildItems property can be used to navigate further down the tree.
+ ///
+ public IEnumerable> Items
+ {
+ get
+ {
+ // Return the TreeViewItemOption wrappers based on the underlying TreeViewItems
+ return BlockDefinition.TreeViewItems.Select(item => lookupTable.Values.FirstOrDefault(x => x.Item == item))
+ .Where(x => x != null);
+ }
+
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ BlockDefinition.TreeViewItems = new List(value.Select(x => x.Item));
+ UpdateItemCache(value);
+ }
+ }
+
+ ///
+ /// Gets all items in the tree view that are selected.
+ ///
+ public IEnumerable> CheckedItems
+ {
+ get
+ {
+ return GetCheckedItems();
+ }
+ }
+
+ ///
+ /// Gets all leaves (= items without children) in the tree view that are selected.
+ ///
+ public IEnumerable> CheckedLeaves
+ {
+ get
+ {
+ return GetCheckedItems().Where(x => !x.ChildItems.Any());
+ }
+ }
+
+ ///
+ /// Gets all nodes (= items with children) in the tree view that are selected.
+ ///
+ public IEnumerable> CheckedNodes
+ {
+ get
+ {
+ return GetCheckedItems().Where(x => x.ChildItems.Any());
+ }
+ }
+
+ ///
+ /// Gets the values of all items in the tree view that are selected.
+ ///
+ public IEnumerable CheckedValues
+ {
+ get
+ {
+ return GetCheckedItems().Select(x => x.Value);
+ }
+ }
+
+ ///
+ /// Gets the values of all leaves in the tree view that are selected.
+ ///
+ public IEnumerable CheckedLeafValues
+ {
+ get
+ {
+ return CheckedLeaves.Select(x => x.Value);
+ }
+ }
+
+ ///
+ /// Gets the values of all nodes in the tree view that are selected.
+ ///
+ public IEnumerable CheckedNodeValues
+ {
+ get
+ {
+ return CheckedNodes.Select(x => x.Value);
+ }
+ }
+
+ ///
+ /// Gets or sets the tooltip.
+ ///
+ /// When the value is null.
+ public string Tooltip
+ {
+ get
+ {
+ return BlockDefinition.TooltipText;
+ }
+
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ BlockDefinition.TooltipText = value;
+ }
+ }
+
+ ///
+ public virtual bool IsReadOnly
+ {
+ get
+ {
+ return BlockDefinition.IsReadOnly;
+ }
+
+ set
+ {
+ BlockDefinition.IsReadOnly = value;
+ }
+ }
+
+ ///
+ /// Sets the IsCollapsed state for all items in the tree view to true, causing the entire tree view to be collapsed.
+ ///
+ public void Collapse()
+ {
+ foreach (var item in GetAllItems())
+ {
+ item.IsCollapsed = true;
+ }
+ }
+
+ ///
+ /// Sets the IsCollapsed state for all items in the tree view to false, causing the entire tree view to be expanded.
+ ///
+ public void Expand()
+ {
+ foreach (var item in GetAllItems())
+ {
+ item.IsCollapsed = false;
+ }
+ }
+
+ ///
+ /// Can be used to retrieve an item from the tree view based on its key value.
+ ///
+ /// Key used to search for the item.
+ /// Item in the tree that matches the provided key.
+ /// True if the item was found, otherwise false.
+ public bool TryFindTreeViewItem(string key, out TreeViewItemOption item)
+ {
+ return lookupTable.TryGetValue(key, out item);
+ }
+
+ ///
+ /// This method is used to update the cached TreeViewItems and lookup table.
+ /// This is done after loading the results from the UI Block, after handling the Events or when setting the Items.
+ /// This method should only be called from outside the TreeView if you are checking or collapsing items from outside of the TreeView and need to access the CheckedItems or CollapsedItems.
+ ///
+ public void UpdateItemCache()
+ {
+ UpdateItemCache(Items);
+ }
+
+ ///
+ /// Iterates over all items in the tree and returns them in a flat collection.
+ ///
+ /// A flat collection containing all items in the tree view.
+ public IEnumerable> GetAllItems()
+ {
+ return lookupTable.Values;
+ }
+
+ ///
+ /// Returns all items in the tree view that are located at the provided depth.
+ /// Whenever the requested depth is greater than the longest branch in the tree, an empty collection will be returned.
+ ///
+ /// Depth of the requested items.
+ /// All items in the tree view that are located at the provided depth.
+ public IEnumerable> GetItems(int depth)
+ {
+ return GetItems(Items, depth, 0);
+ }
+
+ ///
+ /// Load any changes made through user interaction.
+ ///
+ ///
+ /// Represents the information a user has entered or selected in a dialog box of an interactive
+ /// Automation script.
+ ///
+ /// should be used as key to get the changes for this widget.
+ protected internal override void LoadResult(IUIResults uiResults)
+ {
+ var checkedItemKeys = uiResults.GetCheckedItemKeys(this);
+ var expandedItemKeys = uiResults.GetExpandedItemKeys(this);
+
+ // Check for changes
+ // Expanded Items
+ RegisterExpandedItems(expandedItemKeys);
+
+ // Collapsed Items
+ RegisterCollapsedItems(expandedItemKeys);
+
+ // Checked Items
+ List newlyCheckedItemKeys = RegisterCheckedItems(checkedItemKeys);
+
+ // Unchecked Items
+ List newlyUncheckedItemKeys = RegisterUncheckedItems(checkedItemKeys);
+
+ // Changed Items
+ List changedItemKeys = new List();
+ changedItemKeys.AddRange(newlyCheckedItemKeys);
+ changedItemKeys.AddRange(newlyUncheckedItemKeys);
+ if (changedItemKeys.Any() && OnChanged != null)
+ {
+ itemsChanged = true;
+ changedItems = new List>();
+
+ foreach (string changedItemKey in changedItemKeys)
+ {
+ if (lookupTable.TryGetValue(changedItemKey, out var item))
+ {
+ changedItems.Add(item);
+ }
+ }
+ }
+
+ // Persist states
+ foreach (TreeViewItemOption item in lookupTable.Values)
+ {
+ item.Item.IsChecked = checkedItemKeys.Contains(item.KeyValue);
+ item.Item.IsCollapsed = !expandedItemKeys.Contains(item.KeyValue);
+ }
+
+ UpdateItemCache();
+ }
+
+ ///
+ /// Raises zero or more events of the widget.
+ /// This method is called after was called on all widgets.
+ ///
+ /// It is up to the implementer to determine if an event must be raised.
+ protected internal override void RaiseResultEvents()
+ {
+ // Expanded items
+ if (itemsExpanded && OnExpanded != null)
+ {
+ OnExpanded(this, expandedItems);
+ }
+
+ // Collapsed items
+ if (itemsCollapsed && OnCollapsed != null)
+ {
+ OnCollapsed(this, collapsedItems);
+ }
+
+ // Checked items
+ if (itemsChecked && OnChecked != null)
+ {
+ OnChecked(this, checkedItems);
+ }
+
+ // Unchecked items
+ if (itemsUnchecked && OnUnchecked != null)
+ {
+ OnUnchecked(this, uncheckedItems);
+ }
+
+ // Changed items
+ if (itemsChanged && OnChanged != null)
+ {
+ OnChanged(this, changedItems);
+ }
+
+ itemsExpanded = false;
+ itemsCollapsed = false;
+ itemsChecked = false;
+ itemsUnchecked = false;
+ itemsChanged = false;
+
+ UpdateItemCache();
+ }
+
+ ///
+ /// Returns all items in the TreeView that are checked.
+ ///
+ /// All checked TreeViewItems in the TreeView.
+ private IEnumerable> GetCheckedItems()
+ {
+ return lookupTable.Values.Where(x => x.ItemType == TreeViewItem.TreeViewItemType.CheckBox && x.IsChecked);
+ }
+
+ ///
+ /// This method is used to recursively go through all the items in the TreeView.
+ ///
+ /// List of TreeViewItems to be visited.
+ /// Parent TreeViewItemOption wrappers.
+ /// Flat collection containing every item in the provided children collection and all underlying items.
+ private IEnumerable> GetAllItemsRecursive(IEnumerable children, IEnumerable> parentOptions)
+ {
+ List> allItems = new List>();
+ foreach (var item in children)
+ {
+ var option = parentOptions.FirstOrDefault(x => x.Item == item);
+ if (option != null)
+ {
+ allItems.Add(option);
+ // For child items, we need to search in the lookup table
+ var childOptions = option.ChildItems.Select(child => lookupTable.Values.FirstOrDefault(x => x.Item == child)).Where(x => x != null);
+ allItems.AddRange(GetAllItemsRecursive(option.ChildItems, childOptions));
+ }
+ }
+
+ return allItems;
+ }
+
+ ///
+ /// Returns all TreeViewItems in the TreeView that are located on the provided depth.
+ ///
+ /// Items to be checked.
+ /// Depth that was requested.
+ /// Current depth in the tree.
+ /// All TreeViewItems in the TreeView that are located on the provided depth.
+ private IEnumerable> GetItems(IEnumerable> children, int requestedDepth, int currentDepth)
+ {
+ List> requestedItems = new List>();
+ bool depthReached = requestedDepth == currentDepth;
+ foreach (TreeViewItemOption item in children)
+ {
+ if (depthReached)
+ {
+ requestedItems.Add(item);
+ }
+ else
+ {
+ int newDepth = currentDepth + 1;
+ var childOptions = item.ChildItems.Select(child => lookupTable.Values.FirstOrDefault(x => x.Item == child)).Where(x => x != null);
+ requestedItems.AddRange(GetItems(childOptions, requestedDepth, newDepth));
+ }
+ }
+
+ return requestedItems;
+ }
+
+ private void RegisterExpandedItems(IEnumerable expandedItemKeys)
+ {
+ List newlyExpandedItems = collapsedItemCache.Where(x => expandedItemKeys.Contains(x.Key) && x.Value).Select(x => x.Key).ToList();
+ if (newlyExpandedItems.Any() && OnExpanded != null)
+ {
+ itemsExpanded = true;
+ expandedItems = new List>();
+
+ foreach (string newlyExpandedItemKey in newlyExpandedItems)
+ {
+ if (lookupTable.TryGetValue(newlyExpandedItemKey, out var item))
+ {
+ expandedItems.Add(item);
+ }
+ }
+ }
+ }
+
+ private void RegisterCollapsedItems(IEnumerable expandedItemKeys)
+ {
+ List newlyCollapsedItems = collapsedItemCache.Where(x => !expandedItemKeys.Contains(x.Key) && !x.Value).Select(x => x.Key).ToList();
+ if (newlyCollapsedItems.Any() && OnCollapsed != null)
+ {
+ itemsCollapsed = true;
+ collapsedItems = new List>();
+
+ foreach (string newlyCollapsedItemKey in newlyCollapsedItems)
+ {
+ if (lookupTable.TryGetValue(newlyCollapsedItemKey, out var item))
+ {
+ collapsedItems.Add(item);
+ }
+ }
+ }
+ }
+
+ private List RegisterCheckedItems(IEnumerable checkedItemKeys)
+ {
+ List newlyCheckedItemKeys = checkedItemCache.Where(x => checkedItemKeys.Contains(x.Key) && !x.Value).Select(x => x.Key).ToList();
+ if (newlyCheckedItemKeys.Any() && OnChecked != null)
+ {
+ itemsChecked = true;
+ checkedItems = new List>();
+
+ foreach (string newlyCheckedItemKey in newlyCheckedItemKeys)
+ {
+ if (lookupTable.TryGetValue(newlyCheckedItemKey, out var item))
+ {
+ checkedItems.Add(item);
+ }
+ }
+ }
+
+ return newlyCheckedItemKeys;
+ }
+
+ private List RegisterUncheckedItems(IEnumerable checkedItemKeys)
+ {
+ List newlyUncheckedItemKeys = checkedItemCache.Where(x => !checkedItemKeys.Contains(x.Key) && x.Value).Select(x => x.Key).ToList();
+ if (newlyUncheckedItemKeys.Any() && OnUnchecked != null)
+ {
+ itemsUnchecked = true;
+ uncheckedItems = new List>();
+
+ foreach (string newlyUncheckedItemKey in newlyUncheckedItemKeys)
+ {
+ if (lookupTable.TryGetValue(newlyUncheckedItemKey, out var item))
+ {
+ uncheckedItems.Add(item);
+ }
+ }
+ }
+
+ return newlyUncheckedItemKeys;
+ }
+
+ private void UpdateItemCache(IEnumerable> items)
+ {
+ checkedItemCache = new Dictionary();
+ collapsedItemCache = new Dictionary();
+ lookupTable = new Dictionary>();
+
+ BuildLookupTable(items);
+
+ foreach (var item in lookupTable.Values)
+ {
+ try
+ {
+ checkedItemCache.Add(item.KeyValue, item.IsChecked);
+ if (item.SupportsLazyLoading)
+ {
+ collapsedItemCache.Add(item.KeyValue, item.IsCollapsed);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new TreeViewDuplicateItemsException(item.KeyValue, e);
+ }
+ }
+ }
+
+ private void BuildLookupTable(IEnumerable> items)
+ {
+ foreach (var item in items)
+ {
+ lookupTable[item.KeyValue] = item;
+
+ // Recursively process children
+ var childOptions = item.ChildItems.Select(child =>
+ {
+ // Try to find existing wrapper or create a temporary one
+ var existing = lookupTable.Values.FirstOrDefault(x => x.Item == child);
+ return existing;
+ }).Where(x => x != null);
+
+ if (item.ChildItems.Any())
+ {
+ // For children that don't have wrappers yet, we need to handle them
+ // This is a limitation - child items added directly to TreeViewItem won't have metadata
+ BuildLookupTableFromTreeViewItems(item.ChildItems);
+ }
+ }
+ }
+
+ private void BuildLookupTableFromTreeViewItems(IEnumerable items)
+ {
+ foreach (var item in items)
+ {
+ if (!lookupTable.ContainsKey(item.KeyValue))
+ {
+ // Create a wrapper with default value for items without explicit metadata
+ var wrapper = new TreeViewItemOption(item, default(T));
+ lookupTable[item.KeyValue] = wrapper;
+ }
+
+ BuildLookupTableFromTreeViewItems(item.ChildItems);
+ }
+ }
+ }
+}
diff --git a/InteractiveAutomationToolkit/Components/TreeViewItemOption.cs b/InteractiveAutomationToolkit/Components/TreeViewItemOption.cs
new file mode 100644
index 0000000..423b9ff
--- /dev/null
+++ b/InteractiveAutomationToolkit/Components/TreeViewItemOption.cs
@@ -0,0 +1,199 @@
+namespace Skyline.DataMiner.Utils.InteractiveAutomationScript
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+
+ using Skyline.DataMiner.Net.AutomationUI.Objects;
+
+ ///
+ /// Represents a TreeViewItem with an associated value of type .
+ /// This allows attaching custom metadata to tree view items.
+ ///
+ /// The type of the value associated with this tree view item.
+ public sealed class TreeViewItemOption : IEquatable>
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The underlying TreeViewItem.
+ /// The value to associate with this tree view item.
+ /// When item is null.
+ public TreeViewItemOption(TreeViewItem item, T value)
+ {
+ Item = item ?? throw new ArgumentNullException(nameof(item));
+ Value = value;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ /// Creates a TreeViewItem with the specified parameters.
+ ///
+ /// The unique key for the tree view item.
+ /// The text to display for the tree view item.
+ /// The value to associate with this tree view item.
+ /// The type of the tree view item.
+ /// The child items of this tree view item.
+ /// When keyValue or displayValue is null.
+ public TreeViewItemOption(
+ string keyValue,
+ string displayValue,
+ T value,
+ TreeViewItem.TreeViewItemType itemType = TreeViewItem.TreeViewItemType.CheckBox,
+ IEnumerable> childItems = null)
+ {
+ if (keyValue == null)
+ {
+ throw new ArgumentNullException(nameof(keyValue));
+ }
+
+ if (displayValue == null)
+ {
+ throw new ArgumentNullException(nameof(displayValue));
+ }
+
+ var children = childItems?.Select(x => x.Item).ToList() ?? new List();
+ Item = new TreeViewItem(displayValue, keyValue, children)
+ {
+ ItemType = itemType
+ };
+ Value = value;
+ }
+
+ ///
+ /// Gets the underlying TreeViewItem.
+ ///
+ public TreeViewItem Item { get; }
+
+ ///
+ /// Gets the value associated with this tree view item.
+ ///
+ public T Value { get; }
+
+ ///
+ /// Gets or sets the unique key for the tree view item.
+ ///
+ public string KeyValue
+ {
+ get => Item.KeyValue;
+ set => Item.KeyValue = value;
+ }
+
+ ///
+ /// Gets or sets the text to display for the tree view item.
+ ///
+ public string DisplayValue
+ {
+ get => Item.DisplayValue;
+ set => Item.DisplayValue = value;
+ }
+
+ ///
+ /// Gets or sets the type of the tree view item.
+ ///
+ public TreeViewItem.TreeViewItemType ItemType
+ {
+ get => Item.ItemType;
+ set => Item.ItemType = value;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the tree view item is checked.
+ ///
+ public bool IsChecked
+ {
+ get => Item.IsChecked;
+ set => Item.IsChecked = value;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the tree view item is collapsed.
+ ///
+ public bool IsCollapsed
+ {
+ get => Item.IsCollapsed;
+ set => Item.IsCollapsed = value;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the tree view item supports lazy loading.
+ ///
+ public bool SupportsLazyLoading
+ {
+ get => Item.SupportsLazyLoading;
+ set => Item.SupportsLazyLoading = value;
+ }
+
+ ///
+ /// Gets the child items of this tree view item.
+ ///
+ public IEnumerable ChildItems => Item.ChildItems;
+
+ ///
+ /// Determines whether the specified object is equal to the current object.
+ ///
+ /// The object to compare with the current object.
+ /// true if the specified object is equal to the current object; otherwise, false.
+ public override bool Equals(object obj)
+ {
+ return obj is TreeViewItemOption option && Equals(option);
+ }
+
+ ///
+ /// Determines whether the specified TreeViewItemOption is equal to the current TreeViewItemOption.
+ ///
+ /// The TreeViewItemOption to compare with the current TreeViewItemOption.
+ /// true if the specified TreeViewItemOption is equal to the current TreeViewItemOption; otherwise, false.
+ public bool Equals(TreeViewItemOption other)
+ {
+ if (ReferenceEquals(this, other)) return true;
+ if (ReferenceEquals(null, other)) return false;
+
+ return Item.KeyValue.Equals(other.Item.KeyValue) &&
+ EqualityComparer.Default.Equals(Value, other.Value);
+ }
+
+ ///
+ /// Serves as the default hash function.
+ ///
+ /// A hash code for the current object.
+ public override int GetHashCode()
+ {
+ int hashCode = 11;
+ hashCode ^= 13 * Item.KeyValue.GetHashCode();
+ hashCode ^= 13 * (Value != null ? Value.GetHashCode() : 0);
+ return hashCode;
+ }
+
+ ///
+ /// Returns a string that represents the current object.
+ ///
+ /// A string that represents the current object.
+ public override string ToString()
+ {
+ return $"{DisplayValue} ({KeyValue}) => {Value}";
+ }
+
+ ///
+ /// Determines whether two TreeViewItemOption objects are equal.
+ ///
+ /// The first object to compare.
+ /// The second object to compare.
+ /// true if the objects are equal; otherwise, false.
+ public static bool operator ==(TreeViewItemOption left, TreeViewItemOption right)
+ {
+ return Equals(left, right);
+ }
+
+ ///
+ /// Determines whether two TreeViewItemOption objects are not equal.
+ ///
+ /// The first object to compare.
+ /// The second object to compare.
+ /// true if the objects are not equal; otherwise, false.
+ public static bool operator !=(TreeViewItemOption left, TreeViewItemOption right)
+ {
+ return !Equals(left, right);
+ }
+ }
+}
diff --git a/InteractiveAutomationToolkit/UiResultsExtensions.cs b/InteractiveAutomationToolkit/UiResultsExtensions.cs
index ec55d0b..92b8456 100644
--- a/InteractiveAutomationToolkit/UiResultsExtensions.cs
+++ b/InteractiveAutomationToolkit/UiResultsExtensions.cs
@@ -201,6 +201,42 @@ public static IEnumerable GetCheckedItemKeys(this IUIResults uiResults,
return result.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
}
+ ///
+ /// Gets the names of the tree view items that are expanded according to the UI results.
+ ///
+ /// Represents the information a user has entered or selected in a dialog box of an interactive Automation script.
+ /// The generic tree view widget.
+ /// The type of the value associated with each tree view item.
+ /// The names of tree view items that are expanded.
+ public static IEnumerable GetExpandedItemKeys(this IUIResults uiResults, TreeView treeView)
+ {
+ string[] expandedItems = uiResults.GetExpanded(treeView.DestVar);
+ if (expandedItems == null)
+ {
+ return Array.Empty();
+ }
+
+ return expandedItems.Where(x => !String.IsNullOrWhiteSpace(x)).ToList();
+ }
+
+ ///
+ /// Gets the names of the tree view items that are checked according to the UI results.
+ ///
+ /// Represents the information a user has entered or selected in a dialog box of an interactive Automation script.
+ /// The generic tree view widget.
+ /// The type of the value associated with each tree view item.
+ /// The names of tree view items that are checked.
+ public static IEnumerable GetCheckedItemKeys(this IUIResults uiResults, TreeView treeView)
+ {
+ string result = uiResults.GetString(treeView.DestVar);
+ if (String.IsNullOrEmpty(result))
+ {
+ return Array.Empty();
+ }
+
+ return result.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+
///
/// Gets the time zone info of the client in which the calendar is displayed.
///
diff --git a/InteractiveAutomationToolkitTests/GenericTreeViewTests.cs b/InteractiveAutomationToolkitTests/GenericTreeViewTests.cs
new file mode 100644
index 0000000..7c60a33
--- /dev/null
+++ b/InteractiveAutomationToolkitTests/GenericTreeViewTests.cs
@@ -0,0 +1,274 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Skyline.DataMiner.Utils.InteractiveAutomationScript;
+using System.Linq;
+
+namespace InteractiveAutomationToolkitTests
+{
+ [TestClass]
+ public class GenericTreeViewTests
+ {
+ [TestMethod]
+ public void Constructor_Test()
+ {
+ var rootItems = new[]
+ {
+ new TreeViewItemOption("root1", "Root Item 1", 1),
+ new TreeViewItemOption("root2", "Root Item 2", 2)
+ };
+
+ var treeView = new TreeView(rootItems);
+
+ Assert.IsNotNull(treeView);
+ Assert.AreEqual(2, treeView.Items.Count());
+ Assert.IsFalse(treeView.IsReadOnly);
+ }
+
+ [TestMethod]
+ public void Constructor_WithChildItems_Test()
+ {
+ var childItems = new[]
+ {
+ new TreeViewItemOption("child1", "Child Item 1", "child1data"),
+ new TreeViewItemOption("child2", "Child Item 2", "child2data")
+ };
+
+ var rootItem = new TreeViewItemOption("root", "Root Item", "rootdata", childItems: childItems);
+ var treeView = new TreeView(new[] { rootItem });
+
+ Assert.IsNotNull(treeView);
+ Assert.AreEqual(1, treeView.Items.Count());
+ Assert.AreEqual(3, treeView.GetAllItems().Count()); // 1 root + 2 children
+ }
+
+ [TestMethod]
+ public void TryFindTreeViewItem_Test()
+ {
+ var rootItems = new[]
+ {
+ new TreeViewItemOption("root1", "Root Item 1", 100),
+ new TreeViewItemOption("root2", "Root Item 2", 200)
+ };
+
+ var treeView = new TreeView(rootItems);
+ treeView.UpdateItemCache();
+
+ bool found = treeView.TryFindTreeViewItem("root1", out var item);
+
+ Assert.IsTrue(found);
+ Assert.IsNotNull(item);
+ Assert.AreEqual("root1", item.KeyValue);
+ Assert.AreEqual("Root Item 1", item.DisplayValue);
+ Assert.AreEqual(100, item.Value);
+ }
+
+ [TestMethod]
+ public void TryFindTreeViewItem_NotFound_Test()
+ {
+ var rootItems = new[]
+ {
+ new TreeViewItemOption("root1", "Root Item 1", 100)
+ };
+
+ var treeView = new TreeView(rootItems);
+ treeView.UpdateItemCache();
+
+ bool found = treeView.TryFindTreeViewItem("nonexistent", out var item);
+
+ Assert.IsFalse(found);
+ Assert.IsNull(item);
+ }
+
+ [TestMethod]
+ public void GetAllItems_Test()
+ {
+ var child1 = new TreeViewItemOption("child1", "Child 1", 10);
+ var child2 = new TreeViewItemOption("child2", "Child 2", 20);
+ var rootItem = new TreeViewItemOption("root", "Root", 1, childItems: new[] { child1, child2 });
+
+ var treeView = new TreeView(new[] { rootItem });
+ treeView.UpdateItemCache();
+
+ var allItems = treeView.GetAllItems().ToList();
+
+ Assert.AreEqual(3, allItems.Count);
+ Assert.IsTrue(allItems.Any(x => x.KeyValue == "root"));
+ Assert.IsTrue(allItems.Any(x => x.KeyValue == "child1"));
+ Assert.IsTrue(allItems.Any(x => x.KeyValue == "child2"));
+ }
+
+ [TestMethod]
+ public void GetItems_Depth0_Test()
+ {
+ var child = new TreeViewItemOption("child", "Child", 10);
+ var root = new TreeViewItemOption("root", "Root", 1, childItems: new[] { child });
+
+ var treeView = new TreeView(new[] { root });
+ treeView.UpdateItemCache();
+
+ var depth0Items = treeView.GetItems(0).ToList();
+
+ Assert.AreEqual(1, depth0Items.Count);
+ Assert.AreEqual("root", depth0Items[0].KeyValue);
+ }
+
+ [TestMethod]
+ public void GetItems_Depth1_Test()
+ {
+ var child = new TreeViewItemOption("child", "Child", 10);
+ var root = new TreeViewItemOption("root", "Root", 1, childItems: new[] { child });
+
+ var treeView = new TreeView(new[] { root });
+ treeView.UpdateItemCache();
+
+ var depth1Items = treeView.GetItems(1).ToList();
+
+ Assert.AreEqual(1, depth1Items.Count);
+ Assert.AreEqual("child", depth1Items[0].KeyValue);
+ }
+
+ [TestMethod]
+ public void CheckedValues_Test()
+ {
+ var item1 = new TreeViewItemOption("item1", "Item 1", 100);
+ var item2 = new TreeViewItemOption("item2", "Item 2", 200);
+ var item3 = new TreeViewItemOption("item3", "Item 3", 300);
+
+ var treeView = new TreeView(new[] { item1, item2, item3 });
+ treeView.UpdateItemCache();
+
+ // Simulate checking items
+ item1.IsChecked = true;
+ item3.IsChecked = true;
+ treeView.UpdateItemCache();
+
+ var checkedValues = treeView.CheckedValues.ToList();
+
+ Assert.AreEqual(2, checkedValues.Count);
+ Assert.IsTrue(checkedValues.Contains(100));
+ Assert.IsTrue(checkedValues.Contains(300));
+ Assert.IsFalse(checkedValues.Contains(200));
+ }
+
+ [TestMethod]
+ public void CollapseAndExpand_Test()
+ {
+ var child = new TreeViewItemOption("child", "Child", 10);
+ var root = new TreeViewItemOption("root", "Root", 1, childItems: new[] { child });
+
+ var treeView = new TreeView(new[] { root });
+ treeView.UpdateItemCache();
+
+ treeView.Collapse();
+
+ Assert.IsTrue(root.IsCollapsed);
+ Assert.IsTrue(child.IsCollapsed);
+
+ treeView.Expand();
+
+ Assert.IsFalse(root.IsCollapsed);
+ Assert.IsFalse(child.IsCollapsed);
+ }
+
+ [TestMethod]
+ public void TreeViewItemOption_Equals_Test()
+ {
+ var item1 = new TreeViewItemOption("key1", "Display 1", 100);
+ var item2 = new TreeViewItemOption("key1", "Display 1", 100);
+ var item3 = new TreeViewItemOption("key2", "Display 2", 200);
+
+ Assert.AreEqual(item1, item2);
+ Assert.AreNotEqual(item1, item3);
+ }
+
+ [TestMethod]
+ public void TreeViewItemOption_Properties_Test()
+ {
+ var item = new TreeViewItemOption("key", "display", "value");
+
+ Assert.AreEqual("key", item.KeyValue);
+ Assert.AreEqual("display", item.DisplayValue);
+ Assert.AreEqual("value", item.Value);
+
+ item.KeyValue = "newKey";
+ item.DisplayValue = "newDisplay";
+
+ Assert.AreEqual("newKey", item.KeyValue);
+ Assert.AreEqual("newDisplay", item.DisplayValue);
+ Assert.AreEqual("value", item.Value); // Value is read-only
+ }
+
+ [TestMethod]
+ public void CheckedLeaves_And_CheckedNodes_Test()
+ {
+ var leaf1 = new TreeViewItemOption("leaf1", "Leaf 1", 10);
+ var leaf2 = new TreeViewItemOption("leaf2", "Leaf 2", 20);
+ var node = new TreeViewItemOption("node", "Node", 100, childItems: new[] { leaf1, leaf2 });
+
+ var treeView = new TreeView(new[] { node });
+ treeView.UpdateItemCache();
+
+ // Check both node and one leaf
+ node.IsChecked = true;
+ leaf1.IsChecked = true;
+ treeView.UpdateItemCache();
+
+ var checkedNodes = treeView.CheckedNodes.ToList();
+ var checkedLeaves = treeView.CheckedLeaves.ToList();
+ var checkedNodeValues = treeView.CheckedNodeValues.ToList();
+ var checkedLeafValues = treeView.CheckedLeafValues.ToList();
+
+ Assert.AreEqual(1, checkedNodes.Count);
+ Assert.AreEqual("node", checkedNodes[0].KeyValue);
+ Assert.AreEqual(1, checkedNodeValues.Count);
+ Assert.AreEqual(100, checkedNodeValues[0]);
+
+ Assert.AreEqual(1, checkedLeaves.Count);
+ Assert.AreEqual("leaf1", checkedLeaves[0].KeyValue);
+ Assert.AreEqual(1, checkedLeafValues.Count);
+ Assert.AreEqual(10, checkedLeafValues[0]);
+ }
+
+ [TestMethod]
+ public void EmptyConstructor_Test()
+ {
+ var treeView = new TreeView(System.Linq.Enumerable.Empty>());
+
+ Assert.IsNotNull(treeView);
+ Assert.AreEqual(0, treeView.Items.Count());
+ Assert.AreEqual(0, treeView.GetAllItems().Count());
+ }
+
+ [TestMethod]
+ public void ParameterlessConstructor_Test()
+ {
+ var treeView = new TreeView();
+
+ Assert.IsNotNull(treeView);
+ Assert.AreEqual(0, treeView.Items.Count());
+ Assert.AreEqual(0, treeView.GetAllItems().Count());
+ Assert.IsFalse(treeView.IsReadOnly);
+ }
+
+ [TestMethod]
+ public void NullValue_Test()
+ {
+ // Test that null values are handled correctly
+ var item = new TreeViewItemOption("key", "display", null);
+
+ Assert.IsNull(item.Value);
+ Assert.AreEqual("key", item.KeyValue);
+ Assert.AreEqual("display", item.DisplayValue);
+ }
+
+ [TestMethod]
+ public void ComplexType_Test()
+ {
+ // Test with a complex custom type
+ var customData = new { Id = 123, Name = "Test", Active = true };
+ var item = new TreeViewItemOption