I want to modify the content in a TabItem of a TabView. And that TabItem uses DataTemplate.
When I am trying to access the children of that item like the following:
var container = tabview.ContainerFromIndex(tabview.SelectedIndex);
int count = VisualTreeHelper.GetChildrenCount(container);
I got the ArgumentException: Wrong Parameter Reference on the second line. How should I use VisualTreeHelper to modify it?
Here is a easy method:
public static T GetChildObject<T>(DependencyObject obj, string name) where T : FrameworkElement
{
DependencyObject child = null;
T grandChild = null;
for (int i = 0; i <= VisualTreeHelper.GetChildrenCount(obj) - 1; i++)
{
child = VisualTreeHelper.GetChild(obj, i);
if (child is T && (((T)child).Name == name | string.IsNullOrEmpty(name)))
{
return (T)child;
}
else
{
grandChild = GetChildObject<T>(child, name);
}
if (grandChild != null)
{
return grandChild;
}
}
return null;
}
From your description, you can already get the container of the target element. Let's assume that the element you need is named TargetEle and the type is TextBlock. You can write it like this:
var target = GetChildObject<TextBlock>(container,"TargetEle");
Update
I tested your code and found that you didn't capture the events loaded by the page.
In fact, the SelectionChanged event is fired when the TabView is just created, but the visual tree is not loaded yet, and you can't get the content from it through the code. You can create an IsLoaded property in the page, set it to True when Page Loaded, and determine this property in the SelectionChanged time.
Only when it is True, proceed to the next step.
Related
I have created a WPF application . I need to identify Next focusable element . For that I have added following code.
UIElement elementWithFocus = System.Windows.Input.Keyboard.FocusedElement as UIElement;
var a = elementWithFocus.PredictFocus(FocusNavigationDirection.Next);
But it is showing that Next is not supported. How I can achieve the same?
Try to list all the controls on your window and search for the one which has it's TabIndex property equals to the current TabIndex property + 1
private UIElement SearchNextControl(int currentTabIndex)
{
foreach (UIElement element in AllTheControls)
{
if(element.TabIndex == (currentTabIndex + 1))
{
return element;
}
}
return null;
}
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?
Solved:
I have solved it by subscribing to row.Loaded - once this method gets called, I can traverse the visual tree and find the DataGridCellsPresenter which I need to manipulate.
Which of course makes sense, should invest into understanding WPF more :(
Original question:
I need to manipulate the DataGridCellsPresenter when a row has been added into a Datagrid. I tried hooking into the LoadingRow event and access it via e.Row, however the row has not been inserted into the datagrid when the event occurs (so there's no DataGridCellsPresenter in e.Rows visual tree and e.Row isn't in the DataGrids rows yet).
As far as I'm aware, there doesn't seem to be a LoadedRow event. Is there any way I can access the newly added row when it has been loaded?
PS. I tried updating the layout on both the datagrid and e.Row, to no avail.
You could retrieve the row from it's index:
//found this on SO, I don't remember who, credit to original coder
public static DataGridRow GetRow(this DataGrid grid, int index)
{
DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
// May be virtualized, bring into view and try again.
grid.UpdateLayout();
grid.ScrollIntoView(grid.Items[index]);
row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
}
return row;
}
Or by it's data item:
var row= DataGrid.ItemContainerGenerator.ContainerFromItem(youritem);
EDIT
This method might help you too:
public static T FindChild<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
I have the following code that is supposed to find an element inside of something. The problem is that I have a DataGrid, whose first column has a CheckBox in it's header. The checkbox itself is defined in a Style, which exists in the <controls:ChildWindow.Resources> dictionary of the parent child window.
At runtime, if all elements of the binding collection of the grid are "selected", than I need to select the damn checkbox. However, since the checkbox exists in the style it is not easily accesible and thus I need to walk through the entire DOM to find the specific checkbox.
This is the code that makes the DOM Traversal. It is implemented as an extension method to FrameworkElement so I can call the FindElement or GetChildren methods from any control that inherits from FrameworkElement:
public static class FrameworkElementExtensions
{
public static FrameworkElement FindElement(this FrameworkElement parentFrameworkElement, string childFrameworkElementNameToSearch)
{
FrameworkElement childFrameworkElementFound = null;
parentFrameworkElement.SearchElements(ref childFrameworkElementFound, childFrameworkElementNameToSearch);
return childFrameworkElementFound;
}
public static List<FrameworkElement> GetChildren(this FrameworkElement parentElement)
{
List<FrameworkElement> childFrameworkElementsFound = new List<FrameworkElement>();
parentElement.GetChildren(childFrameworkElementsFound);
return childFrameworkElementsFound;
}
public static void SearchElements(this FrameworkElement parentFrameworkElement, ref FrameworkElement childFrameworkElementToFind, string childFrameworkElementName)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(parentFrameworkElement);
if (childrenCount > 0)
{
FrameworkElement childFrameworkElement = null;
for (int i = 0; i < childrenCount; i++)
{
childFrameworkElement = (FrameworkElement)VisualTreeHelper.GetChild(parentFrameworkElement, i);
if (childFrameworkElement != null && childFrameworkElement.Name.Equals(childFrameworkElementName))
{
childFrameworkElementToFind = childFrameworkElement;
return;
}
childFrameworkElement.SearchElements(ref childFrameworkElementToFind, childFrameworkElementName);
}
}
}
public static void GetChildren(this FrameworkElement parentFrameworkElement, List<FrameworkElement> allChildFrameworkElement)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(parentFrameworkElement);
if (childrenCount > 0)
{
for (int i = 0; i < childrenCount; i++)
{
FrameworkElement childFrameworkElement = (FrameworkElement)VisualTreeHelper.GetChild(parentFrameworkElement, i);
allChildFrameworkElement.Add(childFrameworkElement);
childFrameworkElement.GetChildren(allChildFrameworkElement);
}
}
}
}
So the issue at hand is that when i call something along the lines of SomeDataGrid.FindElement("HeaderCheckBox"); it always returns a null. The assumption here is that I have a DataGrid called SomeDataGrid and a CheckBox defined within a style called HeaderCheckBox.
Upon further debugging I also found out that no matter what control I call these extension methods from, the VisualTreeHelper.GetChildrenCount method call used in the last two methods in my code always returns 0 ??? WTF?
Anyone has any idea on how to fix this?
Thanks,
Martin
Sure, no problem. Here it is.
It was made with VS2010 and Silverlight 4... just in case.
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.