How do I get the Children of a ContentPresenter? - wpf

Using the code I can get a content presenter. I would like to locate the first textbox inside it and set the focus accordingly.
Dim obj = TerritoryListViewer.ItemContainerGenerator.ContainerFromItem(myModel)

You can use VisualTreeHelper static class to crawl controls tree.
This is how it can be accomplished in c# (sorry I'm VB dyslexic))
T FindFirstChild<T>(FrameworkElement element) where T: FrameworkElement
{
int childrenCount = VisualTreeHelper.GetChildrenCount(element);
var children = new FrameworkElement[childrenCount];
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(element, i) as FrameworkElement;
children[i] = child;
if (child is T)
return (T)child;
}
for (int i = 0; i < childrenCount; i++)
if (children[i] != null)
{
var subChild = FindFirstChild<T>(children[i]);
if (subChild != null)
return subChild;
}
return null;
}

ContentPresenter has the only child. You get the child simply by
VisualTreeHelper.GetChild(yourContentPresenterObj, 0);
If you need to go deeper - down to a first found TextBox, then, yes, you use the more comprehensive approach suggested by #alpha-mouse.

Dim myContentPresenter = CType(obj, ContentPresenter)
Dim myDataTemplate = myContentPresenter.ContentTemplate
Dim target = CType(myDataTemplate.FindName("txtQuantity", myContentPresenter), TextBox)

In my case I needed to iterate on all controls of a certain base type placed on a custom canvas which was being used inside an ItemsControl.
This Linq expression was used to get those controls from within MeasureOverride():
var foobarControls =
InternalChildren
.OfType<ContentPresenter>()
.Where(c => VisualTreeHelper.GetChildrenCount(c) > 0)
.Select(c => VisualTreeHelper.GetChild(c, 0))
.OfType<FoobarControlBase>();
This guards against cases where the ContentPresenter had no children. I found in some instances depending on when this was called the visual tree might not be established and as a result the ContentPresenters would have no children. (This situation might have been a bug in itself, actually, but nonetheless this code turned out to be reliable.)

Related

WPF Bing Maps Control - Change Scale Bar Units

I'm trying out the Bing Maps control for WPF and this is driving me mad.
I can't figure out how to access the built-in scale bar so I can change the units to metric.
I can add a new scale bar, compass, etc. just fine but can't access the default one.
Anyone have any idea how to do this?
An old question, but there is a much easier way to do this. Just set the Culture property on the map to a country that uses metric.
eg:
<m:Map
x:Name="MapObject"
CredentialsProvider="YOUR_KEY"
Center="-38.1231102,145.2116017"
ZoomLevel="11"
Mode="Aerial"
Culture="en-au"
/>
Here you go buddy. Credit goes to whoever posted the FindVisualChild method on the Internet.
void SomeMethod()
{
var mapScale = FindVisualChild<Scale>(YourBingMapControl);
mapScale.DistanceUnit = DistanceUnit.KilometersMeters;
}
static TChildItem FindVisualChild<TChildItem>(DependencyObject obj) where TChildItem : DependencyObject
{
// Search immediate children first (breadth-first)
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is TChildItem)
return (TChildItem)child;
else
{
var childOfChild = FindVisualChild<TChildItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}

Record items visible to user in ListBox

I have a ListBox or DataGrid filled with thousands of entries. I would like to know items that the user has looked at (scrolling, searching or otherwise). How can I tell what is visible to the user in the ListBox?
Bonus: Set a timer so that the item has to be shown for a minimum of N milliseconds (in the event the user is just pulling down the scrollbar).
Update: This is a near duplicate of Get items in view within a listbox - but the solution it gives, using "SelectedItems", is not sufficient. I need to know the items whether they are selected or not!
All you need to do is to get the underlying StackPanel that's inside the ListBox. It has enough information about which elements are showing. (It implements the interface IScrollInfo).
To get the underlying StackPanel (or actually VirtualizingStackPanel) from a given ListBox, we'll have to use VisualTreeHelper to go through the Visual Tree and look for the VirtualizingStackPanel, like so:
private VirtualizingStackPanel GetInnerStackPanel(FrameworkElement element)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var child = VisualTreeHelper.GetChild(element, i) as FrameworkElement;
if (child == null) continue;
Debug.WriteLine(child.ToString());
if (child is VirtualizingStackPanel) return child as VirtualizingStackPanel;
var panel = GetInnerStackPanel(child);
if (panel != null)
return panel;
}
return null;
}
Now that we have the StackPanel, we're very close. The StackPanel has the properties VerticalOffset and ViewportHeight (both coming from IScrollInfo) that can give us all the information we need.
private void button1_Click(object sender, RoutedEventArgs e)
{
var theStackPanel = GetInnerStackPanel(MyListBox);
List<FrameworkElement> visibleElements = new List<FrameworkElement>();
for (int i = 0; i < theStackPanel.Children.Count; i++)
{
if (i >= theStackPanel.VerticalOffset && i <= theStackPanel.VerticalOffset + theStackPanel.ViewportHeight)
{
visibleElements.Add(theStackPanel.Children[i] as FrameworkElement);
}
}
MessageBox.Show(visibleElements.Count.ToString());
MessageBox.Show(theStackPanel.VerticalOffset.ToString());
MessageBox.Show((theStackPanel.VerticalOffset + theStackPanel.ViewportHeight).ToString());
}

