The following code is taken direct from Microsoft at http://msdn.microsoft.com/en-us/library/system.windows.forms.treeview.aftercheck%28VS.80%29.aspx.
// Updates all child tree nodes recursively.
private void CheckAllChildNodes(TreeNode treeNode, bool nodeChecked)
{
foreach (TreeNode node in treeNode.Nodes)
{
node.Checked = nodeChecked;
if (node.Nodes.Count > 0)
{
// If the current node has child nodes, call the CheckAllChildsNodes method recursively.
this.CheckAllChildNodes(node, nodeChecked);
}
}
}
// NOTE This code can be added to the BeforeCheck event handler instead of the AfterCheck event.
// After a tree node's Checked property is changed, all its child nodes are updated to the same value.
private void node_AfterCheck(object sender, TreeViewEventArgs e)
{
// The code only executes if the user caused the checked state to change.
if (e.Action != TreeViewAction.Unknown)
{
if (e.Node.Nodes.Count > 0)
{
/* Calls the CheckAllChildNodes method, passing in the current
Checked value of the TreeNode whose checked state changed. */
this.CheckAllChildNodes(e.Node, e.Node.Checked);
}
}
}
You put it in a form containing a treeview and call node_AfterCheck on (surprise, surprise), the treeview AfterCheck event. It then recursively checks or unchecks the child nodes on the treeview.
However if you actually try it, and click several times on the same treeview check box fast enough, the child nodes end up with their check out-of-sync with the parent. You probably need a couple of levels of children with perhaps 100 children in-total for the UI update to be slow enough to notice this happening.
I've tried a couple of things (such as disabling the treeview control at the beginning of node_AfterCheck and re-enabling at the end), but the out-of-sync problem still happens.
Any ideas?
The .NET TreeView class heavily customizes mouse handling for the native Windows control in order to synthesize the Before/After events. Unfortunately, they didn't get it quite right. When you start clicking fast, you'll generate double-click messages. The native control responds to a double-click by toggling the checked state for the item, without telling the .NET wrapper about it. You won't get a Before/AfterCheck event.
It's a bug but they won't fix it. The workaround is not difficult, you'll need to prevent the native control from seeing the double-click event. Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox, replacing the existing one.
using System;
using System.Windows.Forms;
class MyTreeView : TreeView {
protected override void WndProc(ref Message m) {
// Filter WM_LBUTTONDBLCLK
if (m.Msg != 0x203) base.WndProc(ref m);
}
}
Using the solution above, I think it is need to paint more detailed steps, how to apply it for those who want to apply it to an already created TreeView. For example, for me, a beginner, this caused difficulties, but here is the solution:
Creating a class "NoClickTree.cs" in your project.
Include this code in new class:
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);
}
}
Go to Form1.Designer.cs or "yourWindowWithTreeView".Designer.cs
Find original initialization at the end of the file, something like private System.Windows.Forms.TreeView treeView;
Replace them on private NoClickTree treeView;
In function private void InitializeComponent() find original initialization, something like this.treeView = new System.Windows.Forms.TreeView();
Replace them on this.treeView = new NoClickTree();
Done!
This steps helped me for solve this problem.
Related
I'm using a listbox to display a list of numeric values where the selected value get applied to an object and is saved. I had the idea to set the height on on the listbox to be just enough to display a single row. Of course this causes the vertical scollbar to appear, and without a scroll body, which is exactly what I was looking for.
When I click the up/down arrow on the list box scrollbar, it scrolls the next item into view correctly. However, the item is not selected. I immediately had problems with the actual selected value being saved and not the value that is in view. This is because it requires a click on the number after the scrollbar button to actually select the value, which is not very intuitive.
So, after some intel-sense hunting, I began search for possible ways to increment the selected value with clicks on the listbox scrollbar buttons, but I can't find anything that actually uses them.
I have found posts that describe actions when clicking on the scroll bar body, and still others that use designer added buttons to perform the index change on the listbox. However, the scroll bar body is not visible do to the short height of the list box and it seems silly to add more buttons when I already have those two available and doing most of the work.
I have also found a post that described something similar, but using a listview. But, I would hate to have to swap the control out at this point for one feature I really think should be available somewhere on the control I'm currently using.
So, I guess what I'm looking for is a way to address the click event handler of the vertical scrollbar buttons on a listbox control.
Any help would be greatly appreciated.
(Thanks all, for the 1000 other things I didn't have to post to solve here!)
I had heard about that Phil, and your right. I'm doing a replacement for the numeric up-down.
I just figured that there was probably a viable alternative, since that specific control was not originally part of the framework. I had also gotten much of it working and really like the results, and the way it picked up the theme.
Since the core of this application will become a start point for future applications, I wanted to include this functionality and was prepared to do a little work for it.
What I ended up doing was a little complicated that it was worth, but made easy with a useful helper function. I needed to search the 'Visual Tree' for my target type. From there I was able to access enough functionality to finish up.
First:
Using a helper function I found here (Thanks Bruno) I was able to add this to my Loaded event:
private Double currentVerticalOffset;
private void Page_Loaded_1(object sender, RoutedEventArgs e)
{
ScrollViewer sv = Helpers.ViewHelpers.ListBoxHelper.FindVisualChild<ScrollViewer>(listbox);
sv.ScrollChanged += HandleRankScrollChange;
currentVerticalOffset = sv.VerticalOffset;
}
Then, I handle the scroll changed event:
private void HandleRankScrollChange(object sender, ScrollChangedEventArgs e)
{
ScrollViewer sv = Helpers.ViewHelpers.ListBoxHelper.FindVisualChild<ScrollViewer>(listbox);
if (sv.VerticalOffset > currentVerticalOffset)
{
Helpers.ViewHelpers.ListBoxHelper.SelectNextItem(listbox);
}
if (sv.VerticalOffset < currentVerticalOffset)
{
Helpers.ViewHelpers.ListBoxHelper.SelectPreviousItem(listbox);
}
currentVerticalOffset = sv.VerticalOffset;
}
The helpers I call here are pretty simple, but again, this will become a foundation kit, so having the methods will probably come in handy again.
public static void SelectNextItem(ListBox lb)
{
if (lb.SelectedIndex < lb.Items.Count)
{
lb.SelectedIndex++;
}
}
public static void SelectPreviousItem(ListBox lb)
{
if (lb.SelectedIndex > 0)
{
lb.SelectedIndex--;
}
}
Bruno's helper function
public static childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
{
return (childItem)child;
}
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return null;
}
Thanks again.
Is there a way in WPF to define the background of alternating visible rows?
I've tried setting the AlternationCount property, but that restarts for every child node which gives a weird look.
Ideally what I would like is to know what the visual index of a given node is. Only expanded nodes are counted.
There was no easy way to do this as WPF creates nested containers for the tree nodes. So as Rachel mentioned, looping through the items seemed to be the way to go. But I didn't want to depart too much from the built in ItemsControl.AlternationIndex attached property as that is the one people would expect. Because it is readonly I had to access it via reflection, but after that things fell into place.
First off, make sure you handle the Loaded, Expanded and Collapsed events of your TreeViewItem. In the event handler find the owning TreeView and do a recursive alternation count set of all visible nodes. I created an extension method to handle it:
public static class AlternationExtensions
{
private static readonly MethodInfo SetAlternationIndexMethod;
static AlternationExtensions()
{
SetAlternationIndexMethod = typeof(ItemsControl).GetMethod(
"SetAlternationIndex", BindingFlags.Static | BindingFlags.NonPublic);
}
public static int SetAlternationIndexRecursively(this ItemsControl control, int firstAlternationIndex)
{
var alternationCount = control.AlternationCount;
if (alternationCount == 0)
{
return 0;
}
foreach (var item in control.Items)
{
var container = control.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (container != null)
{
var nextAlternation = firstAlternationIndex++ % alternationCount;
SetAlternationIndexMethod.Invoke(null, new object[] { container, nextAlternation });
if (container.IsExpanded)
{
firstAlternationIndex = SetAlternationIndexRecursively(container, firstAlternationIndex);
}
}
}
return firstAlternationIndex;
}
}
As you can see it runs through each node and sets the custom alternation index. It checks if the node is expanded and if so continues the count on the child nodes.
Above I mentioned that you have to handle the Loaded event for the TreeViewItem. If you only handle the expanded and collapsed events you won't get the new containers that are created when a node is first opened. So you have to do a new pass when the child node has been created and added to the visual tree.
Something I've done with javascript is create an OnLoaded event for a table which loops through the table rows and if the row is visible, it sets the background color to a nextColor variable, and changes the nextColor variable to the opposite color. That might work here.
I'm writing a WPF control that dynamically changes its contents depending on what types of Window/UserControl descendants are in its parentage list (part of an experiment with convention vs. configuration).
As such, I need some code to run after my control is fully parented (i.e. all the way up to the Window that's being shown). Ideally, I'd also like my code to run before the first Measure/Arrange pass, since my code is going to change the control's contents and force another Measure/Arrange pass.
I've looked at EndInit, but it fires after the control is loaded from XAML, at which time it might not be fully parented. (For example, if my control was on a UserControl, then EndInit will fire once the UserControl is loaded -- but before it's parented to anything else. I want to wait until the UserControl is parented to something, and that's parented to something else, all the way up.)
Currently I'm just hooking the Loaded event from my control's constructor, and running my code there (oddly enough, WPF doesn't have an OnLoaded method to override):
public class MyControl
{
public MyControl()
{
Loaded += (sender, e) => { ... };
}
}
This works -- it fires when the parents are fully populated -- but it's slightly less than optimal, because there's a Measure/Arrange pass that happens before Loaded.
Is there a good place I can put my code so that it runs after the Parents are set all the way up, but before the first Measure/Arrange pass?
Extra coolness points for solutions that would also work in Silverlight, ElementHost, the Blend/VS designer, and VisualBrush (i.e., not assuming that the top-level parent is a Window, or in the case of VisualBrush, not assuming that there even is a parent -- just that it's as parented as it's gonna be before showing up on the screen, or being sent to the printer, or whatever).
I believe the parents are all set in a single dispatcher operation, so you should be able to get that behavior by putting your logic in a delegate and queuing it up as the next dispatcher operation after the parent is set:
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
base.OnVisualParentChanged(oldParent);
this.Dispatcher.BeginInvoke(new Action(OnReady));
}
private void OnReady()
{
// Element should be fully parented here
}
You could also do that from EndInit rather than OnVisualParentChanged if you want to handle the case of no parent, although EndInit appears to be called more than once so you will need to check for duplicates:
private bool readyQueued;
public override void EndInit()
{
base.EndInit();
if (!readyQueued)
{
this.Dispatcher.BeginInvoke(new Action(OnReady));
readyQueued = true;
}
}
private void OnReady()
{
readyQueued = false;
// Element should be fully parented here
}
XAML
<TreeView Name="GroupView" ItemsSource="{Binding Documents}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="OnTreeNodeDoubleClick"/>
</Style>
</TreeView.ItemContainerStyle>
....
</TreeView>
Code-Behind
private void OnTreeNodeDoubleClick(object sender, MouseButtonEventArgs mouseEvtArgs)
{
Console.WriteLine("{3} MouseDoubleClick Clicks={0} ChangedButton={1} Source={2} Handled={4} ButtonState={5}",
mouseEvtArgs.ClickCount, mouseEvtArgs.ChangedButton, mouseEvtArgs.OriginalSource,
mouseEvtArgs.Timestamp, mouseEvtArgs.Handled, mouseEvtArgs.ButtonState);
}
I find that for one double click, the event handler is called multiple times. I'm trying to open up a document in tab on a double-click on the corresponding tree node; so I'd need to filter out the extra calls.
23479156 MouseDoubleClick Clicks=1 ChangedButton=Left Source=System.Windows.Controls.TextBlock Handled=False ButtonState=Pressed
23479156 MouseDoubleClick Clicks=1 ChangedButton=Left Source=System.Windows.Controls.TextBlock Handled=False ButtonState=Pressed
In my slightly-complicated app, it is being raised 4 times per double-click. On a simple repro-app, it is being raised 2 times per double click. Also all the event argument parameters are the same too, so I can't distinguish the last one of a set.
Any ideas why this is the way it is?
I know this is an old question, but as I came across it in my searches for the solution, here are my findings for any future visitors!
TreeViewItems are recursively contained within each other. TreeViewItem is a HeaderedContentControl (see msdn), with the child nodes as the Content. So, each TreeViewItem's bounds include all of its child items. This can be verified using the excellent WPF Inspector by selecting a TreeViewItem in the visual tree, which will highlights the bounds of the TreeViewItem.
In the OP's example, the MouseDoubleClick event is registered on each TreeViewItem using the style. Therefore, the event will be raised for the TreeViewItems that you double-clicked on - and each of its parent items - separately. This can be verified in your debugger by putting a breakpoint in your double-click event handler and putting a watch on the event args' Source property - you will notice that it changes each time the event handler is called. Incidentally, as can be expected, the OriginalSource of the event stays the same.
To counter this unexpected behaviour, checking whether the source TreeViewItem is selected, as suggested by Pablo in his answer, has worked the best for me.
When a TreeViewItem is double clicked, that item is selected as part of the control behavior. Depending on the particular scenario it could be possible to say:
...
TreeViewItem tviSender = sender as TreeViewItem;
if (tviSender.IsSelected)
DoAction();
...
I've done some debugging and it appears to be a bug in WPF. Most answers already given are correct, and the workaround is to check if the tree view item is selected.
#ristogod's answer is the closest to the root problem - it mentions that setting e.Handled = true the first time handler is invoked doesn't have the desired effect and the event continues to bubble up, calling parent TreeViewItems' handlers (where e.Handled is false again).
The bug seems to be in this code in WPF:
http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,5ed30e0aec6a58b2
It receives the MouseLeftButtonDown event (which is handled by the child control already), but it fails to check if e.Handled is already set to true. Then it proceeds to create a new MouseDoubleClick event args (with e.Handled == false) and invokes that always.
The question also remains why after having set it to handled the first time the event continues to bubble? Because in this line, when we register the handler Control.HandleDoubleClick:
http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,40
we pass true as the last argument to RegisterClassHandler:
http://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/EventManager.cs,161
which is handledEventsToo.
So the unfortunate behavior is a confluence of two factors:
Control.HandleDoubleClick is called always (for handled events too), and
Control.HandleDoubleClick fails to check if the event had already been handled
I will notify the WPF team but I'm not sure this bug is worth fixing because it might break existing apps (who rely on the current behavior of event handlers being called even if Handled was set to true by a previous handler).
private void TreeView_OnItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.Source is TreeViewItem
&& (e.Source as TreeViewItem).IsSelected)
{
// your code
e.Handled = true;
}
}
This is not actually a bubbling issue. I've seen this before. Even when you tell the event that you handled it, it continues to keep bubbling up. Except that I don't think that it's actually bubbling up, but rather firing the node above's own double click event. I could be totally wrong on that. But in either case, it's important to know that saying:
e.handled = true;
Does nothing to stop this from happening.
One way to prevent this behavior is to note that when you are double clicking, you are first single clicking and that the selected event should fire first. So while you can't stop the Double Click events from occurring, you should be able to check inside the handler to see whether the event logic should run. This example leverages that:
TreeViewItem selectedNode;
private void MouseDoubleClickEventHandler(object sender, MouseButtonEventArgs e)
{
if(selectedNode = e.Source)
{
//do event logic
}
}
private void TreeViewSelectedEventHandler(object sender, RoutedEventArgs e)
{
selectedNode = (TreeViewItem)e.Source;
}
Sometimes however you have situations where the nodes are being selected by other beans than through the TreeView SelectedItemChanged event. In that case you can do something like this. If you happen to have a TreeView with a single declared top node, you can give that node a specific name and then do something like this:
bool TreeViewItemDoubleClickhandled;
private void MouseDoubleClickEventHandler(object sender, MouseButtonEventArgs e)
{
if (!TreeViewItemDoubleClickhandled)
{
//do logic here
TreeViewItemDoubleClickhandled = true;
}
if (e.Source == tviLoadTreeTop)
{
TreeViewItemDoubleClickhandled = false;
}
e.Handled = true;
}
Regardless of the method you use, the important thing is to note that for whatever reason with TreeViewItem double clicking that you can't stop the events from firing up the tree. At least I haven't found a way.
I have a little bit more elegant solution than checking for selection or creating flags:
A helper method:
public static object GetParent(this DependencyObject obj, Type expectedType) {
var parent = VisualTreeHelper.GetParent(obj);
while (parent != null && parent.GetType() != expectedType)
parent = VisualTreeHelper.GetParent(parent);
return parent;
}
And then your handler:
public void HandleDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is DependencyObject)
if (sender == (e.OriginalSource as DependencyObject).GetParent(typeof(TreeViewItem)))
{
// sender is the node, which was directly doubleclicked
}
}
This is the wonderful world of event bubbling. The event is bubbling up the node hierarchy of your TreeView and your handler is called once for every node in the hierarchy path.
Just use something like
// ...
if (sender != this)
{
return;
}
// Your handler code goes here ...
args.Handled = true;
// ...
in your handler code.
There are some pretty major problems with this solution, but it could work in case someone needs to solve this problem in multiple places and I did find a scenario where the accepted solution doesn't work (double clicking on a toggle button that opens up a popup, where the toggle button is inside another element that handles double click.)
public class DoubleClickEventHandlingTool
{
private const string DoubleClickEventHandled = "DoubleClickEventHandled";
public static void HandleDoubleClickEvent()
{
Application.Current.Properties[DoubleClickEventHandled] = DateTime.Now.AddSeconds(1);
}
public static bool IsDoubleClickEventHandled()
{
var doubleClickWasHandled = Application.Current.Properties[DoubleClickEventHandled] as DateTime?;
return doubleClickWasHandled.HasValue && !IsDateTimeExpired(doubleClickWasHandled.Value);
}
private static bool IsDateTimeExpired(DateTime value)
{
return value < DateTime.Now;
}
public static void EnableDoubleClickHandling()
{
Application.Current.Properties[DoubleClickEventHandled] = null;
}
public static bool IsDoubleClickEventHandledAndEnableHandling()
{
var handled = IsDoubleClickEventHandled();
EnableDoubleClickHandling();
return handled;
}
}
Use DoubleClickEventHandlingTool.HandleDoubleClickEvent()
inside the inner/low level element eg:
private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{if (e.ClickCount == 2) DoubleClickEventHandlingTool.HandleDoubleClickEvent();}
High level double click event then only performs it's action when:
if (!DoubleClickEventHandlingTool.IsDoubleClickEventHandledAndEnableHandling())
The most likely reason is that the doubleclick handler is installed multiple times, so each instance of the handler is being called once for each click.
I am attempting to write a 'User Control' in WinForms .NET (not ASP.NET). The control is relatively simple. It will contain a label, a button, and a DataGridView.
However, the control needs to be able to instantiate itself, i.e. when the user clicks the button (of the parent control) at least 1 nested (children) control of the same type will be displayed underneath (kind of like a Tree)
I am having no success writing such a recursive control using a 'User Control'. A StackOverflow Exception occurs when instantiating MyControl within it's own constructor.
Therefore, I am leaning towards using a 'Custom Control', hoping it can handle the instantiation of itself (maybe in the Paint event??). Alot more work has to go into a Custom Control however, so I don't want to go down this path if it's going to take forever. I am on a tight deadline.
Anybody done this using a Custom Control or have any solid ideas on how to create a recursive control?
By the way, this control would be used in a fairly finite number of recursive combinations, so maybe it would be better to create a separate control for each parent/children scenario? I am thinking that would result in at least 10 separate user controls.
thanks for your help
UPDATE (here is my initial attempt at a stop condition per your feedback, but this is still causing children to be created indefinitely) :
public partial class CustomX : UserControl
{
private IList _children = new List();
public CustomX()
{
InitializeComponent();
Recurse(0);
}
private void Recurse(int childCount)
{
if (childCount
The problem is probably that the child control also instantiates a child control. There has to be a stop condition or controls will be generated until the stack overflows.
This should work:
public partial class CustomX : UserControl
{
private IList _children = new List();
public CustomX(int depth)
{
InitializeComponent();
if(depth > 0)
{
CustomX child = new CustomX(depth-1);
this.Controls.Add(child)
}
}
}
You should have no problem doing this with a user control. It is more likely an issue with not terminating the recursion properly. It (might) be more readable to perform the control creation in just the topmost parent control rather than delegating that task into each constructor.
Can you post the code you have in your constructor?