C2024-036 Add Filtering ability to the main PROMS procedure tree. #608

Merged
jjenko merged 1 commits from C2024-036 into Development 2025-09-04 15:52:58 -04:00
2 changed files with 238 additions and 13 deletions

View File

@@ -71,9 +71,12 @@ namespace VEPROMS
this.labelItem10 = new DevComponents.DotNetBar.LabelItem(); this.labelItem10 = new DevComponents.DotNetBar.LabelItem();
this.bottomProgBar = new DevComponents.DotNetBar.ProgressBarItem(); this.bottomProgBar = new DevComponents.DotNetBar.ProgressBarItem();
this.labelItem9 = new DevComponents.DotNetBar.LabelItem(); this.labelItem9 = new DevComponents.DotNetBar.LabelItem();
this.lblPreEditView = new DevComponents.DotNetBar.LabelItem();
this.lblEditView = new DevComponents.DotNetBar.LabelItem(); this.lblEditView = new DevComponents.DotNetBar.LabelItem();
this.labelItem11 = new DevComponents.DotNetBar.LabelItem(); this.labelItem11 = new DevComponents.DotNetBar.LabelItem();
this.btnItemInfo = new DevComponents.DotNetBar.ButtonItem(); this.btnItemInfo = new DevComponents.DotNetBar.ButtonItem();
this.btnFilter = new DevComponents.DotNetBar.ButtonItem();
this.txtFilter = new DevComponents.DotNetBar.TextBoxItem();
this.lblItemID = new DevComponents.DotNetBar.LabelItem(); this.lblItemID = new DevComponents.DotNetBar.LabelItem();
this.lblResolution = new DevComponents.DotNetBar.LabelItem(); this.lblResolution = new DevComponents.DotNetBar.LabelItem();
this.btnEditItem = new DevComponents.DotNetBar.ButtonItem(); this.btnEditItem = new DevComponents.DotNetBar.ButtonItem();
@@ -524,19 +527,22 @@ namespace VEPROMS
this.bottomBar.Font = new System.Drawing.Font("Segoe UI", 9F); this.bottomBar.Font = new System.Drawing.Font("Segoe UI", 9F);
this.bottomBar.IsMaximized = false; this.bottomBar.IsMaximized = false;
this.bottomBar.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] { this.bottomBar.Items.AddRange(new DevComponents.DotNetBar.BaseItem[] {
this.labelItem10, this.btnFilter,
this.txtFilter,
this.labelItem10,
this.bottomProgBar, this.bottomProgBar,
this.labelItem9, this.labelItem9,
this.lblEditView, this.lblPreEditView,
this.labelItem11, this.lblEditView,
this.btnItemInfo, this.labelItem11,
this.lblItemID, this.btnItemInfo,
this.lblResolution, this.lblItemID,
this.btnEditItem, this.lblResolution,
this.lblUser, this.btnEditItem,
this.lblLastChange, this.lblUser,
this.btnStepRTF, this.lblLastChange,
this.btnFixMSWord}); this.btnStepRTF,
this.btnFixMSWord});
this.bottomBar.Location = new System.Drawing.Point(5, 573); this.bottomBar.Location = new System.Drawing.Point(5, 573);
this.bottomBar.Name = "bottomBar"; this.bottomBar.Name = "bottomBar";
this.bottomBar.Size = new System.Drawing.Size(1185, 25); this.bottomBar.Size = new System.Drawing.Size(1185, 25);
@@ -546,6 +552,21 @@ namespace VEPROMS
this.bottomBar.TabStop = false; this.bottomBar.TabStop = false;
this.bottomBar.Text = "bar1"; this.bottomBar.Text = "bar1";
// //
// btnFilter
//
this.btnFilter.Name = "btnFilter";
this.btnFilter.Text = "Tree View Search:";
this.btnFilter.Click += SubmitFilter;
//
// txtFilter
//
this.txtFilter.TextBox.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F);
this.txtFilter.Name = "txtFilter";
this.txtFilter.TextBoxWidth = 120;
this.txtFilter.TextBox.TabIndex = 0;
this.txtFilter.TextBox.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.FilterEnterKey);
this.superTooltip1.SetSuperTooltip(this.txtFilter, new DevComponents.DotNetBar.SuperTooltipInfo("Filter", "", "This will filter the Procedure Tree in PROMS to Procedures containing entered words in the Procedure Number or Title. Press enter or the Tree View Search button to submit.", null, null, DevComponents.DotNetBar.eTooltipColor.Gray));
//
// txtSearch // txtSearch
// //
this.txtSearch.Dock = System.Windows.Forms.DockStyle.Right; this.txtSearch.Dock = System.Windows.Forms.DockStyle.Right;
@@ -647,6 +668,11 @@ namespace VEPROMS
this.lblEditView.ForeColor = System.Drawing.SystemColors.MenuText; this.lblEditView.ForeColor = System.Drawing.SystemColors.MenuText;
this.lblEditView.Name = "lblEditView"; this.lblEditView.Name = "lblEditView";
this.lblEditView.Text = "Edit"; this.lblEditView.Text = "Edit";
// lblEditView
//
this.lblPreEditView.BorderSide = DevComponents.DotNetBar.eBorderSide.Left;
this.lblPreEditView.BorderType = DevComponents.DotNetBar.eBorderType.Bump;
this.lblPreEditView.Name = "lblPreEditView";
// //
// labelItem11 // labelItem11
// //
@@ -1646,9 +1672,9 @@ namespace VEPROMS
this.ResumeLayout(false); this.ResumeLayout(false);
} }
#endregion #endregion
private DevComponents.DotNetBar.RibbonControl ribbonControl1; private DevComponents.DotNetBar.RibbonControl ribbonControl1;
private DevComponents.DotNetBar.Office2007StartButton office2007StartButton1; private DevComponents.DotNetBar.Office2007StartButton office2007StartButton1;
private DevComponents.DotNetBar.ItemContainer itemContainer1; private DevComponents.DotNetBar.ItemContainer itemContainer1;
private DevComponents.DotNetBar.ItemContainer itemContainer2; private DevComponents.DotNetBar.ItemContainer itemContainer2;
@@ -1744,8 +1770,11 @@ namespace VEPROMS
private DevComponents.DotNetBar.TabItem tabItemLibDocs; private DevComponents.DotNetBar.TabItem tabItemLibDocs;
private Volian.Controls.Library.DisplayTags displayTags; private Volian.Controls.Library.DisplayTags displayTags;
private Volian.Controls.Library.DisplaySearch displaySearch1; private Volian.Controls.Library.DisplaySearch displaySearch1;
private DevComponents.DotNetBar.LabelItem lblPreEditView;
private DevComponents.DotNetBar.LabelItem lblEditView; private DevComponents.DotNetBar.LabelItem lblEditView;
private DevComponents.DotNetBar.ButtonItem btnItemInfo; private DevComponents.DotNetBar.ButtonItem btnItemInfo;
private DevComponents.DotNetBar.ButtonItem btnFilter;
private DevComponents.DotNetBar.TextBoxItem txtFilter;
private DevComponents.DotNetBar.ButtonItem btnFixMSWord; private DevComponents.DotNetBar.ButtonItem btnFixMSWord;
private Volian.Controls.Library.DisplayBookMarks displayBookMarks; private Volian.Controls.Library.DisplayBookMarks displayBookMarks;
//private DevComponents.DotNetBar.LabelItem lblLocked; //private DevComponents.DotNetBar.LabelItem lblLocked;

View File

@@ -22,6 +22,9 @@ using Volian.Base.Library;
using Volian.Print.Library; using Volian.Print.Library;
using JR.Utils.GUI.Forms; using JR.Utils.GUI.Forms;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Collections.Concurrent;
using System.Threading.Tasks;
[assembly: log4net.Config.XmlConfigurator(Watch = true)] [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) void btnPrevious_Click(object sender, EventArgs e)
{ {
CurrentID = FindPreviousLine(); CurrentID = FindPreviousLine();