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
Related
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.
Having a standard WinForms 2.0 PropertyGrid control I'm looking for a way to either change the border color of the control or remove the border altogether.
I'm aware of the LineColor property which unfortunately only changes the inner borders between the cells.
Additionally, I used ILSpy to take a look at the source code of the PropertyGrid control and still found nothing meaningful to me.
My question is:
How to remove the outer border of a PropertyGrid control or change the color of the outer border?
Update 2012-05-04 - Solution (aka "hack"):
Based on Jamie's answer I assembled a working solution (which you can download from here):
The idea is to place the property grid inside a panel and let the panel clip the control.
With this approach, I did place the clipping panel into another panel that has a Padding of "1" (or whatever you want the borders to be) and gave this panel a BackColor that serves as the border color (green in my example).
Set the Anchor of the property grid to "Left, Right, Top, Bottom", set the Dock of the clipping panel to "Full".
This works well for my requirements. I would see this as kind of a hack since it consumes the resources of two panels which I hoped I could save.
this is another alternative, as it seems that my first answer is not suitable for this particular control. This is a dirty trick but should work:
Put a Panel control in your window or dialog, let say with size 100H x 300V. Put the propertygrid inside the panel with position -1,-1 and size 102,302.
Here is code from my project
PropertyGrid have two controls that need process.
+ doccomment is Document help.
+ gridView that display property value.
Those controls draw border rectangle with color ControlDark.
We need re draw rectangle with HelpBackColor and LineColor to make clear view.
namespace Bravo.Bravo7.UI
{
public class MyPropertyGrid : PropertyGrid
{
public class SnappableControl : NativeWindow
{
private Control _parent;
private MyPropertyGrid _ownerGrid;
public SnappableControl(Control parent, MyPropertyGrid ownerGrid)
{
_parent = parent;
_parent.HandleCreated += _parent_HandleCreated;
_parent.HandleDestroyed += _owner_HandleDestroyed;
_ownerGrid = ownerGrid;
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case (int)NativeMethods.WM_NCPAINT:
case (int)NativeMethods.WM_PAINT:
using (var g = _parent.CreateGraphics())
{
using (var pen = new Pen(_ownerGrid.HelpBackColor))
{
var clientRectangle = _parent.ClientRectangle;
clientRectangle.Width--;
clientRectangle.Height--;
g.DrawRectangle(pen, clientRectangle);
}
}
break;
}
}
void _owner_HandleDestroyed(object sender, EventArgs e)
{
ReleaseHandle();
}
void _parent_HandleCreated(object sender, EventArgs e)
{
AssignHandle(_parent.Handle);
}
}
public class PropertyGridView : NativeWindow
{
private Control _parent;
private MyPropertyGrid _ownerGrid;
public PropertyGridView(Control parent, MyPropertyGrid ownerGrid)
{
_parent = parent;
_parent.HandleCreated += _owner_HandleCreated;
_parent.HandleDestroyed += _owner_HandleDestroyed;
_ownerGrid = ownerGrid;
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case (int)NativeMethods.WM_NCPAINT:
case (int)NativeMethods.WM_PAINT:
using (var g = _parent.CreateGraphics())
{
using (var pen = new Pen(_ownerGrid.LineColor))
{
g.DrawRectangle(pen, 0, 0, _parent.Width - 1, _parent.Height - 1);
}
}
break;
}
}
void _owner_HandleDestroyed(object sender, EventArgs e)
{
ReleaseHandle();
}
void _owner_HandleCreated(object sender, EventArgs e)
{
AssignHandle(_parent.Handle);
}
}
public class MyToolStripRenderer : ToolStripSystemRenderer
{
protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e)
{
//base.OnRenderToolStripBorder(e);
}
}
public MyPropertyGrid()
{
base.LineColor = SystemColors.Control;
base.ViewBackColor = Color.FromArgb(246, 246, 246);
base.DrawFlatToolbar = true;
base.ToolStripRenderer = new MyToolStripRenderer();
var docDocument = typeof(PropertyGrid)
.GetField("doccomment", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(this) as Control;
new SnappableControl(docDocument, this);
var gridView = typeof(PropertyGrid)
.GetField("gridView", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(this) as Control;
new PropertyGridView(gridView, this);
}
}
}
you will need a bit of interop for that:
[DllImport("User32", CharSet=CharSet.Auto)]
private static extern int SetWindowLong(IntPtr hWnd, int Index, int Value);
[DllImport("User32", CharSet=CharSet.Auto)]
private static extern int GetWindowLong(IntPtr hWnd, int Index);
int GWL_STYLE = -16;
int WS_BORDER = 0x00800000;
IntPtr hWnd = yourPropertyGrid.Handle;
int style = GetWindowLong(hWnd, GWL_STYLE);
style = style & ~WS_BORDER;
SetWindowLong(hWnd, GWL_STYLE, style);
this code work.
private void SetHelpBoderColor(bool showBorder)
{
if (showBorder)
{
//Set Default ViewBackColor
PropertyInfo viewBackColor = this.propertyGrid.GetType().GetProperty("ViewBorderColor");
if (viewBackColor != null)
viewBackColor.SetValue(this.propertyGrid, SystemColors.ControlDark, null);
//Set Default HelpBorderColor
PropertyInfo helpBorderColor = this.propertyGrid.GetType().GetProperty("HelpBorderColor");
if (helpBorderColor != null)
helpBorderColor.SetValue(this.propertyGrid, SystemColors.ControlDark, null);
}
else
{
//Set ViewBackColor
PropertyInfo viewBackColor = this.propertyGrid.GetType().GetProperty("ViewBorderColor");
if (viewBackColor != null)
viewBackColor.SetValue(this.propertyGrid, SystemColors.Control, null);
//Set HelpBorderColor
PropertyInfo helpBorderColor = this.propertyGrid.GetType().GetProperty("HelpBorderColor");
if (helpBorderColor != null)
helpBorderColor.SetValue(this.propertyGrid, SystemColors.Control, null);
}
if (DesignMode)
{
Parent.Refresh();
}
}
One rapid and intuitive solution is to create a Parent Panel and a Child Panel as in the following image:
So I have a TreeView in a C# windows form app. What I need is for some nodes to be "locked" so that they cannot be checked (or unchecked), based on a parameter.
What I am doing now is this:
private void tv_local_BeforeCheck(object sender, TreeViewCancelEventArgs e) {
TNode node = (TNode)e.Node;
//if a part node, cancel the action.
if (node.Type == "Part") {
e.Cancel = true;
}
//if a locked node, cancel the action
if (node.Locked == true) {
e.Cancel = true;
}
}
This code works great on a single click of the checkbox, but if the user double clicks on a checkbox, it still checks/unchecks.
I have tried playing with the nodeMouseDoubleClick event, but that doesnt really help, since I cannot cancel the event...
Is there any ideas out there how to cancel a double click event on a node?... or anything else?
Thanks
This is a bug in the TreeView I think (http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/9d717ce0-ec6b-4758-a357-6bb55591f956/). You need to subclass the tree view and disable the double-click message in order to fix it. Like this:
public class NoClickTree : TreeView
{
protected override void WndProc(ref Message m)
{
// Suppress WM_LBUTTONDBLCLK
if (m.Msg == 0x203) { m.Result = IntPtr.Zero; }
else base.WndProc(ref m);
}
};
Of course if you do this you'll no longer be able to use the double-click metaphor in the tree-view for other things (such as double click a node to launch a property page, or something).
If you want your double click to actually toggle the check box then try:
protected override void WndProc(ref Message m)
{
// Filter WM_LBUTTONDBLCLK when we're showing check boxes
if (m.Msg == 0x203 && CheckBoxes)
{
// See if we're over the checkbox. If so then we'll handle the toggling of it ourselves.
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);
}
I managed it with the following code, which prevents checking root nodes:
private void MyTreeView_MouseUp(object sender, MouseEventArgs e)
{
// HACK: avoid to check root nodes (mr)
var node = ((TreeView)sender).GetNodeAt(new Point(e.X, e.Y));
if (node != null && node.Parent == null)
BeginInvoke(new MouseEventHandler(TreeView_MouseUpAsync), sender, e);
}
private void TreeView_MouseUpAsync(object sender, MouseEventArgs e)
{
if (IsDisposed)
return;
var node = ((TreeView)sender).GetNodeAt(new Point(e.X, e.Y));
node.Checked = false;
}
Try extending the TreeNode class and add a boolean property that maintains the proper checkedState. That way, when someone double-clicks a node, you can reset the node's checked state back to the value stored in the property. There might be a more elegant solution, but this is the best I can think of.
I have TreeView control on winform. I desire to make several nodes unselectable. How can I achive this.
There is only one idea in my mind - custom drawn nodes, but may be more easier way exists? Please advice me
I have already try such code in BeforeSelect event handler:
private void treeViewServers_BeforeSelect(object sender, TreeViewCancelEventArgs e)
{
if (e.Node.Parent != null)
{
e.Cancel = true;
}
}
But effect it gained is not appropriate. Node temporary get selection when I am holding left mouse button on it.
Thanks in advance!
You could completely disable mouse events in case you click on a not-selectable node.
To do this, you have to override TreeView a shown in the following code
public class MyTreeView : TreeView
{
int WM_LBUTTONDOWN = 0x0201; //513
int WM_LBUTTONUP = 0x0202; //514
int WM_LBUTTONDBLCLK = 0x0203; //515
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_LBUTTONDOWN ||
m.Msg == WM_LBUTTONUP ||
m.Msg == WM_LBUTTONDBLCLK)
{
//Get cursor position(in client coordinates)
Int16 x = (Int16)m.LParam;
Int16 y = (Int16)((int)m.LParam >> 16);
// get infos about the location that will be clicked
var info = this.HitTest(x, y);
// if the location is a node
if (info.Node != null)
{
// if is not a root disable any click event
if(info.Node.Parent != null)
return;//Dont dispatch message
}
}
//Dispatch as usual
base.WndProc(ref m);
}
}
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.