Enumerate ItemsControl.Items as UIElements - silverlight

I have a list of hyperlinks that are displayed through an ItemsControl, something like this:
<ItemsControl x:Name="SubMenu" Visibility="Collapsed">
<ItemsControl.ItemTemplate>
<DataTemplate>
<HyperlinkButton Content="{Binding Name}"
NavigateUri="{Binding Url}"
TargetName="ContentFrame"
Style="{StaticResource LinkStyle}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Style="{StaticResource LinksStackPanelStyle}"
VerticalAlignment="Center"
HorizontalAlignment="Left" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
what I need to do is enumerate the actual hyperlinks in the submenu, something like this:
foreach (UIElement child in SubMenu.Items) // this does not work!
{
HyperlinkButton hb = child as HyperlinkButton;
if (hb != null && hb.NavigateUri != null)
{
if (hb.NavigateUri.ToString().Equals(e.Uri.ToString()))
{
VisualStateManager.GoToState(hb, "ActiveLink", true);
}
else
{
VisualStateManager.GoToState(hb, "InactiveLink", true);
}
}
}
The problem is that I can´t seem to find a way to enumerate the actual UI elements in the ItemsCollection.Items.
Anyone know how to do this or a possible workaround?
I can mention that what I´m trying to do is build a menu and submenu that display the hyperlinks clicked as a sort of breadcrumb.
UPDATE:
The best thing would be if I could get to that stackpanel somehow because this code seems to work:
foreach (UIElement child in LinksStackPanel.Children)
{
HyperlinkButton hb = child as HyperlinkButton;
if (hb != null && hb.NavigateUri != null)
{
if (hb.NavigateUri.ToString().Equals(e.Uri.ToString()))
{
VisualStateManager.GoToState(hb, "ActiveLink", true);
}
else
{
VisualStateManager.GoToState(hb, "InactiveLink", true);
}
}
}

The solution looks like this:
foreach (var item in SubMenu.Items)
{
var hb = SubMenu.ItemContainerGenerator.ContainerFromItem(item).FindVisualChild<HyperlinkButton>();
if (hb.NavigateUri.ToString().Equals(e.Uri.ToString()))
{
VisualStateManager.GoToState(hb, "ActiveLink", true);
}
else
{
VisualStateManager.GoToState(hb, "InactiveLink", true);
}
}
The extension method FindVisualChild:
public static T FindVisualChild<T>(this DependencyObject instance) where T : DependencyObject
{
T control = default(T);
if (instance != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(instance); i++)
{
if ((control = VisualTreeHelper.GetChild(instance, i) as T) != null)
{
break;
}
control = FindVisualChild<T>(VisualTreeHelper.GetChild(instance, i));
}
}
return control;
}

Try using the ItemContainerGenerator.ContainerFromItem method
foreach (var item in SubMenu.Items)
{
var child = SubMenu.ItemContainerGenerator.ContainerFromItem(item);
HyperlinkButton hb = child as HyperlinkButton;
// use hb
}

FindVisualChild from Johan Leino answer has bug: traversing of lower levels in control hierarchy does not have any effect because he don't check result of recursive call.
That is fixed version.
public static T FindVisualChild<T>(this DependencyObject instance) where T : DependencyObject
{
T control = default(T);
if (instance != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(instance); i++)
{
if ((control = VisualTreeHelper.GetChild(instance, i) as T) != null)
{
break;
}
if ((control = FindVisualChild<T>(VisualTreeHelper.GetChild(instance, i))) != null)
{
break;
}
}
}
return control;
}

Try this:
foreach (UIElement child in SubMenu.Items.OfType<UIElement>())
This is using the Enumerable.OfType<TResult> extension method that filters the collection down to only those items that are of the specified type.

Related

Make wpf button fonts match at runtime

