In WPF, I have a structure like this
<Button>
<Grid>
<!--definitions for 1 row and 2 columns-->
<TextBlock x:Name="t1" Grid.Column="0"/>
<TextBlock x:Name="t2" Grid.Column="1"/>
</Grid>
</Button>
Supposed a Button b with this structure is generated dynamically. How to access t1 from Button b?
Edit for clarification: Since t1 resides within Button b, is it possible to change the content of t1 if one only have access to b? something along the line of b.childGridElement.childTextBlock_t1.Text = "newString"?
This should work for the use case you've provided:
((TextBlock)b.FindName("t1")).Text = "newString";
You need to use Visual Tree Helper for this.
Define Handler Extension Method
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject parent) where T : DependencyObject
{
if (parent != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the available child is not null and is of required Type<T> then return with this child else continue this loop
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
now in your xaml
IEnumerable<TextBlock> textblockes=FindVisualChildren<TextBlock>(b);
foreach (var textblock in textblockes)
{
if (textblock!= null && textblock.Name="t1")
{
//write code for t1 here;
}
if (textblock!= null && textblock.Name="t2")
{
//write code for t2 here;
}
}
In above method whatever is your tree structure , it will find all the textblock of your Button b and then on the basis of Name property you can do appropriate operations.
Related
For example, I have a ItemsControl with a custom DataTemplate:
<ItemsControl Name="CategoriesList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
For example my collection contains 100 elements. I want to get which CheckBoxes is checked and which is not or I just want to change a Content property. In both cases I need to get a CheckBox element from code. So it's easy to get Items from code:
var cList = CategoriesList.Items;
foreach (var item in cList)
{
//Do Something
}
But I need to get CheckBoxes from these items. Is it possible?
Thank you!
You need to use a visual tree helper for the same
I have tried it with a listbox and it works! Ant I think the same would work for an ItemsControl as well because the properties and methods of a listbox and an ItemsControl are same.
Just make and use this Method to dig inside the visual tree of a listbox
public static T FindFirstElementInVisualTree<T>(DependencyObject parentElement) where T : DependencyObject
{
try
{
int childCount = VisualTreeHelper.GetChildrenCount(parentElement);
if (childCount == 0)
return null;
for (int i = 0; i < childCount; i++)
{
var child = VisualTreeHelper.GetChild(parentElement, i);
if (child != null && child is T)
{
return (T)child;
}
else
{
var result = FindFirstElementInVisualTree<T>(child);
if (result != null)
return result;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return null;
}
And that's how you are going to use this method in code
ListBoxItem SelectedListBoxItem = this.lstInstagramTags.ItemContainerGenerator.ContainerFromIndex(int index) as ListBoxItem;
if (SelectedListBoxItem == null)
return;
// Iterate whole listbox tree and search for this items
Button btn= FindFirstElementInVisualTree<Button>(SelectedListBoxItem );
btn.Content="Hello";
And a Link too
Hope this helps.
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.
Within my Windows.Resources I have the following column defined:
<DataGridTextColumn x:Key="CustomColumn" x:Shared="False">
<DataGridTextColumn.Header>
<StackPanel>
<Label Padding="0" Name="labelA"/>
<Separator HorizontalAlignment="Stretch"/>
<Label Padding="0" Name="labelB"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
I have an event that gets fired from my ViewModel and adds the following "CustomColumn" to my DataGrid:
var column = FindResource("CustomColumn") as DataGridTextColumn;
var label = FindName("labelA") as Label;
label.Content = string.Format("A {0}", i);
DataGrid1.Columns.Add(column);
The question is, how would I change the content of the two labels inside the CustomColumn header? I above code fails because it is unable to find "labelA". (adding the column works, but I also need to set these labels). My guess is, I need to find it through the VisualTree -- but I want to make sure I'm not doing anything else wrong.
Thanks for the help.
I created some Visual Tree helpers that I use all the time to find objects in the Visual Tree
For example, you can find a Label named "LabelA" with this:
VisualTreeHelpers.FindChild<Label>(column, "LabelA");
Here's the FindChild method in case the above link doesn't work
using System.Windows;
using System.Windows.Media;
namespace MyNamespace
{
public class VisualTreeHelpers
{
/// <summary>
/// Looks for a child control within a parent by name
/// </summary>
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
{
// 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
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
}
}
I have a Canvas being populated from a XAML load from a file using:
SBWindowContainerCanvas.Children.Clear();
StreamReader stringreader = new StreamReader("C:\\xaml\\xmltest.xaml");
XmlReader xmlreader = XmlReader.Create(stringreader);
var mainborder = XamlReader.Load(xmlreader);
SBWindowContainerCanvas.Children.Add((Blacklight.Controls.ClippingBorder)mainborder);
The border contains many child elements including some user controls.
partial snippet of XAML file:
<av:ContentControl Name="VisScoreCC" Width="417" Height="228" IsHitTestVisible="True" av:Canvas.Left="855" av:Canvas.Top="8" av:Selector.IsSelected="False">
<fsp:Scores3DigitControl Name="Vis3DigitScoreControl" VisitorControl="True" OperatorControl="True" NumericValue="4" UseGradientMainBG="False" UseGradientNumberBG="False" UseGradientLogoBG="False" UseGradientTeamNameBG="False" UseRadialMainBG="False" UseRadialNumberBG="False" UseRadialLogoBG="False" UseRadialTeamNameBG="False" UseImageStringMainBG="False|none|False|Tile|Top Left|1" SolidColorMainBG="#FFD4D2D2" SolidColorNumberBG="#FF000000" SolidColorBannerBG="#FF000000" FGColorNumbers="#FFFF0000" FGColorTeamName="#FFFFFFFF" FGColorBanner="#FFFFA500" SolidColorImageLogoBG="#FF000000" SolidColorTeamNameBG="#FF000000" GradientMainBG="#FF000000|#FF808080|#FFFFFFFF" GradientNumberBG="#FF000000|#FF808080|#FFFFFFFF" GradientLogoBG="#FF000000|#FF808080|#FFFFFFFF" GradientTeamNameBG="#FF000000|#FF808080|#FFFFFFFF" GradientOffsetsMainBG="0|0.5|1" GradientOffsetsNumbersBG="0|0.5|1" GradientOffsetsLogoBG="0|0.5|1" GradientOffsetsTeamNameBG="0|0.5|1" LinearAngleMainBG="180" LinearAngleNumberBG="180" LinearAngleLogoBG="180" LinearAngleTeamNameBG="180" OffSegmentOpacity="0.1" RoundRadiusNumbers="0" RoundRadiusLogo="0" RoundRadiusTeamName="0" PosSizeStringNumbers="180|8|200|150" PosSizeStringLogo="20|10|150|150" PosSizeStringTeamName="25|165|360|60" TeamNameFontString="Arial|40|True" UseRightSideBanner="True" Style="{av:DynamicResource Scores3DigitControlTemplate}" Height="Auto" Margin="0,0,0,0" IsHitTestVisible="True" />
</av:ContentControl>
<av:ContentControl Name="HomeScoreCC" Width="417" Height="228" IsHitTestVisible="True" av:Canvas.Left="11" av:Canvas.Top="8" av:Selector.IsSelected="False">
<fsp:Scores3DigitControl Name="Home3DigitScoreControl" VisitorControl="False" OperatorControl="True" NumericValue="4" UseGradientMainBG="False" UseGradientNumberBG="False" UseGradientLogoBG="False" UseGradientTeamNameBG="False" UseRadialMainBG="False" UseRadialNumberBG="False" UseRadialLogoBG="False" UseRadialTeamNameBG="False" UseImageStringMainBG="False|none|False|Tile|Top Left|1" TargetElementIndex="0" SolidColorMainBG="#FFD4D2D2" SolidColorNumberBG="#FF000000" SolidColorBannerBG="#FF000000" FGColorNumbers="#FFFF0000" FGColorTeamName="#FFFFFFFF" FGColorBanner="#FFFFA500" SolidColorImageLogoBG="#FF000000" SolidColorTeamNameBG="#FF000000" GradientMainBG="#FF000000|#FF808080|#FFFFFFFF" GradientNumberBG="#FF000000|#FF808080|#FFFFFFFF" GradientLogoBG="#FF000000|#FF808080|#FFFFFFFF" GradientTeamNameBG="#FF000000|#FF808080|#FFFFFFFF" GradientOffsetsMainBG="0|0.5|1" GradientOffsetsNumbersBG="0|0.5|1" GradientOffsetsLogoBG="0|0.5|1" GradientOffsetsTeamNameBG="0|0.5|1" LinearAngleMainBG="180" LinearAngleNumberBG="180" LinearAngleLogoBG="180" LinearAngleTeamNameBG="180" OffSegmentOpacity="0.1" RoundRadiusNumbers="0" RoundRadiusLogo="0" RoundRadiusTeamName="0" PosSizeStringNumbers="35|8|200|150" PosSizeStringLogo="250|10|150|150" PosSizeStringTeamName="25|165|360|60" TeamNameFontString="Arial|40|True" UseRightSideBanner="False" Style="{av:DynamicResource Scores3DigitControlTemplate}" OverridesDefaultStyle="False" Name="Home3DigitScoreControl" Height="Auto" IsHitTestVisible="True" />
</av:ContentControl>
When I want to get access to my controls in code-behind, I am not able to actually re-hook to the elements using:
Scores3DigitControl Vis3DigitScoreControlC = (Scores3DigitControl)SBWindowContainerCanvas.FindName("Vis3DigitScoreControl");
if (Vis3DigitScoreControlC == null)
{
MessageBox.Show("Couldn't Find Vis");
}
else
{
Vis3DigitScoreControlC.Visibility = Visibility.Hidden;
}
The UI Elements show up fine on the screen but I always get a "Couldn't Find Vis" message. From everything I read so far, using the FindName should work as long as my element is a child somewhere in the canvas, right? What am I missing?
Thank you
FindName is generally used for getting elements from a template. In your case your probably best to use the LogicalTreeHelper
LogicalTreeHelper.FindLogicalNode(SBWindowContainerCanvas,
"Vis3DigitScoreControl");
Try to use that method:
public 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;
}
}
}
}
It should return to you all object which you need. And after you will be able choose one in foreach loop.
Try using x:Name instead of Name and checking after that element has loaded.
LogicalTreeHelper.FindLogicalNode(...) works here, because it goes across NameScope boundries.
Whereas FindName(...) works in same NameScope.
Dynamically adding controls require good understanding of NameScopes.