WPF RichTextbox remove Foreground information from TextRange

sorry for my bad english... The default for a RichTextBox content is to inherit the Foreground color from the RichTextBox itself. That's nice, but if I set a specific Foreground color to some part of my text, that part does not inherit the Foreground anymore, obviously. How can I make my "colored" text inherit the Foreground again? I'm trying to do something like the "Automatic" color from Office Word but after I have set a specific color to a TextRange, I do not know how to unset it :/
TextRange.ClearAllProperties() does what I need, but also erases other properties like FontSize and FontFamily...
TextRange.ApplyPropertyValue(ForegroundProperty, DependencyProperty.UnsetValue) also does not do the trick...
You can also unset it by setting the property to null (this worked for me clearing out the background, for example removing highlighting)
TextRange.ApplyPropertyValue(TextElement.BackgroundProperty, null);
This seemed almost impossible to achieve since there is no "RemovePropertyValue" method. I also tried with span and got the same exception as you did so I made a method that collects all the Paragraphs within the TextRange and made a span for each separetly.. less than ideal, I know.. Anyway, it works for a small example but might be pretty hard to work with for something more complex.
private List<Span> m_spanList = new List<Span>();
private void c_setForegroundButton_Click(object sender, RoutedEventArgs e)
{
TextPointer textPointerStart = c_richTextBox1.Selection.Start;
TextPointer textPointerEnd = c_richTextBox1.Selection.End;
TextRange textRange = new TextRange(textPointerStart, textPointerEnd);
SetForeground(textRange);
}
private void c_clearForegroundButton_Click(object sender, RoutedEventArgs e)
{
foreach (Span span in m_spanList)
{
span.ClearValue(Span.ForegroundProperty);
}
}
public void SetForeground(TextRange textRange)
{
List<Paragraph> spannedParagraphs = new List<Paragraph>();
if (textRange.Start.Paragraph != null)
{
TextRange curRange = null;
Block cur = textRange.Start.Paragraph;
do
{
spannedParagraphs.Add(cur as Paragraph);
// Get next range
curRange = new TextRange(cur.ContentStart, cur.ContentEnd);
} while ((textRange.End.Paragraph == null || !curRange.Contains(textRange.End.Paragraph.ContentEnd)) && (cur = cur.NextBlock) != null);
}
if (spannedParagraphs.Count == 1)
{
Span span = new Span(c_richTextBox1.Selection.Start, c_richTextBox1.Selection.End);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
else
{
for (int i = 0; i < spannedParagraphs.Count; i++)
{
if (i == spannedParagraphs.Count - 1)
{
Paragraph paragraph = spannedParagraphs[i];
// For some reason I get an exception here when I try this..
//m_span = new Span(paragraph.ElementStart, c_richTextBox1.Selection.End);
c_richTextBox1.Selection.Select(paragraph.ElementStart, c_richTextBox1.Selection.End);
Span span = new Span(c_richTextBox1.Selection.Start, c_richTextBox1.Selection.End);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
else if (i == 0)
{
Paragraph paragraph = spannedParagraphs[i];
Span span = new Span(c_richTextBox1.Selection.Start, paragraph.ElementEnd);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
else
{
Paragraph paragraph = spannedParagraphs[i];
Span span = new Span(paragraph.ElementStart, paragraph.ElementEnd);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
}
}
}
If you look at the code of method TextRange.ApplyPropertyValue in the .NET Reference Source, you'll see that in the end it calls DependencyObject.SetValue on a collection of Inlines and Blocks, and DependencyObject.SetValue treats the value DependencyProperty.UnsetValue specially by effectively clearing the local value for the property.
The problem is that they didn't think of that when implementing TextRange.ApplyPropertyValue: it checks the passed property value against the property type, and in case of a reference type, it makes sure the passed value is either null or inherits from the same class, thus preventing us from passing DependencyProperty.UnsetValue.
One solution I found to implement a way of clearing local values of a TextRange for dependency properties of a reference type is the following:
// We declare a marker brush to be detected later in the TextRange.
var markerBrush = new SolidColorBrush();
// First we ask the TextRange implementation to set our marker brush on its content.
// Using ApplyPropertyValue here takes care of splitting inlines when necessary to make
// sure that only the selected text gets affected.
range.ApplyPropertyValue(TextElement.ForegroundProperty, markerBrush);
// Now, we search the text range for every Inline that has our brush set as the foreground
// brush, and we clear the Foreground dependency property.
var position = range.Start;
while (position != null && range.Contains(position))
{
if (position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart &&
position.Parent is Inline inline &&
inline.ReadLocalValue(TextElement.ForegroundProperty) == _foregroundClearBrush)
inline.ClearValue(TextElement.ForegroundProperty);
position = position.GetNextContextPosition(LogicalDirection.Forward);
}

AssociatedObject.FindName in Silverlight behavior OnAttached method returns null

I'm making a Silverlight behavior to enable dragging an element by a contained "drag handle" element (rather than the whole element being draggable). Think of it like a window title bar.
In the OnAttached method I am calling: AssociatedObject.FindName(DragHandle)
but this is returning null.
I then tried handling the AssociatedObject's Loaded event and running my code there, but I still get a null returned.
Am I misunderstanding what FindName is able to do? The AssociatedObject is in an ItemsControl (I want a collection of draggable elements). So is there some kind of namescope problem?
Yes, it sounds like a namescope problem. The MSDN documentation on XAML namescopes goes over how namesopes are defined for templates and item controls. Are you using a template for the items in your ItemsControl?
You may just have to walk the visual tree recursively with something like this to find the correct element by name:
private static FrameworkElement FindChildByName(FrameworkElement parent, string name)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
FrameworkElement child = VisualTreeHelper.GetChild(parent, i) as FrameworkElement;
if (child != null && child.Name == name)
{
return child;
}
else
{
FrameworkElement grandChild = FindChildByName(child, name);
if (grandChild != null)
{
return grandChild;
}
}
}
return null;
}

How can I access the ListViewItems of a WPF ListView?

Within an event, I'd like to put the focus on a specific TextBox within the ListViewItem's template. The XAML looks like this:
<ListView x:Name="myList" ItemsSource="{Binding SomeList}">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<!-- Focus this! -->
<TextBox x:Name="myBox"/>
I've tried the following in the code behind:
(myList.FindName("myBox") as TextBox).Focus();
but I seem to have misunderstood the FindName() docs, because it returns null.
Also the ListView.Items doesn't help, because that (of course) contains my bound business objects and no ListViewItems.
Neither does myList.ItemContainerGenerator.ContainerFromItem(item), which also returns null.
To understand why ContainerFromItem didn't work for me, here some background. The event handler where I needed this functionality looks like this:
var item = new SomeListItem();
SomeList.Add(item);
ListViewItem = SomeList.ItemContainerGenerator.ContainerFromItem(item); // returns null
After the Add() the ItemContainerGenerator doesn't immediately create the container, because the CollectionChanged event could be handled on a non-UI-thread. Instead it starts an asynchronous call and waits for the UI thread to callback and execute the actual ListViewItem control generation.
To be notified when this happens, the ItemContainerGenerator exposes a StatusChanged event which is fired after all Containers are generated.
Now I have to listen to this event and decide whether the control currently want's to set focus or not.
As others have noted, The myBox TextBox can not be found by calling FindName on the ListView. However, you can get the ListViewItem that is currently selected, and use the VisualTreeHelper class to get the TextBox from the ListViewItem. To do so looks something like this:
private void myList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (myList.SelectedItem != null)
{
object o = myList.SelectedItem;
ListViewItem lvi = (ListViewItem)myList.ItemContainerGenerator.ContainerFromItem(o);
TextBox tb = FindByName("myBox", lvi) as TextBox;
if (tb != null)
tb.Dispatcher.BeginInvoke(new Func<bool>(tb.Focus));
}
}
private FrameworkElement FindByName(string name, FrameworkElement root)
{
Stack<FrameworkElement> tree = new Stack<FrameworkElement>();
tree.Push(root);
while (tree.Count > 0)
{
FrameworkElement current = tree.Pop();
if (current.Name == name)
return current;
int count = VisualTreeHelper.GetChildrenCount(current);
for (int i = 0; i < count; ++i)
{
DependencyObject child = VisualTreeHelper.GetChild(current, i);
if (child is FrameworkElement)
tree.Push((FrameworkElement)child);
}
}
return null;
}
I noticed that the question title does not directly relate to the content of the question, and neither does the accepted answer answer it. I have been able to "access the ListViewItems of a WPF ListView" by using this:
public static IEnumerable<ListViewItem> GetListViewItemsFromList(ListView lv)
{
return FindChildrenOfType<ListViewItem>(lv);
}
public static IEnumerable<T> FindChildrenOfType<T>(this DependencyObject ob)
where T : class
{
foreach (var child in GetChildren(ob))
{
T castedChild = child as T;
if (castedChild != null)
{
yield return castedChild;
}
else
{
foreach (var internalChild in FindChildrenOfType<T>(child))
{
yield return internalChild;
}
}
}
}
public static IEnumerable<DependencyObject> GetChildren(this DependencyObject ob)
{
int childCount = VisualTreeHelper.GetChildrenCount(ob);
for (int i = 0; i < childCount; i++)
{
yield return VisualTreeHelper.GetChild(ob, i);
}
}
I'm not sure how hectic the recursion gets, but it seemed to work fine in my case. And no, I have not used yield return in a recursive context before.
You can traverse up the ViewTree to find the item 'ListViewItem' record set that corresponds to the cell triggered from hit test.
Similarly, you can get the column headers from the parent view to compare and match the cell's column. You may want to bind the cell name to the column header name as your key for your comparator delegate/filter.
For example: HitResult is on TextBlock shown in green. You wish to obtain the handle to the 'ListViewItem'.
/// <summary>
/// ListView1_MouseMove
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListView1_MouseMove(object sender, System.Windows.Input.MouseEventArgs e) {
if (ListView1.Items.Count <= 0)
return;
// Retrieve the coordinate of the mouse position.
var pt = e.GetPosition((UIElement) sender);
// Callback to return the result of the hit test.
HitTestResultCallback myHitTestResult = result => {
var obj = result.VisualHit;
// Add additional DependancyObject types to ignore triggered by the cell's parent object container contexts here.
//-----------
if (obj is Border)
return HitTestResultBehavior.Stop;
//-----------
var parent = VisualTreeHelper.GetParent(obj) as GridViewRowPresenter;
if (parent == null)
return HitTestResultBehavior.Stop;
var headers = parent.Columns.ToDictionary(column => column.Header.ToString());
// Traverse up the VisualTree and find the record set.
DependencyObject d = parent;
do {
d = VisualTreeHelper.GetParent(d);
} while (d != null && !(d is ListViewItem));
// Reached the end of element set as root's scope.
if (d == null)
return HitTestResultBehavior.Stop;
var item = d as ListViewItem;
var index = ListView1.ItemContainerGenerator.IndexFromContainer(item);
Debug.WriteLine(index);
lblCursorPosition.Text = $"Over {item.Name} at ({index})";
// Set the behavior to return visuals at all z-order levels.
return HitTestResultBehavior.Continue;
};
// Set up a callback to receive the hit test result enumeration.
VisualTreeHelper.HitTest((Visual)sender, null, myHitTestResult, new PointHitTestParameters(pt));
}
We use a similar technique with WPF's new datagrid:
Private Sub SelectAllText(ByVal cell As DataGridCell)
If cell IsNot Nothing Then
Dim txtBox As TextBox= GetVisualChild(Of TextBox)(cell)
If txtBox IsNot Nothing Then
txtBox.Focus()
txtBox.SelectAll()
End If
End If
End Sub
Public Shared Function GetVisualChild(Of T As {Visual, New})(ByVal parent As Visual) As T
Dim child As T = Nothing
Dim numVisuals As Integer = VisualTreeHelper.GetChildrenCount(parent)
For i As Integer = 0 To numVisuals - 1
Dim v As Visual = TryCast(VisualTreeHelper.GetChild(parent, i), Visual)
If v IsNot Nothing Then
child = TryCast(v, T)
If child Is Nothing Then
child = GetVisualChild(Of T)(v)
Else
Exit For
End If
End If
Next
Return child
End Function
The technique should be fairly applicable for you, just pass your listviewitem once it's generated.
Or it can be simply done by
private void yourtextboxinWPFGrid_LostFocus(object sender, RoutedEventArgs e)
{
//textbox can be catched like this.
var textBox = ((TextBox)sender);
EmailValidation(textBox.Text);
}

Resources