I have several buttons, like this:
<Button Name="btnContent" Grid.Column="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
<Viewbox MaxWidth="100" StretchDirection="Both">
<TextBlock Text="Content" ></TextBlock>
</Viewbox>
</Button>
<Button Name="btnMoreContent" Grid.Column="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
<Viewbox MaxWidth="100" StretchDirection="Both">
<TextBlock Text="More Content" ></TextBlock>
</Viewbox>
</Button>
The font scales to fit the buttons. How can I grab the text size / scale value of the button with the smallest text at runtime and set them all to that size?
I tried:
double FontSize = btnContentButton.FontSize;
if (FontSize < btnLongerContentButton.FontSize)
{
FontSize = btnLongerContentButton.FontSize;
}
btnContentButton.FontSize = FontSize;
btnLongerContentButton.FontSize = FontSize;
But this doesn't work, because I never actually changed the font size - It sets them all to 12.
Try the suggested solutions in this link and see if they can solve the problem.
However, you might want to try this one too: Handle Load event, find all TextBlocks with parent of type ViewBox (You might check other conditions or set proper names, to avoid further problems here), adjust their Width, properly, based on the Width of the one with the longest Text:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
List<TextBlock> tbs = new List<TextBlock>();
TextBlock longestText = null;
foreach (TextBlock t in FindVisualChildren<TextBlock>(this))
{
if (t.Parent is Viewbox)
{
tbs.Add(t);
if (longestText == null)
longestText = t;
else if (t.Text.Length > longestText.Text.Length)
longestText = t;
}
}
double a = longestText.ActualWidth / longestText.ActualHeight;
foreach (TextBlock tb in tbs)
{
if (tb == longestText)
continue;
tb.Width = a * tb.ActualHeight;
}
}
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;
}
}
}
}
FindVisualChildren method is from here.
Hope it helps.

Getting the reference to FrameworkElement in nested ItemsControls

I have a nested ItemsControl to display the following Model:
public class Parent
{
public string ParentTitle
{
get;
set;
}
ICollection<Child> Children
{
get;
set;
}
}
public class Child
{
public string ChildTitle
{
get;
set;
}
}
The ItemsControl looks like this:
<ItemsControl x:Name="listOfParents">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Parent}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button x:Name="btnTarget" Grid.Row="0" Content="{Binding ParentTitle}"></Button>
<ItemsControl Grid.Row="1" ItemsSource="{Binding Children}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Child}">
<Button x:Name="btnSource" Content="{Binding ChildTitle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The listOfParents Itemssouce is List<Parent>. How do I access Button btnTarget when btnSource is clicked?
You can access the Button with the FindChild():
Listing of function:
public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject
{
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);
T childType = child as T;
if (childType == null)
{
foundChild = FindChild<T>(child, childName);
if (foundChild != null) break;
}
else
if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
if (frameworkElement != null && frameworkElement.Name == childName)
{
foundChild = (T)child;
break;
}
else
{
foundChild = FindChild<T>(child, childName);
if (foundChild != null)
{
break;
}
}
}
else
{
foundChild = (T)child;
break;
}
}
return foundChild;
}
The call is made so:
private void btnSource_Click(object sender, RoutedEventArgs e)
{
Button MyBtnTarget = FindChild<Button>(listOfParents, "btnTarget");
MessageBox.Show(MyBtnTarget.Content.ToString());
}
But in this way, the function will select the very first button, and we need to get access to all the elements. For this, I rewrote the function so that it returns all the elements of the list. Here's the code:
public static void FindChildGroup<T>(DependencyObject parent, string childName, ref List<T> list) where T : DependencyObject
{
// Checks should be made, but preferably one time before calling.
// And here it is assumed that the programmer has taken into
// account all of these conditions and checks are not needed.
//if ((parent == null) || (childName == null) || (<Type T is not inheritable from FrameworkElement>))
//{
// return;
//}
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
// Get the child
var child = VisualTreeHelper.GetChild(parent, i);
// Compare on conformity the type
T child_Test = child as T;
// Not compare - go next
if (child_Test == null)
{
// Go the deep
FindChildGroup<T>(child, childName, ref list);
}
else
{
// If match, then check the name of the item
FrameworkElement child_Element = child_Test as FrameworkElement;
if (child_Element.Name == childName)
{
// Found
list.Add(child_Test);
}
// We are looking for further, perhaps there are
// children with the same name
FindChildGroup<T>(child, childName, ref list);
}
}
return;
}
}
Calling function:
private void btnSource_Click(object sender, RoutedEventArgs e)
{
// Create the List of Button
List<Button> list = new List<Button>();
// Find all elements
FindChildGroup<Button>(listOfParents, "btnTarget", ref list);
string text = "";
foreach (Button elem in list)
{
text += elem.Content.ToString() + "\n";
}
MessageBox.Show(text, "Text in Button");
}
In general there are several ways to access the template. Here's one: How to use FindName with a ContentControl.

