LogicalTree Control directly under mouse no VisualTree - wpf

This is a WPF application.
I'm trying to get the control directly under the mouse and it's proving to be a surprising pain.
Mouse.DirectlyOver, InputHitTest and VisualTreeHelper.HitTest all reference the VISUAL tree. I'm trying to grab the control itself.
Example: If I have a TextBox and use any of the above, it will return a TextBoxView whereas I want the TextBox itself.
This is happening inside of a PreviewLeftButtonDown event. Sender is not an option as Sender is always a ListViewItem for me. If I check e.OriginalSource it is still the VisualTree element and not the actual control.
Happy to explain further if need be.
Thanks

You just need to walk up the visual tree until you find the type you want. Here's a link to some code.
Here's the code from that link
// walk up the visual tree to find object of type T, starting from initial object
public static T FindUpVisualTree<T>(DependencyObject initial) where T : DependencyObject
{
DependencyObject current = initial;
while (current != null && current.GetType() != typeof(T))
{
current = VisualTreeHelper.GetParent(current);
}
return current as T;
}

I agree with mdm20 that the only way to get to the Textbox is by traversing up to the parent. In fact, here is a link to the same question asked a couple of years ago, with the same answer. If you want to limit unnecessary tree traversal, then you could stop searching once you hit the ListViewItem, as anything above that point is not what you are looking for anyway.
However, add the last link and this one together, and it actually seems to me that you already have your answer. If a TextBoxView is returned, then you know that a textbox was hit. You could even cache the incoming TextBox that goes through the HitTestFilterCallBack potentially, but that is more of a theory and possibly bug prone. However, going down that path, you could just test if the TextBox coming through the filter is the parent of the TextBoxView

Walk up the visual tree until you find a UIElement:
public UIElement FindUIElement(DependencyObject visualTreeNode)
{
UIElement elem;
while ((elem = (visualTreeNode as UIElement)) != null)
visualTreeNode = VisualTreeHelper.GetParent(visualTreeNode);
return elem;
}

Related

WPF ListBox - Detect Scrollbar Up/Down Button click

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.

IsKeyboardFocusWithin in silverlight

How to get the keyboard focus is anywhere within the element or its visual tree child elements in Silverlght?
There are two possible solutions depending on your scenario (we usually prefer more detail in a question).
First you can use the FocusManager.GetFocusedElement() static method to get the element that currently has the focus. You could then use the VisualTreeHelper to determine if the element is with the your element. I would normally use an extension class to make using VisualTreeHelper easier. Mine is found here. With that class present. Then:-
public static bool IsFocusIn(DependencyObject element)
{
DependendyObject focusedElement = FocusManager.GetFocusedElement() as DependencyObject;
if (focusedElement != null)
{
return focusedElement.Ancestors().Any(e => e == element);
}
return false;
}
The second approach is to add event handlers to the GotFocus and LostFocus events of your element. You can then track whenever the focus enters or leaves any control within your element.

Code Analysis CA2000: Is this code okay?

Have a look at this code, I believe it solved CA2000 but I want to make sure I'm not overlooking something. Basically this code loads a new Control based on what is selected in a TreeView. That Control is then displayed and is visible/usable until another Node in the TreeView is selected.
private void Something(object sender, TreeViewEventArgs e)
{
ProjectTreeNode node = (e.Node as ProjectTreeNode);
foreach (Control c in optionsPlaceholderPanel.Controls)
c.Dispose();
optionsPlaceholderPanel.Controls.Clear();
if (node != null)
{
//ProjectOptions inherits from Control and is therefore IDisposable
ProjectOptions options = new ProjectOptions(node.Project);
ShowOptionsPanel(options);
}
}
private void ShowOptionsPanel(Control control)
{
optionsPlaceholderPanel.Controls.Add(control);
control.Dock = DockStyle.Fill;
}
So basically the Control is in scope always, until a new Control is loaded in place of it. When I do that, I'm disposing the prior-loaded Control so I think it's safe to ignore CA2000 in this case. Also, when the Form finally closes and optionsPlaceholderPanel is disposed, this will also dispose the child controls, right?
foreach (Control c in optionsPlaceholderPanel.Controls)
c.Dispose();
No, this code has a bug. Which in itself is triggered by a bug in the ControlCollection class. Your foreach loop is modifying the panel's Controls collection. This normally produces an InvalidOperationException, "Collection was modified, enumeration operation may not execute", but the class forgets to do this.
The Dispose() call on the control removes it from the collection. In effect, you'll only dispose every other control. This should have a side-effect, they remain visible on the panel. Ymmv. Fix:
for (int ix = optionsPlaceholderPanel.Controls.Count - 1; ix >= 0; --ix)
optionsPlaceholderPanel.Controls[ix].Dispose();
Or less efficient, although you'd never see the difference:
while (optionsPlaceholderPanel.Controls.Count > 0)
optionsPlaceholderPanel.Controls[0].Dispose();
Otherwise the code is okay, CA2000 tends to produce false warnings.

Define Background Color of Alternating TreeView Rows Based on Visibility

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.

Winforms treeview, recursively check child nodes problem

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.

Resources