FindVisualChild cannot find Grid in DataTemplate - c#

I am trying to use a basic implementation of "FindVisualChild" for WPF in order to find a specific Grid that exists within a DataTemplate of a ListBox.
The implementation is as follows:
private DependencyObject FindVisualChild<T>(DependencyObject obj, string name)
{
Console.WriteLine(((FrameworkElement)obj).Name);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
FrameworkElement fe = child as FrameworkElement;
//not a framework element or is null
if (fe == null) return null;
if(!string.IsNullOrEmpty(fe.Name))
Console.WriteLine(fe.Name);
if (child is T && fe.Name.Equals(name, StringComparison.CurrentCultureIgnoreCase))
return child;
else
{
//Not found it - search children
DependencyObject nextLevel = FindVisualChild<T>(child, name);
if (nextLevel != null)
return nextLevel;
}
}
return null;
}
My issue is that this code was working yesterday to find a Grid that I have defined in the DataTemplate with the name "MainTermServListGrid" as shown here:
<ListBox HorizontalAlignment="Stretch" Grid.Row="1" x:Name="TermServListBox" ItemsSource="{Binding TermServs}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="MainTermServListGrid">
//code here
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
However, today when I try to use the same method to find that Grid, the result is always null. If I debug and step through the code, it looks like it is not even finding any of the items that exist within the DataTemplate.
I am calling the FindVisualChild method right after I populate the ListBox with items. Could it be that I am not waiting long enough and the window does not have enough time to finish initializing and presenting the new items in the list box before I am trying to find a specific child within that listbox?
If that is the case, would a simple call to await Task.Delay(500) work to give the UI enough time to finish loading? Or am I doing something totally wrong here?

Turns out I was right in assuming I was not giving the UI enough time to finish loading. I believe it is due to the fact that I was populating the ListBox with items using a particular method, and at the end of that method I was raising an Event which triggered the search in the Window's code-behind.
Because there was never a period if time between finishing the loading of the items and the event being raised to look for them, I dont think the ui had time to finish initializing everything.
Basically all I did to fix the issue in my event handler was the following:
private async void ViewModelOnListPopulated(object sender, EventArgs eventArgs)
{
await Task.Delay(500);
//Continue on to find the visual child...
}

Related

split XAML children into multiple elements in a user control

I've been trying to make a user control for a questionaire application where I have some questions that needs follow-up questions under certain conditions. A simple senario could be that we want to show follow-up quesions if we get a "yes" on some question.
At this point I have followed the example from Zenexer on a similar question. My thought was that I would put the first child of my user control in one container (a StackPanel or whatever) and all
subsequent elements in a second StackPanel. But in the aforementioned example all child elements get stuffed into one element in the user control. To my understanding this is because [ContentProperty(nameof(Children))] is set to all of the content of the user control.
I tried to change the getter and setter of Children in the Zenexer's example but to no avail.
The Question:
Is there a way to spilt the children of my user control into two (or more) elements in my user control with XAML that looks something like this:
MainWindow.xaml
<SubQuestionBox>
<BinaryQuestion
QuestionNumber="4.1"
QuestionText="Parent question"/>
<TextQuestion
QuestionNumber="4.1.1"
QuestionText="Child question 1"/>
<TextQuestion
QuestionNumber="4.1.2"
QuestionText="Child question 2"/>
</SubQuestionBox>
SubQuestionBox.xaml
<UserControl x:Class="SubQuestionBox">
<!--StackPanel To contain the question controls-->
<StackPanel>
<StackPanel x:Name="ParentContainer" />
<StackPanel x:Name="SubQuestionsContainer" />
</StackPanel>
</UserControl>
If anyone wonders how I did it, it goes something like this.
SubQuestionBox.xaml is something like in the original question
SubQuestionBox.xaml.cs
The Children property is a DependencyProperty like in the example of #Zenexer
public SubQuestionBox()
{
InitializeComponent();
Children = SubQuestionsContainer.Children;
// add listener for Loaded event and call OnLoaded()
Loaded += OnLoaded;
if (ParentQuestion != null)
// The BinaryQuestion has a AnswerChanged event
ParentQuestion.AnswerChanged += ToggleCollapse;
}
public void DistributeQuestions()
{
// This method seems super hacky to me.
// I would have thought there is a more elegant way
UIElement parent = null;
if (Children != null)
{
parent = Children[0];
Children.RemoveAt(0);
}
if (ParentContainer != null && parent != null)
{
ParentContainer.Children.Add(parent);
}
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
DistributeQuestions();
}