why the name of the Controls in ItemsControl not displaying in Codebehind file in Wpf

i Created simple sample using the ItemsControl and DataTemplate.. i want to bind the values using the C# code in textblocs.But i didn't get the textblock names and Datatemplate names in Code behind file please tell me why.. What to do to get the names of the controls ?
<ItemsControl ItemsSource="{Binding Path=.}" >
<ItemsControl.ItemTemplate>
<DataTemplate x:Name="datatemp">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="Textblock1" Text="{Binding }" FontWeight="Bold" ></TextBlock>
<TextBlock Text=", " />
<TextBlock Text="{Binding }" x:Name="Textblock2"></TextBlock>
<TextBlock Text=", " />
<TextBlock Text="{Binding }" x:Name="Textblock3"></TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
but here in Code file Textblock1 and other names not dispalying even i used the "Name" only Instead of "x:Name "
No member will be generated for the DataTemplate. A datatemplate is just used for instantiating items dynamically at runtime, so the Controls don't even exist until you add items to your ItemsControl, and even then i think the names of individual controls inside the DataTemplate are just useful for usage from inside the DataTemplate's markup.
You can generate names, if needed when the ItemsControl is loaded.
private void OnPopupItemsLoaded(object sender, RoutedEventArgs e)
{
var itemsControl = sender as ItemsControl;
itemsControl.ApplyTemplate();
var numItems = itemsControl.ItemContainerGenerator.Items.Count();
for (var i = 0; i < numItems; i++)
{
var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i);
textBlock = FindVisualChild<TextBlock>(container);
if (textBlock != null)
{
textBlock.Name = SanitizeName(textBlock.Text);
textBlock.Uid = $"Item{i}";
}
}
}
private 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;
}
}
}
// None found
return null;
}
// FrameworkeElement.Name must start with an underscore or letter and
// only contain letters, digits, or underscores.
private string SanitizeName(string textString)
{
// Text may start with a digit
var sanitizedName = "_";
foreach (var c in textString)
{
if (char.IsLetterOrDigit(c))
{
sanitizedName += c;
}
else
{
sanitizedName += "_";
}
}
return sanitizedName;
}

How can I access a checked Radio Button in a data bound ItemsControl?

