I am parsing an XML file and displaying it as a TreeView. Then, I need to do a phrase search, deleting all branches that do not contain a mention
Now I have the following recursive function code, but it does not work correctly. I must say right away that there can be quite a lot of branches and I cannot know in advance the degree of nesting
void RecursiveSearch(TreeViewItem item)
{
foreach (TreeViewItem children in item.Items)
{
if (children.Items.Count > 0)
{
RecursiveSearch(children);
}
if (children.Header.ToString().Contains(searchTB.Text) == false)
{
item.Items.Remove(children);
return;
}
}
return;
}
And call this on Button Click:
private void searchBtn_Click(object sender, RoutedEventArgs e)
{
RecursiveSearch((TreeViewItem)treeView.Items[0]);
}
Could you suggest what is wrong with it?
Thanks in advance
From what i read in your comments my suggestion would:
void RecursiveSearch(TreeViewItem item)
{
foreach (TreeViewItem children in item.Items)
{
if (children.Items.Count > 0)
{
RecursiveSearch(children);
}
if (children.Header.ToString().Contains(searchTB.Text) == false)
{
If (children.Items.Count == 0)
{
item.Items.Remove(children);
}
return;
}
}
return;
}
Related
I have a WPF TreeView which is populated with many child controls. All of them with children themselves are TreeViewItems and all without children are Checkboxes. Currently it has about 1,600 Checkbox children and sub-children. I also have a Textbox which is used to search through the tree by matching data in the checkboxes Tags.
The following code filters the TreeView when a key is typed into the Textbox. If this event is triggered when the Textbox is empty then a different algorithm runs which instead shows all children and collapses those which are TreeViewItems.
My question has two parts to it, why doesn't the filter method work properly? It is letting though results which do not satisfy the search term.
And secondly, how can I optimise the search function? It currently locks up my UI thread for a couple of seconds while the ShowAndCollapse algorithm appears to not.
// 'MainTree' is my TreeView being searched through
private void SearchTermTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = (TextBox)sender;
if (textBox.Text.Length == 0)
{
ShowAndCollapse(MainTree);
}
else
{
Filter(MainTree, textBox.Text);
}
}
private void ShowAndCollapse(ItemsControl parent)
{
foreach (Control control in parent.Items)
{
control.Visibility = Visibility.Visible;
if (control is TreeViewItem treeViewItem)
{
ShowAndCollapse(treeViewItem);
treeViewItem.IsExpanded = false;
}
}
}
private void Filter(ItemsControl parent, string searchTerm)
{
foreach (Control child in parent.Items)
{
if (child is TreeViewItem treeViewItem)
{
Filter(treeViewItem, searchTerm);
if (IsEmpty(treeViewItem))
{
treeViewItem.IsExpanded = false;
treeViewItem.Visibility = Visibility.Collapsed;
}
else
{
treeViewItem.IsExpanded = true;
treeViewItem.Visibility = Visibility.Visible;
}
}
else if (child is CheckBox checkBox)
{
foreach (string term in searchTerm.ToLower().Split(' '))
{
if (!checkBox.Tag.ToString().ToLower().Contains(term))
{
checkBox.Visibility = Visibility.Collapsed;
return;
}
}
checkBox.Visibility = Visibility.Visible;
}
else
throw new Exception("Unexpected child type!");
}
}
private bool IsEmpty(TreeViewItem treeViewItem)
{
foreach (Control item in treeViewItem.Items)
{
if (item.Visibility == Visibility.Visible)
{
return false;
}
}
return true;
}
Thanks!
I think your return statement inside your method Filter may be the problem, possible fix would be to use break in the if statement and move checkbox.Visibility = Visibility.Visible above the for loop.
To optimize the method, look into using async - await or do as Abion47 suggested and do your filtering and sorting on the underlying data source.
How do I show a messagebox based on the various SelectedText in the Combobox? It currently just returns a NULL value when running.
I need to show the specific messagebox for each Combobox Text as once I can do this then depending on the SelectedText different SQL Connections will be used and Queries run.
I've included my code below. After some research it seems that the SelectedText control will always return a null value as it loses focus. How do I get around this?
private void button2_Click(object sender, EventArgs e)
{
if(comboSelectServer.SelectedText == "SERV1")
{
MessageBox.Show("SERV1");
}
else if(comboSelectServer.SelectedText == "SERV2")
{
MessageBox.Show("SERV2");
}
else if(comboSelectServer.SelectedText == "SERV3")
{
MessageBox.Show("SERV3");
}
}
Try this.
if (comboSelectServer.Text == "SERV1")
{
MessageBox.Show("SERV1");
}
else if (comboSelectServer.Text == "SERV2")
{
MessageBox.Show("SERV2");
}
else if (comboSelectServer.Text == "SERV3")
{
MessageBox.Show("SERV3");
}
However, this is easier...
if (comboSelectServer.SelectedIndex == 0) //SERV1
{
MessageBox.Show("SERV1");
}
else if (comboSelectServer.SelectedIndex == 1) //SERV2
{
MessageBox.Show("SERV2");
}
else if (comboSelectServer.SelectedIndex == 2) //SERV3
{
MessageBox.Show("SERV3");
}
Try like this
private void button2_Click(object sender, EventArgs e)
{
if(comboSelectServer.SelectedItem.ToString()== "SERV1")
{
MessageBox.Show("SERV1");
}
else if(comboSelectServer.SelectedItem.ToString()== "SERV2")
{
MessageBox.Show("SERV2");
}
else if(comboSelectServer.SelectedItem.ToString()== "SERV3")
{
MessageBox.Show("SERV3");
}
}
Maybe I'm missing something, but why not simply do:
private void button2_Click(object sender, EventArgs e)
{
MessageBox.Show(comboSelectServer.SelectedItem.ToString());
}
After reading this question, it has become apparent to me that I am writing my event incorrectly. However, I have no idea how I am going to be able to re-write what I have written using Object sender. My event adds the text from selected checkboxes to a two-dimensional list (report), and the order in report must be the same as the order of the selected checkboxes. Also, no more than two checkboxes can be selected at a time. Here is the event:
void checkedListBox_ItemCheck(CheckedListBox chkdlstbx, ItemCheckEventArgs e)
{
int index = Convert.ToInt32(chkdlstbx.Tag);
if ((chkdlstbx.CheckedItems.Count == 0) && (e.CurrentValue == CheckState.Unchecked))
{
Var.report[index].Add(chkdlstbx.Text);
}
if ((chkdlstbx.CheckedItems.Count == 1) && (e.CurrentValue == CheckState.Checked))
{
Var.report[index].RemoveAt(0);
}
if ((chkdlstbx.CheckedItems.Count == 1) && (e.CurrentValue == CheckState.Unchecked))
{
if (chkdlstbx.SelectedIndex < chkdlstbx.CheckedIndices[0])
{
Var.report[index].Insert(0, chkdlstbx.Text);
}
else
{
Var.report[index].Add(chkdlstbx.Text);
}
}
if ((chkdlstbx.CheckedItems.Count == 2) && (e.CurrentValue == CheckState.Checked))
{
if (chkdlstbx.SelectedIndex == chkdlstbx.CheckedIndices[0])
{
Var.report[index].RemoveAt(0);
}
else
{
Var.report[index].RemoveAt(1);
}
}
if ((chkdlstbx.CheckedItems.Count == 2) && (e.CurrentValue == CheckState.Unchecked))
{
e.NewValue = CheckState.Unchecked;
}
updateReport();
}
It is being called by this line:
chkdlstbx.ItemCheck += new ItemCheckEventHandler(checkedListBox_ItemCheck);
If anyone could help me re-write my event using object, that'd be awesome. I'm not really sure how else I would go about solving this problem!
This should suffice:
void checkedListBox_ItemCheck(object sender, ItemCheckEventArgs e)
{
CheckedListBox chkdlstbx = sender as CheckedListBox;
if (chkdlstbx == null)
{
throw new InvalidArgumentException();
}
....
}
I am trying to move multiple selected item from one ListBox to another ListBox using this code
protected void imgbtnMoveRightListBox_Click(object sender, ImageClickEventArgs e)
{
foreach (var item in lstboxSkill.SelectedItem)
{
lstBBoxSkill2.Items.Add(item);
}
}
but it shows an error
foreach statement cannot operate on variables of type
'System.Web.UI.WebControls.ListItem' because
'System.Web.UI.WebControls.ListItem' does not contain a public
definition for 'GetEnumerator'
I don't know why this error occured.
Please help me to fix it
Check this code.
protected void imgbtnMoveRightListBox_Click(object senderImageClickEventArge)
{
foreach (ListItem item in lstboxSkill.Items)
{
lstBBoxSkill2.Items.Add(item);
}
}
Please check this snap shot I have created and its working fine.
and the code behind code is given below:
protected void Page_Load(object sender, EventArgs e)
{
lstboxSkill.Items.Add("ASP.Net");
lstboxSkill.Items.Add("C#");
lstboxSkill.Items.Add("AJAX");
lstboxSkill.Items.Add("JavaScript");
lstboxSkill.Items.Add("HTML");
lstboxSkill.Items.Add("HTML5");
lstboxSkill.Items.Add("JQuery");
}
protected void imgbtnMoveRightListBox_Click(object sender, EventArgs e)
{
foreach (ListItem Item in lstboxSkill.Items)
{
if (Item.Selected == true)
{
lstBBoxSkill2.Items.Add(Item);
}
}
}
This is because the SelectedItem property returns only the item with the lowest index out of the list of items selected. You should change your code as
protected void imgbtnMoveRightListBox_Click(object sender, EventArgs e)
{
foreach (ListItem Item in lstboxSkill.Items)
{
if (Item.Selected == true)
{
lstBBoxSkill2.Items.Add(Item);
}
}
}
And set both listbox SelectionMode="Multiple".
Hope this will help you. Don't forget marked as answer
Thanks
Solomon S.
lstboxSkill.SelectedItem is a ListItem, which is neither an array nor an object collection that implements the System.Collections.IEnumerable<T> or System.Collections.Generic.IEnumerable<T> interface, so it's not possible to do foreach against it.
I think this is what you're looking for:
foreach (var item in lstboxSkill.Items)
{
if (item.Selected)
{
lstBBoxSkill2.Items.Add(item);
}
}
protected void imgbtnMoveRightListBox_Click(object sender, EventArgs e)
{
lbltxt.Visible = false;
if (ListBox1.SelectedIndex >= 0) // in this we are checking whether a single item is clicked.
{
for (int i = 0; i < ListBox1.Items.Count; i++) // we are looping through the list box items
{
if (ListBox1.Items[i].Selected) // finding the selected items
{
if (!arraylist1.Contains(ListBox1.Items[i]))
{
arraylist1.Add(ListBox1.Items[i]); //if found then adding those items to the array list
}
}
}
for (int i = 0; i < arraylist1.Count; i++)
{
if (!ListBox2.Items.Contains(((ListItem)arraylist1[i])))
{
ListBox2.Items.Add(((ListItem)arraylist1[i])); // we are adding the array elements to the second list box
}
ListBox1.Items.Remove(((ListItem)arraylist1[i]));
}
ListBox2.SelectedIndex = -1;
}
else
{
lbltxt.Visible = true;
lbltxt.Text = "Please select atleast one in Listbox1 to move";
}
}
Source
I'm using WinForms TreeView and reaction to AfterLabelEdit event. Here's the snippet of the code:
if (e.Label.Contains("|"))
{
if (WantAutofix())
{
label = e.Label.Replace('|', '_');
}
else
{
e.CancelEdit = true;
e.Node.BeginEdit();
return;
}
}
The problem is that when user doesn't want automatic fix of bad character, node doesn't stay in edit mode. Any way to fix this?
A few things to keep in mind:
The AfterLabelEdit event always ends edit mode after it is raised, even if you call BeginEdit in the middle of your event handler. You can use TreeView.BeginInvoke to "leapfrog" this by having EditMode start up again after the TreeView does its thing. (NOTE: this does not create a new thread or race condition, it simply delays the method for 1 window message.) There is more information on some of the issues with this event here (though it suggests what I think is a worse solution).
e.Label is null if the user didn't make any changes, so when we "leapfrog" with BeginInvoke, it is as if the user didn't make any changes, so we also need to handle that case.
BeginInvoke is an acceptable workaround in this case, you should find it to be very reliable in this situation.
This works very well for me, tested with .NET 2.0:
private void treeView1_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
{
//we have to handle both the first and future edits
if ((e.Label != null && e.Label.Contains("|") || (e.Label == null && e.Node.Text.Contains("|"))))
{
if (WantAutofix())
{
e.CancelEdit = true;
if(e.Label != null)
e.Node.Text = e.Label.Replace('|', '_');
else
e.Node.Text = e.Node.Text.Replace('|', '_');
}
else
{
//lets the treeview finish up its OnAfterLabelEdit method
treeView1.BeginInvoke(new MethodInvoker(delegate() { e.Node.BeginEdit(); }));
}
}
}
private bool WantAutofix()
{
return MessageBox.Show("You entered a |, you want me to AutoFix?", String.Empty, MessageBoxButtons.YesNo) == DialogResult.Yes;
}
Use EndEdit and Replace the "bad character" if user want automatic fix
You could try making the BeginEdit() occur asynchronously:
private void treeView1_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
{
if (e.Label.Contains("|"))
{
if (WantAutofix())
{
}
else
{
e.CancelEdit = true;
BeginInvoke(new ActionDelegate(new NodeBeginEditAsync(e.Node).Execute));
return;
}
}
}
public class NodeBeginEditAsync
{
private readonly TreeNode _node;
public NodeBeginEditAsync(TreeNode node)
{
_node = node;
}
public void Execute()
{
_node.BeginEdit();
}
}
public delegate void ActionDelegate();
That way the CancelEdit is given a chance to complete before a new BeginEdit tries to take over.
try this...
TreeNode node = tv.SelectedNode;
if (tv.SelectedNode.Parent == null)
{
node.TreeView.LabelEdit = false;
}
else
{
node.Text = FieldName.Text;
if (node == null) { return; }
node.TreeView.LabelEdit = true;
node.BeginEdit();
}