Why does the VisualTreeHelper return a border for a button

I am using the VisualTreeHelper to perform a HitTest on a button that looks like this...
<Button Width="100"
Height="100"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="Purple">
Hello world
</Button>
However when I perform the actual HitTest as such:
HitTestResult result = VisualTreeHelper.HitTest(_ContentHolder, new Point(xTransform, yTransform));
if (result != null)
{
}
The returned object looks like this VisualHit = {System.Windows.Controls.Border} which doesn't quite make sense to me.
Could anyone please provide any guidance as to what I might be doing wrong, how I would return the actual Button object (such that I can trigger any event handlers) and why I may be experiencing such behaviour?
If you look at the control template for the Button here you can see that it is composed almost entirely of a Border control. Since the Border is what actually took the hit, that is what is being returned. You should use a common helper function to walk up the visual tree to find the actual button.
public static T FindParent<T>(DependencyObject child) where T : DependencyObject
{
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
return parent;
else
return FindParent<T>(parentObject);
}
Call this function, passing in the result of the hit test and specifying Button as the type should return the parent button that was hit.
Now... With all that being said, you might be going about what you are trying to do the wrong way. Without more details about what you are doing and, more importantly why, it could be hard to guide you.

How to get UIElement out of templated data in ListView?

Okay, i feel slightly dumb for askin this but, I have a listview with a templated class MyClass or whatever, whenever i "myListView.Add(new MyClass())" the winrt platform adds a new UIElement there and binds the proper properties into their proper uielements properly, now, I want to be able to iterate through these logical items (myListView.Items or myListView.SelectedItems) and get their corresponding UIElement for animation, is that possible?
like for example
class PhoneBookEntry {
public String Name { get;set }
public String Phone { get;set }
public PhoneBookEntry(String name, String phone) {
Name = name; Phone = phone;
}
};
myListView.Add(new PhoneBookEntry("Schwarzeneger", "123412341234");
myListView.Add(new PhoneBookEntry("Stallone", "432143214321");
myListView.Add(new PhoneBookEntry("Statham", "567856785678");
myListView.Add(new PhoneBookEntry("Norris", "666666666666");
And in XAML (just an example so i can explain what I mean)
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Phone}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
So, my point and objective here is to
foreach(PhoneBookEntry pbe in myListView.Items) // or SelectedItems
{
UIElement el; // How can I get the UIElement associated to this PhoneBookEntry pbe?
if(el.Projection == null)
el.Projection = new PlaneProjection;
PlaneProjection pp = el.Projection as PlaneProjection;
// Animation code goes here.
if(myListView.SelectedItems.Contains(pbe)
//something for selected
else
//something for not selected
}
I just need a way to get an UIElement which is being used to represent this logical data class PhoneBookEntry in the templated listview.
Also, this necessity comes with a very big problem I'm having where, selected items doesn't differ visually on Windows Phone -_- any ideas?
You can also use the ListView.ContainerFromItem or ListView.ContainerFromIndex methods which will return the container UI element for a given item in the list view (of course, only if the container is generated)
Ok I may look like a fool answering my own question but i've figured a way out.
First things first: ListViews only create UIElements for determinate items in the list (the ones cached and the ones being shown). So if you do add 2000 items to myListView.Items, the effective ammount of UIElements representing these items will be 56 or close number.
Because, the ItemListView simulates the UIElements even if they're not there, just to give size and position to the scrollbar (hence why scrolling down on very large lists cause some lag, WinRT is unloading UIElements and loading new ones)
From that, I figured out I could simply iterate through the current list of loaded UIElements through
// For each of the cached elements
foreach(LIstViewItem lvi in myListView.ItemsPanelRoot.Children)
{
// Inside here I can get the base object used to fill the data template using:
PhoneBookEntry pbe = lvi.Content as PhoneBookEntry;
if(pbe.Name == "Norris")
BeAfraid();
// Or check if this ListViewItem is or not selected:
bool isLviSelected = lvi.IsSelected;
// Or, like I wanted to, get an UIElement to animate projection
UIElement el = lvi as UIElement;
if(el.Projection == null)
el.Projection = new PlaneProjection();
PlaneProjection pp = el.Projection as PlaneProjection;
// Now I can use pp to rotate, move and whatever with this UIElement.
}
So, this is it. Right beneath my nose...

Compare controls in wpf

sorry about not making myself clear enough and not putting enough effort (wont happen again :)). i'm building a form App where users have to fill out the form. i have a TabControl with 3 TabItem one of the TabItem has TextBoxes the second has TextBoxes and RadioButton and third has only CheckBoxes. i have written a code to dictect error by on clicking submit using ValidationRule/ValidationResult and and using GroupBinding (got it from msdn samples). now the problem am having is code to search through the tabs, compare the controls (e.g. controlA,controlB) to know wich one comes before the other and return the tabindex. one of the use i want with this is letting the user jump to the uncompleted TextBox,RadioButton or CheckBoxes in order like starting from the first Tabitem in the TabControl
with this code i could work the tree to locate the controls(code from Philipp Sumi blog but i modified it a little)
private void Button_Click(
object sender,
RoutedEventArgs e)
{
IEnumerator enumerator = FindLogicalChildren(_parentStackPanel).GetEnumerator();
while (enumerator.MoveNext())
MessageBox.Show(enumerator.Current.ToString());
}
private IEnumerable FindLogicalChildren(
DependencyObject depObj)
{
if (depObj != null)
{
foreach (object childObj in LogicalTreeHelper.GetChildren(depObj))
{
DependencyObject child = childObj as DependencyObject;
if (child != null && child is Control)
{
yield return (Control)child;
}
foreach (Control childOfChild in FindLogicalChildren(child))
{
yield return childOfChild;
}
}
}
}
but i dodnt know how continue to get the tabindex of each control in order form as i work down the tree. can any one please help me on this? Thanks
im using this method:
private int Compare(
Control controlA,
Control controlB)
{
DependencyObject commonAncestor = controlA.FindCommonVisualAncestor(controlB);
for (int index = 0; index < VisualTreeHelper.GetChildrenCount(commonAncestor); index++)
{
Visual childVisual = (Visual)VisualTreeHelper.GetChild(commonAncestor, index);
Control control = (Control)childVisual;
control.TabIndex = index;
}
return controlA.TabIndex.CompareTo(controlB.TabIndex);
}
that returns 1,-1 or 0 to compare two controls to find out which one comes before the other. the question is, can anyone tell me a better way of doing this.

Scroll a WPF FlowDocumentScrollViewer from code?

I have a FlowDocumentScrollViewer I want to automatically scroll to the bottom
when text is added.
<FlowDocumentScrollViewer Name="Scroller">
<FlowDocument Foreground="White" Name="docDebug" FontFamily="Terminal">
<Paragraph Name="paragraphDebug"/>
</FlowDocument>
</FlowDocumentScrollViewer>
In code I add Inlines to the Paragraph, but when there is to much text I would
like to be able to simply scroll down using code instead of having the user doing so.
Any suggestions?
try:
Scroller.ScrollViewer.ScrollToEnd();
Where "Scroller" is the name of your FlowDocumentScrollViewer.
EDIT: I wrote this answer a little too quickly. FlowDocumentScrollViewer does not expose a ScrollViewer property. I had actually extended the FlowDocumentScrollViewer class and implemented the ScrollViewer property myself. Here is the implementation:
/// <summary>
/// Backing store for the <see cref="ScrollViewer"/> property.
/// </summary>
private ScrollViewer scrollViewer;
/// <summary>
/// Gets the scroll viewer contained within the FlowDocumentScrollViewer control
/// </summary>
public ScrollViewer ScrollViewer
{
get
{
if (this.scrollViewer == null)
{
DependencyObject obj = this;
do
{
if (VisualTreeHelper.GetChildrenCount(obj) > 0)
obj = VisualTreeHelper.GetChild(obj as Visual, 0);
else
return null;
}
while (!(obj is ScrollViewer));
this.scrollViewer = obj as ScrollViewer;
}
return this.scrollViewer;
}
}
I've faced a similar problem: I wanted a textual area which could hold my text, is able to wrap it, it fills its parent control and is scrollable.
First I've tried to use a TextBlock with a ScrollViewer and I think it worked, but for some reason I've wanted to use a FlowDocument instead with a FlowDocumentScrollViewer. This latter didn't work and I just couldn't leave the fight unattented so I tried to find solutions and this is how I got here. I've tried to apply the workarounds presented in the answers to the original question, however neither solutions worked out for me (I'm using .NET 4.5, maybe it works in other versions, but I don't know about that).
I've tried using a single FlowDocument by itself also, but the control contains some UI elements I didn't want. So, I came up with another solution.
<ScrollViewer VerticalScrollBarVisibility="Auto">
<FlowDocumentScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
<FlowDocument>
That's right. It works! Calling ScrollViewer.ScrollToBottom() just works! The ScrollViewer enables scrolling and FlowDocumentScrollViewer removes the UI elements from the FlowDocument. Hope it helps!
Apparently my construction had a flaw, because this way the FlowDocument isn't scrollable via a mouse's scrolling wheel. However setting the FlowDocumentScrollViewer control's IsHitTestVisible property to False solves this.
The other answers given here are a bit puzzling, since I don't see any public "ScrollViewer" property on the FlowDocumentScrollViewer.
I hacked around the problem like this. Beware that this method can return null during initialization:
public static ScrollViewer FindScrollViewer(this FlowDocumentScrollViewer flowDocumentScrollViewer)
{
if (VisualTreeHelper.GetChildrenCount(flowDocumentScrollViewer) == 0)
{
return null;
}
// Border is the first child of first child of a ScrolldocumentViewer
DependencyObject firstChild = VisualTreeHelper.GetChild(flowDocumentScrollViewer, 0);
if (firstChild == null)
{
return null;
}
Decorator border = VisualTreeHelper.GetChild(firstChild, 0) as Decorator;
if (border == null)
{
return null;
}
return border.Child as ScrollViewer;
}
This question was asked 7 years ago, now I have the same problem, and I find a simple solution. The follow code add a Section to Flowdocument which same to Paragraph, then scroll to the end.
private void addSection(Section section)
{
section.Loaded += section_Loaded;
fdoc.Blocks.Add(section);
}
private void section_Loaded(object sender, RoutedEventArgs e)//scroll to end
{
var sec = sender as Section;
if (sec != null)
{
sec.BringIntoView();
}
}
This may be a very late answer, but I've found a way to do this.
//after your FlowDocumentScrollViewer(for example, x:Name="fdsv") loaded
ScrollViewer sv = fdsv.Template.FindName("PART_ContentHost", fdsv) as ScrollViewer;
sv.ScrollToBottom();
sv.ScrollToTop();
sv.ScrollToVerticalOffset(100);
// etc.
Check IScrollInfo and ScrollViewer for details.
I hope this helps you.

Categories