I'm exploring logical and visual trees from the same application without success going deeper through the levels.
My code uses a generic explorer:
private static void ProcessGenericTree(object current, List<FrameworkElement> leaves, Type treeType)
{
if (current is FrameworkElement)
{
if (!leaves.Contains(current as FrameworkElement))
leaves.Add(current as FrameworkElement);
}
DependencyObject dependencyObject = current as DependencyObject;
if (dependencyObject != null)
{
if (treeType.Equals(typeof(VisualTreeHelper)))
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
{
ProcessVisualTree(VisualTreeHelper.GetChild(dependencyObject, i), leaves);
}
}
else
{
foreach (object child in LogicalTreeHelper.GetChildren(dependencyObject))
{
ProcessLogicalTree(child, leaves);
}
}
}
}
ProcessLogicalTree and ProcessVisualTree simply iterate (doing something before the ProcessGenericTree re-call).
The result looks complete, but when I'm trying to retrieve a TextBlock into a GridViewColumn Header it looks like the item doesn't exist neither in the Logical nor in the Visual leaves list of FrameworkElement.
It seems to be a Visual Element into a Logical Element. In fact adding a watch this TextBlock appears in the Visual Children of my GridView (retrieved as logical, it stands in a Tab Item not selected), but my code isn't unable to get it.
My call is pretty simple:
ProcessVisualTree(root, _visualElements);
ProcessLogicalTree(root, _logicalElements);
where root is the MainWindow.
So, how can I explore my tree at its deepest level? Maybe re-iterating through the retrieved FrameworkElement list? I think my ProcessGeneric code already does it.
Update: the WPF Visualizer shows a structure of this kind:
ListView > ScrollViewer > Grid > DockPanel > Grid > ScrollContentPresenter > GridViewHeaderRowPresenter > GridViewColumnHeader > HeaderBorder
The GridViewColumnHeader level contains my TextBlock but the visual tree doesn't.
Update 2: using the recursion starting from the main window with my element visible I'm not able to Find the object with a specified name with this code:
public static T FindVisualChild<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
return (T)child;
}
T childItem = FindVisualChild<T>(child);
if (childItem != null) return childItem;
}
}
return null;
}
I'm pretty sure the VisualTreeHelper is not able to retrieve elements inside Header property but the WPF Inspector works correctly.
I wonder if it uses a different approach to traverse the tree (maybe inspecting the Properties like Header too). Suggestions?
I am new with WPF, so I'm not sure if the title of the question is correct or makes any sense, please edit if it can get more relevant. I am using Kinect.Toolbox MouseControl in my application. For using the magnetic controls I have a problem. I know that I can define them in XAML by adding:
<Page ...
xmlns:local ="clr-namespace:Kinect.Toolbox;assembly=Kinect.Toolbox">
...
<Button local:MagneticPropertyHolder.IsMagnetic="True" ... />
....
But I need to do it in the code. Is there anyway to set the magnetic controls in the code? I can get all the controlls in the page like this:
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
foreach (Button tb in FindVisualChildren<Button>(this))
{
//Set the buttons to be magnetic
}
However I cannot understand how to set them progmatically.
This looks like an attached property.
To set it, you'd do something like
tb.SetValue(MagneticPropertyHolder.IsMagneticProperty, true);
or possibly
MagneticPropertyHolder.SetIsMagnetic(tb, true);
A quick glance at the Kinect Toolbox source code suggests that either would work. The second is more type safe.
See How to I access an attached property in code behind? for more information.
I have a ListView which might contains a lot of items, so it is virtualized and recycling items. It does not use sort. I need to refresh some value display, but when there are too many items, it is too slow to update everything, so I would like to refresh only the visible items.
How could I get a list of all currently displayed items ? I tried to look into the ListView or in the ScrollViewer, but I still have no idea how to achieve this. The solution must NOT go through all items to test if they can be seen, because this would be too slow.
I'm not sure code or xaml would be useful, it is just a Virtualized/Recycling ListView with its ItemSource bound to an Array.
Edit :
Answer :
thanks to akjoshi, I found the way :
get the ScrollViewer of the ListView
(with a FindDescendant method, that you can do yourself with the VisualTreeHelper ).
read its ScrollViewer.VerticalOffset : it is the number of the first item shown
read its ScrollViewer.ViewportHeight : it is the count of items shown.
Rq : CanContentScroll must be true.
Have a look at this question on MSDN showing a technique to find out the visible ListView items -
How to find the rows (ListViewItem(s)) in a ListView that are actually visible?
Here's the relevant code from that post -
listView.ItemsSource = from i in Enumerable.Range(0, 100) select "Item" + i.ToString();
listView.Loaded += (sender, e) =>
{
ScrollViewer scrollViewer = listView.GetVisualChild<ScrollViewer>(); //Extension method
if (scrollViewer != null)
{
ScrollBar scrollBar = scrollViewer.Template.FindName("PART_VerticalScrollBar", scrollViewer) as ScrollBar;
if (scrollBar != null)
{
scrollBar.ValueChanged += delegate
{
//VerticalOffset and ViweportHeight is actually what you want if UI virtualization is turned on.
Console.WriteLine("Visible Item Start Index:{0}", scrollViewer.VerticalOffset);
Console.WriteLine("Visible Item Count:{0}", scrollViewer.ViewportHeight);
};
}
}
};
Another thing you should do is to use ObservableCollection as your ItemSource instead of an Array; that will definitely improve the performance.
Update:
Ya that might be true(array vs. ObservableCollection) but I would like to see some statistics related to this;
The real benefit of ObservableCollection is if you have a requirement to add/remove items from your ListView at run-time, in case of an Array you will have to reassign the ItemSource of ListView and the ListView first throws away its previous items and regenerates its entire list.
After trying to figure out something similar, I thought I would share my result here (as it seems easier than the other responses):
Simple visibility test I got from here.
private static bool IsUserVisible(FrameworkElement element, FrameworkElement container)
{
if (!element.IsVisible)
return false;
Rect bounds =
element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
var rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight);
}
Afterwards you can loop through the listboxitems and use that test to determine which are visible. Since the listboxitems are always ordered the same the first visible one in this list would be the first visible one to the user.
private List<object> GetVisibleItemsFromListbox(ListBox listBox, FrameworkElement parentToTestVisibility)
{
var items = new List<object>();
foreach (var item in PhotosListBox.Items)
{
if (IsUserVisible((ListBoxItem)listBox.ItemContainerGenerator.ContainerFromItem(item), parentToTestVisibility))
{
items.Add(item);
}
else if (items.Any())
{
break;
}
}
return items;
}
How I see things :
on one side, you have your data. They must be up to date, because this is where your information is in memory. Iterating on your data list should be pretty fast, and most of all, can be done on another thread, in background
on the other side, you have the display. Your ListView already make the trick of refreshing only the datas displayed, since it's virtualizing ! You need no more tricks, it's already in place !
On last work, using a binding on an ObservableCollection is a good advice. If you intend to modify the ObservableCollection from an another thread, I would recommend this : http://blog.quantumbitdesigns.com/2008/07/22/wpf-cross-thread-collection-binding-part-1/
I spend a lot of time finding a better solution for this,
In my situation i have a scrollviewer, filled with items with custom heigths that can be set visible/invisible, i came up with this. It does the same as above solutions but with a fraction of the CPU. I hope it helps some one.
The first items of the listview / scrollpanel is TopVisibleItem
public int TopVisibleItem { get; private set; }
private double CurrentDistance;
private void TouchScroller_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (myItemControl.Items.Count > 0)
{
MoveDirection direction = (MoveDirection)Math.Sign(e.VerticalChange);
if (direction == MoveDirection.Positive)
while (CurrentDistance < e.VerticalOffset && TopVisibleItem < myItemControl.Items.Count)
{
CurrentDistance += ((FrameworkElement)myItemControl.Items[TopVisibleItem]).ActualHeight;
TopVisibleItem += 1;
}
else
while (CurrentDistance >= e.VerticalOffset && TopVisibleItem > 0)
{
CurrentDistance -= ((FrameworkElement)myItemControl.Items[TopVisibleItem]).ActualHeight;
TopVisibleItem -= 1;
}
}
}
public enum MoveDirection
{
Negative = -1,
Positive = 1,
}
If you have a virtualization enabled ListView, Then you can get all Current Visible items as below:
Get VirtualizingStackPanel
Get all ListViewItems in VirtualizingStackPanel
The code is shown below.
VirtualizingStackPanel virtualizingStackPanel = FindVisualChild<VirtualizingStackPanel>(requiredListView);
List<ListViewItem> items = GetVisualChildren<ListViewItem>(virtualizingStackPanel);
The Functions are shown below.
private 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;
}
private List<childItem> GetVisualChildren<childItem>(DependencyObject obj) where childItem : DependencyObject
{
List<childItem> childList = new List<childItem>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
childList.Add(child as childItem);
}
if (childList.Count > 0)
return childList;
return null;
}
This will return you list of current ListViewItem loaded for displaying.
Hope it helps :).
I have a difficult task to solve. When using WPF Datagrid the Paging works fine as long as you don't have any groupings.
However as soon as groupings come into the place, it becomes a bit tricky. In fact The pageup/down don't work correctly anymore.
One workaround I have found is to access the internal scroll viewer that is wrapper inside the dataGrid by using this:
helper class:
public static class VisualTreeUtilities
{
public static T GetVisualChild<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
}
Actual code:
ctor:
_scrollViewer2 = VisualTreeUtilities.GetVisualChild<ScrollViewer>(this.dataGrid);
private void DataGrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.PageUp:
_scrollViewer2.PageUp();
e.Handled = true;
break;
}
}
This way I can perfectly scroll fine despite using the groupings. The only problem that remains is that Datagrid doesn't take the actual selectedItem with it downwards when paging down, or upwards when paging up. The selected cell remains at the former position and out of sight when paging away. I wished it was like excel when the selected cell remains there. It means when the second cell of third row is selected and you page down, still the second cell of the third row would be selected. How could I do something like this in a WPF datagrid?
I need to understand how internally it works. How does the ListCollectionView know how many rows to page down? if I knew how it counts down the rows, I could use the same principle to count down the rows and set the selectedItem to that position and do a cell.focus() and problem solved.
Can anybody help me with understanding the missing bit?
Many Thanks,
I have a list of files in a ListView in WPF. Users can drag files onto the list view, and right now they are just appended to the end of the list. Is it possible to insert the file into the ListView right where the user dropped it?
WPF isn't really designed to be used that way. While you can brute force add ListViewItem's directly to the ListView, the way it's really supposed to work is that you have a collection of some kind (ObservableCollection<FileInfo> would work well) and bind the ListView's ItemsSource property to that collection.
Then the answer is simple. Instead of the Add method, you use the Insert method of the collection which takes an index.
As for finding which ListViewItem the mouse event occurred over, you could use the VisualTreeHelper.HitTest method.
From my point of view it is little tricky when I used the templated item. I have fight with it little bit. I am sharing my usecase which works with DraggableListBox. But I suppose the same solution works with ListBox control.
As the first I created the dependency object extension which is able to provide me ListItem element:
public static class WpfDomHelper
{
public static T FindParent<T>(this DependencyObject child) where T : DependencyObject
{
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
if (parentObject == null) return null;
T parent = parentObject as T;
if (parent != null)
return parent;
else
return FindParent<T>(parentObject);
}
}
Then I implemented Drop logic which inserts(adds) item according specific Drop Y position of destination ListBoxItems:
private void Grid_Drop(object sender, DragEventArgs e)
{
int dropIndex = -1; // default position directong to add() call
// checking drop destination position
Point pt = e.GetPosition((UIElement)sender);
HitTestResult result = VisualTreeHelper.HitTest(this, pt);
if (result != null && result.VisualHit != null)
{
// checking the object behin the drop position (Item type depend)
var theOne = result.VisualHit.FindParent<Microsoft.TeamFoundation.Controls.WPF.DraggableListBoxItem>();
// identifiing the position according bound view model (context of item)
if (theOne != null)
{
//identifing the position of drop within the item
var itemCenterPosY = theOne.ActualHeight / 2;
var dropPosInItemPos = e.GetPosition(theOne);
// geting the index
var itemIndex = tasksListBox.Items.IndexOf(theOne.Content);
// decission if insert before or below
if (dropPosInItemPos.Y > itemCenterPosY)
{ // when drag is gropped in second half the item is inserted bellow
itemIndex = itemIndex + 1;
}
dropIndex = itemIndex;
}
}
.... here create the item .....
if (dropIndex < 0)
ViewModel.Items.Add(item);
else
ViewModel.Items.Insert(dropIndex, item);
e.Handled = true;
}
So this solution works with my template DraggableListBoxView, I suppose the same solution must work with standard ListBoxView. Good Luck
You can do this. It takes a bit of work, but it can be done. There are a couple demos out there, here is one on CodeProject. This particular one is by the wpf master known as Josh Smith. It's probably not exactly what you are looking for, but it should be pretty darn close.