I have a ItemsControl bound to a string list.
Code:-
List<string> possibleAnswers;
possibleAnswers = GetPossibleAnswers(currentQuestion);
AnswerIC.Items.Clear();
AnswerIC.ItemsSource = possibleAnswers;
Xaml:-
<ItemsControl x:Name="AnswerIC" Grid.Row="1" Margin="0,10,0,10">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel x:Name="AnswerSP" Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton GroupName="AnswerRBG" Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In a button click event, I am trying to find the content of the checked radio button and am unable to. Anyone with suggestions? And I should probably add I am a complete amateur with Silverlight.
well you can do that like following
1) Register radio button Click event
Click="RadioButton_Click"
2) Do Tag="{Binding}"
3)
private void RadioButton_Click(object sender, RoutedEventArgs e)
{
RadioButton rb = sender as RadioButton;
var contant= rb .tag;
}
Rather than add a click event handler to each RadioButton, you can do this by enumerating the Items
string answer = string.Empty;
foreach (var item in AnswerIC.Items)
{
var rb = AnswerIC.ItemContainerGenerator
.ContainerFromItem(item).FindVisualChild<RadioButton>();
if (rb.IsChecked ?? false)
{
answer = item.ToString();
break;
}
}
if (string.IsNullOrEmpty(answer))
{
MessageBox.Show("Please select an answer");
}
else
{
MessageBox.Show(string.Format("You chose: {0}", answer));
}
using the following extension method (see also http://geekswithblogs.net/codingbloke/archive/2010/12/19/visual-tree-enumeration.aspx)
public static T FindVisualChild<T>(this DependencyObject instance) where T : DependencyObject
{
T control = default(T);
if (instance != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(instance); i++)
{
if ((control = VisualTreeHelper.GetChild(instance, i) as T) != null)
{
break;
}
control = FindVisualChild<T>(VisualTreeHelper.GetChild(instance, i));
}
}
return control;
}

Silverlight: retrieving controls nested within a Listbox

i made a listbox that generates dynamic controls such as dropdowns & datepicker. i wanted to retrieve the data within the rows. Normally, in windows forms we commonly index the ...Items[i].FindControl("ControlID") method. How do you do about in XAML?
I need to retrieve the changes upon clicking a button.
btw, here's a simple view of my xaml:
<ListBox>
<stackpanel>
<TextBlock />
<stackpanel>
<grid>
<combobox />
<combobox/>
<datepicker />
</grid>
</stackpanel>
</stackpanel>
</ListBox>
Thank you so much!
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
FrameworkElement selectedItem = (sender as ListBox).SelectedItem as FrameworkElement;
List<FrameworkElement> children = new List<FrameworkElement>();
children = GetChildren(selectedItem, ref children);
}
private List<FrameworkElement> GetChildren(FrameworkElement element, ref List<FrameworkElement> list)
{
int count = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < count; i++)
{
FrameworkElement child = VisualTreeHelper.GetChild(element, i) as FrameworkElement;
if(child != null)
{
list.Add(child);
GetChildren(child, ref list);
}
}
return list;
}
This returns all the FrameworkElements (including paths, borders etc). You can easily extend it and call the GetChildren method recursively only if the child is of certain type (ComboBox, StackPanel etc)
I have a helper class with the following two methods to assist with this sort of task.
XAML:
<ListBox Height="236" HorizontalAlignment="Left" Margin="31,23,0,0"
Name="listBox1" VerticalAlignment="Top" Width="245">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Name="sp">
<TextBlock Name="id">id</TextBlock>
<TextBox Name="test" Text="{Binding Key}"></TextBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Using with a List box, you could pass in the selected item:
var v1 =(ListBoxItem) listBox1.ItemContainerGenerator.ContainerFromIndex(
listBox1.SelectedIndex);
TextBox tb = GetChildByName<TextBox>(v1, "test");
tb.Text = "changed";
and you would get the correct textbox for that selected list box item. You can then use that reference to change properties on it.
public T GetChildByName<T>(DependencyObject parent, string name)
where T : class
{
T obj = RecGetChildByName<T>(parent, name) as T;
if (obj == null) throw new Exception("could find control "
+ "of name as child");
return obj;
}
private DependencyObject RecGetChildByName<T>(DependencyObject parent,
string name)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
Control childControl = child as Control;
if (childControl != null)
{
if (childControl.Name == name) return child;
}
if (VisualTreeHelper.GetChildrenCount(child) > 0)
return RecGetChildByName<T>(child, name);
}
return null;
}
The most straightforward way would be to set a two-way binding on of your controls to objects and then the objects will tell you what the values were set to.
Also you can go through your tree by going through the Content properties of the objects until you get to the leaf objects.
Alternatively, you can use the Selected item and call the VisualTreeHelper's GetChild Method until you're at the leaf objects.

Resources