C2024-036 Add Filtering ability to the main PROMS procedure tree.
Typing a procedure number or title into the search box and pressing enter/clicking the tree view search button will down base the PROMS procedure tree to matching procedures/folders.
This commit is contained in:
@@ -22,6 +22,9 @@ using Volian.Base.Library;
|
||||
using Volian.Print.Library;
|
||||
using JR.Utils.GUI.Forms;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
|
||||
|
||||
@@ -2771,6 +2774,199 @@ namespace VEPROMS
|
||||
}
|
||||
}
|
||||
|
||||
//C2024-036 IntelliSense
|
||||
// Recursively Expand the Tree
|
||||
// up to the procedure level
|
||||
// Since items are only loaded as they are expanded,
|
||||
// this is needed to search for matching text.
|
||||
private void LoadAllProcedures(VETreeNode tn)
|
||||
{
|
||||
if (tn == null || tn.VEObject.IsProcedure)
|
||||
return;
|
||||
|
||||
if (!tn.IsExpanded)
|
||||
{ tn.Expand(); }
|
||||
|
||||
foreach (var nde in tn.Nodes)
|
||||
LoadAllProcedures((VETreeNode)nde);
|
||||
}
|
||||
|
||||
//C2024-036 IntelliSense
|
||||
// Return a list containing all
|
||||
// items in the Tree that match the entered text
|
||||
// + any item that is a parent of those.
|
||||
private List<IVEDrillDownReadOnly> GetMatchingTreeNodes(VETreeNode tn, string texttomatch)
|
||||
{
|
||||
List<IVEDrillDownReadOnly> matches = new List<IVEDrillDownReadOnly>();
|
||||
|
||||
//add any item that matches text
|
||||
//and all parents back up to the top most item
|
||||
if (tn.Text.ToLower().Contains(texttomatch.ToLower()))
|
||||
{
|
||||
matches.Add(tn.VEObject);
|
||||
VETreeNode parent_tn = (VETreeNode) tn.Parent;
|
||||
while (parent_tn != null)
|
||||
{
|
||||
if (!matches.Contains(parent_tn.VEObject))
|
||||
{ matches.Add(parent_tn.VEObject); }
|
||||
parent_tn = (VETreeNode)parent_tn.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tn.VEObject.IsProcedure)
|
||||
{
|
||||
//if not at a procedure level,
|
||||
//loop through sub items
|
||||
//to check those for matches
|
||||
foreach (VETreeNode nde in tn.Nodes.OfType<VETreeNode>())
|
||||
{
|
||||
if (nde.Text != "Dummy VETreeNode(IVEDrillDownReadOnly o)")
|
||||
{
|
||||
List<IVEDrillDownReadOnly> tmpmatches = GetMatchingTreeNodes(nde, texttomatch);
|
||||
matches.AddRange(from mtch in tmpmatches
|
||||
where !matches.Contains(mtch)
|
||||
select mtch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
//C2024-036 IntelliSense
|
||||
// Recursively loop through the Child Nodes
|
||||
// in the TreeView
|
||||
// remove anything not in the list
|
||||
private bool FilterTreeNodes(VETreeNode tn, List<IVEDrillDownReadOnly> filterlist)
|
||||
{
|
||||
if (tn == null)
|
||||
return false;
|
||||
|
||||
if (!filterlist.Contains(tn.VEObject) && (tn != (VETreeNode) tv.Nodes[0])) //Note:Always keep the top node
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (!tn.VEObject.IsProcedure)
|
||||
{
|
||||
//if not a procedure
|
||||
//mark the sub-items that need removal
|
||||
List<TreeNode> lst = (from VETreeNode nde in tn.Nodes.OfType<VETreeNode>()
|
||||
where FilterTreeNodes(nde, filterlist)
|
||||
select (TreeNode) nde).ToList();
|
||||
|
||||
//remove all items in the removal list
|
||||
//need to do this separately as can't modify the collection while looping through it above
|
||||
while (lst.Count > 0)
|
||||
{
|
||||
tn.Nodes.Remove(lst[0]);
|
||||
lst.Remove(lst[0]);
|
||||
}
|
||||
|
||||
//found a folder that matched that has no matching procedures
|
||||
//if this is case, allow folder to be expanded
|
||||
if (tn.Nodes.Count == 0 && filterlist.Contains(tn.VEObject))
|
||||
{
|
||||
tn.ChildrenLoaded = false;
|
||||
tn.RefreshNode();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
//C2024-036 IntelliSense
|
||||
//Collection to block for task to complete
|
||||
private BlockingCollection<string> blockingQueueFilter = new BlockingCollection<string>();
|
||||
|
||||
//C2024-036 IntelliSense
|
||||
// When Text Changes, Perform the filtering
|
||||
// Note that uses a blocking collection
|
||||
// in case multiple events fire at the same time
|
||||
// this is to prevent incorrect behavior when
|
||||
//doing something like hitting backspace a bunch
|
||||
private void SubmitFilter(object sender, EventArgs e)
|
||||
{
|
||||
//Consumer
|
||||
Task.Run(() =>
|
||||
{
|
||||
//Blocks until a new filter is available
|
||||
while (!blockingQueueFilter.IsCompleted)
|
||||
{
|
||||
string fltrtxt = blockingQueueFilter.Take();
|
||||
ProcessFilter(fltrtxt);
|
||||
}
|
||||
});
|
||||
|
||||
//Producer
|
||||
Task.Run(() => { blockingQueueFilter.Add(txtFilter?.Text); });
|
||||
|
||||
}
|
||||
|
||||
//C2024-036 IntelliSense
|
||||
// When Enter Key is pressed in the TextBox
|
||||
private void FilterEnterKey(object sender, KeyPressEventArgs e)
|
||||
{
|
||||
if (e.KeyChar == '\r') // enter key pressed
|
||||
{
|
||||
e.Handled = true;
|
||||
SubmitFilter(sender, e);
|
||||
}
|
||||
}
|
||||
|
||||
//C2024-036 IntelliSense
|
||||
// When Text Changes, Perform the filtering
|
||||
// Note that uses Invoke to
|
||||
// avoid multi-threaded problems
|
||||
//with the blocking collection
|
||||
private void ProcessFilter(string fltrtxt)
|
||||
{
|
||||
//Step 1: reload the base tree
|
||||
VETreeNode tbase = (VETreeNode)tv.Nodes[0];
|
||||
tbase.ChildrenLoaded = false;
|
||||
this.Invoke((Action) (() => { tbase.RefreshNode(); }));
|
||||
|
||||
if (!string.IsNullOrEmpty(fltrtxt))
|
||||
{
|
||||
//Step 2: Expand all TreeNodes
|
||||
this.Invoke((Action)(() => { LoadAllProcedures(tbase); }));
|
||||
|
||||
//Step 3: get items that match filter to those containing the title or number
|
||||
List<IVEDrillDownReadOnly> filterlist = GetMatchingTreeNodes(tbase, fltrtxt);
|
||||
|
||||
//Step 4: filter to those containing the title or number
|
||||
this.Invoke((Action)(() => {FilterTreeNodes(tbase, filterlist);}));
|
||||
|
||||
//Step 5: refresh the view
|
||||
this.Invoke((Action)(() => {tv.Update();}));
|
||||
|
||||
//Step 6: select 1st procedure in tree view
|
||||
this.Invoke((Action)(() => {SelectFirstProcedure(filterlist);}));
|
||||
}
|
||||
|
||||
//Step 7: set progress bar as done
|
||||
this.Invoke((Action)(() => {ProgBarText = "Filtering Complete";}));
|
||||
|
||||
}
|
||||
|
||||
//C2024-036 IntelliSense
|
||||
//Gives Focus to last child in first section of treeview
|
||||
//then given focus to treeview
|
||||
//so that can click enter and open 1st found item/procedure
|
||||
void SelectFirstProcedure(List<IVEDrillDownReadOnly> filterlist)
|
||||
{
|
||||
TreeNode tn = tv.Nodes[0];
|
||||
while (tn.Nodes.Count > 0 && filterlist.Contains(((VETreeNode) tn).VEObject))
|
||||
{
|
||||
tn = tn.Nodes[0];
|
||||
}
|
||||
|
||||
tv.SelectedNode = tn;
|
||||
tv.Enabled = true;
|
||||
tv.SelectedNode.Collapse();
|
||||
tv.Focus();
|
||||
}
|
||||
|
||||
void btnPrevious_Click(object sender, EventArgs e)
|
||||
{
|
||||
CurrentID = FindPreviousLine();
|
||||
|
Reference in New Issue
Block a user