While using asp:TreeView control on web form and populating the nodes in code behind, it expands and collapses as expected. However, when I added OnTreeNodeExpanded event it caused a postback but never fired the event tired to this event and prevented the node from expanding. I am trying to save the nodes that are expanded in session and restore the treeview expansion state when the user revisit the page. Any ideas how to accomplish this with this approach.
Thanks!
Here is the code snippet:
<asp:TreeView ID="tvDevices" runat="server" OnTreeNodeExpanded="TvDevices_OnTreeNodeExpandedCollapsed" OnTreeNodeCollapsed="TvDevices_OnTreeNodeExpandedCollapsed"/>
protected void Page_Load(object sender, EventArgs e)
{
foreach (string s in devices)
{
TreeNode tn = new TreeNode();
tn.Text = s.toString();
tn.SelectAction = TreeNodeSelectAction.None;
tvDevices.Ndes.Add(tn);
}
if (IsPostBack)
{
if (Session["tvDevicesState"] == null)
tvDevices.CollapseAll(); //Default to collapsed state
else
{
RestoreTreeViewState(tvDevices.Nodes, Session["tvDevicesState"] as List<string>);
}
}
else
{
tvDevices.CollapseAll();
}
}
private void SaveTreeViewState(TreeNodeCollection treeNodes, List<string> expandedList)
{
foreach (TreeNode treeNode in treeNodes)
{
if (treeNode.ChildNodes.Count > 0)
{
if (treeNode.Expanded.HasValue && treeNode.Expanded == true)
{
expandedList.Add(treeNode.Text);
}
SaveTreeViewState(treeNode.ChildNodes, expandedList);
}
}
}
private void RestoreTreeViewState(TreeNodeCollection treeNodes, List<string> expandedList)
{
foreach (TreeNode treeNode in treeNodes)
{
treeNode.Expanded = false;
if (expandedList.Contains(treeNode.Text))
{
if (treeNode.ChildNodes.Count > 0)
{
treeNode.Expanded = true;
RestoreTreeViewState(treeNode.ChildNodes, expandedList);
}
}
}
}
protected void TvDevices_OnTreeNodeExpandedCollapsed(object sender, EventArgs e)
{
List<string> expandedList = new List<string>();
SaveTreeViewState(tvDevices.Nodes, expandedList);
Session["tvDevicesState"] = expandedList;
}
I figured out the reason why the OnTreeNodeExpanded event was not fired. In my case, the reason was that there was no value property attached to the tree nodes. After adding a value to each node, the event was being fired.
Below is the working code.
<asp:TreeView ID="tvDevices" runat="server" OnTreeNodeExpanded="TvDevices_OnTreeNodeExpandedCollapsed" OnTreeNodeCollapsed="TvDevices_OnTreeNodeExpandedCollapsed"/>
Code behind:
protected void Page_Load(object sender, EventArgs e)
{
List<TreeNode> root = new List<TreeNode>();
for(int i = 0; i < 5; i++)
{
root.Add(new TreeNode("Parent Node " + i, i.ToString()));
}
foreach (TreeNode tn in root)
{
tn.ChildNodes.Add(new TreeNode("First Child", "first"));
tn.ChildNodes.Add(new TreeNode("Second Child", "second"));
tvDevices.Nodes.Add(tn);
}
tvDevices.CollapseAll(); //Collapse all nodes
RestoreTreeViewState(tvDevices.Nodes, (List<string>)Session["tvState"] ?? new List<string>()); //Restore previously expanded nodes
}
private void SaveTreeViewState(TreeNodeCollection treeNodes, List<string> expandedList)
{
foreach (TreeNode treeNode in treeNodes)
{
if (treeNode.ChildNodes.Count > 0)
{
if (treeNode.Expanded.HasValue && treeNode.Expanded == true)
{
expandedList.Add(treeNode.Value);
}
SaveTreeViewState(treeNode.ChildNodes, expandedList);
}
}
}
private void RestoreTreeViewState(TreeNodeCollection treeNodes, List<string> expandedList)
{
foreach (TreeNode treeNode in treeNodes)
{
if(expandedList.Count == 0)
return;
if (expandedList.Contains(treeNode.Value))
{
if (treeNode.ChildNodes.Count > 0)
{
treeNode.Expand();
RestoreTreeViewState(treeNode.ChildNodes, expandedList);
}
}
}
}
protected void TvDevices_OnTreeNodeExpandedCollapsed(object sender, EventArgs e)
{
List<string> expandedList = new List<string>();
SaveTreeViewState(tvDevices.Nodes, expandedList);
Session["tvDevicesState"] = expandedList;
}
Since you added an event-handler for the treeview's Expanded/Collapsed event, it triggers a postback now. Before the tree view's event handler is called, the Page_Load event handler gets called. Within the Page_Load event handler, check for the Page.IsPostBack first before performing operations on the tree-view control, such as collapsing it by default.
Related
Im setting up a new Form and Im having some issuis with the TreeViewNodes checking and uncheking the childs. Its easier to see the problem in this short clip
Normally it works properly but sometimes it gets stuck (I think there is a conflict with selection but Im not sure) and the methods arent applied properly.
I have this methods to check and uncheck the childs:
private void Treeview_AfterCheck(object sender, TreeViewEventArgs e)
{
if (e.Action != TreeViewAction.Unknown)
if (e.Node.Checked)
{
CheckAll(e.Node.Nodes);
}
if (e.Node.Checked == false)
{
Uncheckall(e.Node.Nodes);
}
}
public void Uncheckall(TreeNodeCollection nodes)
{
foreach (TreeNode node in nodes)
{
node.Checked = false;
foreach (TreeNode node1 in node.Nodes)
{
node1.Checked = false;
foreach (TreeNode node2 in node1.Nodes)
{
node2.Checked = false;
}
}
}
}
public void CheckAll(TreeNodeCollection nodes)
{
foreach (TreeNode node in nodes)
{
node.Checked = true;
foreach (TreeNode node1 in node.Nodes)
{
node1.Checked = true;
foreach (TreeNode node2 in node1.Nodes)
{
node2.Checked = true;
}
}
}
}
And I have tried to make the selection null:
private void TreeView_Select(object sender, TreeViewEventArgs e)
{
TreeView.SelectedNode = null;
}
But the problem remains. Any ideas? Thanks
The answers of the question that I mentioned in my comment shows us different ways regarding how to iterate through TreeView nodes. To do that, you need a Recursive Function which is a function that calls itself.
Now, back to your code. You don't need to create two functions to check and uncheck the nodes, nor using foreach for each node, child node, and child node of a child node...etc. Please try the following:
private void treeView1_AfterCheck(object sender, TreeViewEventArgs e)
{
if (e.Action == TreeViewAction.Unknown) { return; }
foreach (TreeNode tn in GetNodes(e.Node))
tn.Checked = e.Node.Checked;
}
private static IEnumerable<TreeNode> GetNodes(TreeNode parentNode)
{
foreach (TreeNode tn in parentNode.Nodes)
{
yield return tn;
foreach (TreeNode child in GetNodes(tn))
{
yield return child;
}
}
}
This way, you can use this iterator to do other things to your nodes not only to check/uncheck them.
Edit
You can see this strange behaviour in the seconds 7, 10, 15.
I got your point now.
That behavior occurs when you mouse click too fast on a node so you are actually doing mouse click, mouse double click sequence. The tree view control by default does not toggle the check state of the nodes through the mouse double click unless you tell it to do so. How? Already answered by PhilP in this question.
Create a new class that inherits the tree view control and override the WndProc event as follow:
class TreeViewEx : TreeView
{
public TreeViewEx()
{ }
#region This extra to reduce the flickering
private const int TVM_SETEXTENDEDSTYLE = 0x1100 + 44;
private const int TVM_GETEXTENDEDSTYLE = 0x1100 + 45;
private const int TVS_EX_DOUBLEBUFFER = 0x4;
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
protected override void OnHandleCreated(EventArgs e)
{
SendMessage(Handle, TVM_SETEXTENDEDSTYLE, (IntPtr)TVS_EX_DOUBLEBUFFER, (IntPtr)TVS_EX_DOUBLEBUFFER);
base.OnHandleCreated(e);
}
#endregion
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x203 && CheckBoxes)
{
int x = m.LParam.ToInt32() & 0xffff;
int y = (m.LParam.ToInt32() >> 16) & 0xffff;
TreeViewHitTestInfo hitTestInfo = HitTest(x, y);
if (hitTestInfo.Node != null && hitTestInfo.Location == TreeViewHitTestLocations.StateImage)
{
OnBeforeCheck(new TreeViewCancelEventArgs(hitTestInfo.Node, false, TreeViewAction.ByMouse));
hitTestInfo.Node.Checked = !hitTestInfo.Node.Checked;
OnAfterCheck(new TreeViewEventArgs(hitTestInfo.Node, TreeViewAction.ByMouse));
m.Result = IntPtr.Zero;
return;
}
}
base.WndProc(ref m);
}
}
In the designer of the form which contains your TreeView, change the type of the TreeView to the extended one that we just created.
Use the same code to toggle the check state.
Rebuild your project.
That's it all.
Here is a quick demo. I'm mouse clicking and double clicking like crazy. However, it works as it should. Hopefully.
Related
◉ CheckBox not work properly when clicked on quickly multiple times
I have a problem with my C# project(WinApplication). I can't populate my selected node from treeview to listview. How can I do it these;
This is what i have done so far:
List<TreeNode>checked_tree_nodes=new List<TreeNode>();
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
if (e.Action != TreeViewAction.Unknown)
{
if (e.Node.Nodes.Count > 0)
{
unchecknodes(e.Node, e.Node.Checked);
}
}
if (e.Action != TreeViewAction.Unknown)
{
if (e.Node.Nodes.Count > 0)
{
checknodes(e.Node, e.Node.Checked);
}
}
foreach (TreeNode trl in checked_tree_nodes)
{
string text = "";
ListViewItem lv = listView1.FindItemWithText(text);
ListViewItem lvi = new ListViewItem(ypoxrewseis[treeView1.SelectedNode.Index].name);
lvi.SubItems.Add(task[treeView1.SelectedNode.Index].type); lvi.SubItems.Add(task[treeView1.SelectedNode.Index].date.ToString());
if (lv == null)
{
listView1.Items.Add(lvi);
}
else
{
MessageBox.Show("Exists!");
}
}
}
Thanks in advance!
I have a CheckedListBox with 10 items. On each item check a method is being called. I want to disable the checkbox of that particular item for which the method is being executed so that user cannot uncheck the item till the job is completed.
Note: Unchecking of an item calls another method.
Here is the code of ItemCheck Event:
private void host_listbox_ItemCheck(object sender, ItemCheckEventArgs e)
{
int index = e.Index;
try
{
string sitem = host_listbox.Items[index].ToString();
host_list[sitem].checked_event=e;
if (!host_list[sitem].is_busy)
{
host_config.listEnabled = false;
host_list[sitem].con_worker.RunWorkerAsync();
}
if (host_listbox.GetItemCheckState(index) == CheckState.Checked)
{
host_list[sitem].connected = false;
}
}
catch(Exception ex)
{
output_textbox.AppendText("connection failed!" +ex.ToString() +Environment.NewLine);
}
}
You can check/uncheck items in your checkedListBox with this code
checkedListBox.SetItemChecked(item, true);
for more informations go to microsoft documentation
private void host_listbox_ItemCheck(object sender, ItemCheckEventArgs e)
{
int index = e.Index;
try
{
string sitem = host_listbox.Items[index].ToString();
if (host_list[sitem].is_busy // or whatever indicates that background worker is running or any condition that specifies, that you do not want to let this item to be changed)
e.NewValue = e.CurrentValue; //Change the value back
else
{
//Let the checked state of the item change
This code prevent checked state change if associated background work is running:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
workerList = new List<BackgroundWorker>();
for (int i = 0; i < 10; i++)
{
var el = new BackgroundWorker();
el.DoWork += (s, e) =>
{
Thread.Sleep(5000);
};
workerList.Add(el);
checkedListBox1.Items.Add("el " + i);
}
}
private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
{
var worker = workerList[e.Index];
if (worker.IsBusy)
{
e.NewValue = e.CurrentValue;
return;
}
if (e.NewValue == CheckState.Checked)
worker.RunWorkerAsync();
}
public List<BackgroundWorker> workerList { get; set; }
}
Think, that only funcion solution is set selection mode
CheckedListBox.SelectionMode = SelectionMode.None;
private void ItemCheck(object sender, ItemCheckEventArgs e)
{
if (busy)
e.NewValue = e.CurrentValue;
}
I want to allow user to drag any item from MenuStrip to a ListBox.
I did it between to ListBoxes, but can not do it with MenuStrip.
Thanks a lot for your help.
I use WinForms, C#
For the destination ListBox I modified its property
this.listBox2.AllowDrop = true;
and created the following two events:
private void listBox2_DragOver(
object sender, System.Windows.Forms.DragEventArgs e)
{
e.Effect=DragDropEffects.All;
}
private void listBox2_DragDrop(
object sender, System.Windows.Forms.DragEventArgs e)
{
if(e.Data.GetDataPresent(DataFormats.StringFormat))
{
string str= (string)e.Data.GetData(
DataFormats.StringFormat);
listBox2.Items.Add(str);
}
}
What I need is what should be done to the source MenuStrip to allow drag items from it the ListBox, in over words how to make MenuStrip draggable.
Thanks to all for their help.
I found the solution:
The missing event is that I should add event to ToolStripMenuItem_MouseDown, I prefer to use right click instead of left click to avoid the conflict between ToolStripMenuItem_Click and the drag event, this the code:
AllowDrop = true;
private void tsmi_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Right)
DoDragDrop(sender, System.Windows.Forms.DragDropEffects.Copy);
}
Add also this code to the ListView:
private void lvAllowDropListView_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
{
System.Windows.Forms.ToolStripMenuItem button = e.Data.GetData(typeof(System.Windows.Forms.ToolStripMenuItem))
as System.Windows.Forms.ToolStripMenuItem;
if (button != null)
{
try
{
SmallImageList = sysIcons.SmallIconsImageList;
LargeImageList = sysIcons.LargeIconsImageList;
System.Windows.Forms.ToolStripMenuItem item = e.Data.GetData(typeof(System.Windows.Forms.ToolStripMenuItem))
as System.Windows.Forms.ToolStripMenuItem;
if (item != null)
{
AddToolStripMenuItem(item.Text, item.Name);
}
}
catch { }
}
}
private void AddToolStripMenuItem(string name, string tag)
{
System.Windows.Forms.ListViewItem item = new System.Windows.Forms.ListViewItem(name);
int Index = -1;
for (int i = 0; i < Items.Count;i++ )
if(Items[i].Tag.ToString() == tag)
{
Index = i;
break;
}
if (Index == -1)
{
item.Tag = tag;
Items.Add(item);
}
}
Drag Menu Strip Item is the same like ListBox item.
Check your code...
This code checks and unchecks the child nodes of a treeview control.
What algorithm is used in this code?
private int _callCountUp;
private int _callCountDn;
private void tvwPermissions_AfterCheck(object sender, System.Windows.Forms.TreeViewEventArgs e)
{
bool anyChecked = false;
if (_callCountDn == 0 && e.Node.Parent != null)
{
anyChecked = false;
foreach (TreeNode childNode in e.Node.Parent.Nodes)
{
if (childNode.Checked)
{
anyChecked = true;
break;
}
}
_callCountUp += 1;
if (anyChecked)
e.Node.Parent.Checked = true;
_callCountUp -= 1;
}
if (_callCountUp == 0)
{
foreach (TreeNode childNode in e.Node.Nodes)
{
_callCountDn += 1;
childNode.Checked = e.Node.Checked;
_callCountDn -= 1;
}
}
}
Not so sure this has a name. It is quite standard, the _callCountUp/Dn fields avoid trouble when changing the Checked property of a node causes the AfterCheck event handler to run again. StackOverflow is a very typical outcome when the event handler recurses without bound.
The generic pattern resembles this:
private bool modifyingNodes;
private void treeview_AfterCheck(object sender, TreeViewEventArgs e) {
if (modifyingNodes) return;
modifyingNodes = true;
try {
// etc..
}
finally {
modifyingNodes = false;
}
}
The finally block ensures that a handled exception (such as through ThreadExceptionDialog) doesn't permanently leave the state variable set to true. It's optional of course.