Silverlight: retrieving controls nested within a Listbox - silverlight

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.

Related

Get selected TreeViewItem in MVVM

I want make TreeView with editable nodes. I googled this good, as I think, article:
http://www.codeproject.com/Articles/31592/Editable-TextBlock-in-WPF-for-In-place-Editing
But I have a problems. My TreeView formed dinamically, not statically as in the arcticle. Like that
<TreeView Name="_packageTreeView" Margin="5" ItemsSource="{Binding PackageExtendedList}">
<TreeView.InputBindings>
<KeyBinding Key="C" Command="{Binding *TestCommand*}" CommandParameter="{Binding}" />
</TreeView.InputBindings>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding PackageTreeItemChangeCommand}" CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type MasterBuisnessLogic:RootDocPackage}" ItemsSource="{Binding Path=Childs}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition>
</ColumnDefinition>
<ColumnDefinition>
</ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Source="/Resources/DocGroup.png"></Image>
<Etb:EditableTextBlock Margin="5,0,0,0" Grid.Column="1" Text="{Binding Path=Name}"></Etb:EditableTextBlock>
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
PackageExtendedList - List of DocPackageExtended.
So, first question - how can I get TreeViewItem instance in TestCommand? Not instance DocPackageExtended class! I want to get instance selected TreeViewItem like in the article.
And second question - After I get instance TreeViewItem, how can I get EditableTextBlock from the TreeView item's DataTemplate.
added answer
I already tried it. Cause in MVVM ViewModel cannot has any link to View object like TreeView, I make handler in code-behind, like that
private void TreeViewItemSelected(object sender, RoutedEventArgs e)
{
// Already have TreeViewItem instance without of ItemContainerGenerator help
var tvi = e.OriginalSource as TreeViewItem;
if (tvi == null)
return;
var etb = VisualTreeLib.VisualTreeLib.GetVisualChild<EditableTextBlock>(tvi);
if (etb == null)
return;
// Do what I want
etb.IsEditable = true;
}
Unfortunately, this has no any affect :(
I also tried that approach, but also failed.
in DocPackageExtended type I define property
public bool IsEditable
{
get { return _isEditable; }
set
{
_isEditable = value;
OnPropertyChanged(new PropertyChangedEventArgs("IsEditable"));
}
}
than change in XAML:
<Etb:EditableTextBlock Margin="5,0,0,0" Grid.Column="1" Text="{Binding Path=Name}" *IsEditable="{Binding Path=IsEditable}"*/>
and in ViewModel
private void TestCommandMethod(object obj)
{
var dpe = obj as DocPackageExtended;
if (dpe == null)
return;
dpe.IsEditable = true;
}
Doesn't work too :(
Any ideas?
This might help you.
private void Button_Click(object sender, RoutedEventArgs e)
{
TreeViewItem treeViewItemFound = GetItem(MyTreeview, MyTreeview.SelectedItem);
ContentPresenter header = treeViewItemFound.Template.FindName("PART_Header", treeViewItemFound) as ContentPresenter;
if (header != null)
{
TextBox myTextBox = (TextBox)header.ContentTemplate.FindName("MyTextBox", header);
}
}
public TreeViewItem GetItem(ItemsControl container, object itemToSelect)
{
foreach (object item in container.Items)
{
if (item == itemToSelect)
{
return (TreeViewItem)container.ItemContainerGenerator.ContainerFromItem(item);
}
else
{
ItemsControl itemContainer = (ItemsControl)container.ItemContainerGenerator.ContainerFromItem(item);
if (itemContainer.Items.Count > 0)
{
TreeViewItem treeViewItemFound = GetItem(itemContainer, itemToSelect);
if (treeViewItemFound != null)
return treeViewItemFound;
}
}
}
return null;
}
First question: Since it seems that you can select multiple entries, you need to filter all selected entries in TestCommand's executed method:
IEnumerable<DocPackageExtended> selectedEntries = PackageExtendedList.Where(d => d.IsSelected);
If multiple selection is disabled, you could bind the TreeView's selected item to a property in your VM and access this property in TestCommand's method.
Second question: You get the dataitem's container through var container = YourTreeViewInstance.ItemContainerGenerator.ContainerFromItem(dataInstance);. Now you have to go through this container with the help of the VisualTreeHelper until it finds a control of type EditableTextBlock. But I wouldn't do this in a ViewModel, rather in a helper-class or with the help of attached properties.
EDIT: You're binding the IsEditable property of the instances in the Childs property of your DocPackageExtended class to your EditableTextBox, but in your TestCommandMethod you're manipulating the IsEditableproperty of a DocPackageExtended instance directly. You could do the following:
private void TestCommandMethod(object obj)
{
var dpe = obj as DocPackageExtended;
if (dpe == null)
return;
dpe.IsEditable = true;
foreach (RootDocPackage rdp in dpe.Childs)
{
rdp.IsEditable = true;
}
}

ContentControl get all Elements inside DataTemplate without knowing the Name?

i want to get a list with all elements of my current usercontrol. Not only the one in the LogicalTree, ALL elements inside the defined and used datatemplates in the usercontrol.
When iam iterating threw the VisualTree it dont have VisualTree items inside the ContentControl. I thought the VisualTree contains all Elements?
So at the end i need the TextBox and the Button inside the DataTemplate in my List. But i dont know the x:Name of the elements.
Can somebody help?
<UserControl
x:Class="DataTemplate.Test"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot">
<ContentControl>
<ContentControl.ContentTemplate>
<DataTemplate>
<Grid VerticalAlignment="Stretch">
<Button x:Name="btn_1" />
<TextBlock x:Name="tb_1" />
</Grid>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</UserControl>
In code i iterate threw it when UserControl.Loaded event is fired...
public void OnUserControlLoaded(object sender, EventArgs e)
{
BindChildren(LayoutRoot);
}
List<object> list = new List<object>();
private void BindChildren(object target) {
try
{
var count = VisualTreeHelper.GetChildrenCount(target as FrameworkElement);
if(count < 1)
{
foreach (var child in LogicalTreeHelper.GetChildren(_currentElement))
{
list.Add(child);
BindChildren(child);
}
}
else
{
for (int i = 0; i < count; i++)
{
list.Add(VisualTreeHelper.GetChild(target as FrameworkElement, i));
BindChildren(VisualTreeHelper.GetChild(target as FrameworkElement, i));
}
}
}
catch (InvalidCastException exc)
{
throw;
}
}

An issue about WPF treeview

I am new to WPF, and trying to create a tree using treeview.
What I want to do is to generate a tree dynamically. Each treeViewItem contains a comboBox and a textBlock. As user expand a node, the app will retrieve children nodes information from a data source. Finally user could select several nodes with the checkBoxes.
Following some online tutorials, I did the following tree: e as shown below:
<Window.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<HierarchicalDataTemplate DataType="{x:Type sotc:TaxNode}" ItemsSource="{Binding Path=Children}">
<StackPanel Orientation="Horizontal">
<CheckBox Name="chk" Margin="2" Tag="{Binding}"/>
<TextBlock Text="{Binding Path=TaxID}" ToolTip="{Binding Path=Lineage}" />
</StackPanel>
</HierarchicalDataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<TreeView Margin="25,186,22,46">
<TreeViewItem Header="Taxonomy Tree" x:Name="_TaxTree" x:FieldModifier="private">
<TreeViewItem Header="Loading..." TextBlock.FontStyle="Italic"></TreeViewItem>
</TreeViewItem>
</TreeView>
</Grid>
And I have a method to get the selected comboBox
private List<CheckBox> GetSelectedCheckBoxes(ItemCollection items)
{
var list = new List<CheckBox>();
foreach (TreeViewItem item in items)
{
UIElement element = GetChildControl(item, "chk");
if (element != null)
{
var chk = (CheckBox)element;
if (chk.IsChecked.HasValue && chk.IsChecked.Value)
{
list.Add(chk);
}
}
List<CheckBox> l = GetSelectedCheckBoxes(item.Items);
list = list.Concat(l).ToList();
}
return list;
}
private UIElement GetChildControl(DependencyObject parentObject, string childName)
{
UIElement element = null;
if (parentObject != null)
{
int totalChild = VisualTreeHelper.GetChildrenCount(parentObject);
for (int i = 0; i < totalChild; i++)
{
DependencyObject childObject = VisualTreeHelper.GetChild(parentObject, i);
if (childObject is FrameworkElement &&
((FrameworkElement)childObject).Name == childName)
{
element = childObject as UIElement;
break;
}
// get its child
element = GetChildControl(childObject, childName);
if (element != null) break;
}
}
return element;
}
But due to lack of knowledge on WPF, I do not know what is the ItemCollection I should pass to the method.
Any advice or tutorials will be greatly appreciated.
Have a nice holiday
In your XAML, you can add a Name property to your TreeView:
<Grid>
<TreeView Name="MyAwesomeTreeView" Margin="25,186,22,46">
<TreeViewItem Header="Taxonomy Tree" x:Name="_TaxTree" x:FieldModifier="private">
<TreeViewItem Header="Loading..." TextBlock.FontStyle="Italic"></TreeViewItem>
</TreeViewItem>
</TreeView>
</Grid>
Now you can now access the items of this treeview in codebehind like this:
ItemCollection myDataItems = MyAwesomeTreeView.Items
GetSelectedCheckBoxes(myDataItems, MyAwesomeTreeView);
However the method you've shown will not work on TreeViews with data templates. The reason is that a TreeView will only generate the TreeViewItems once they are actually visible on screen. Until then, it only contains the underlying data. To get around this, you need to use the TreeView's ItemContainerGenerator. Modify your checkbox method to:
private List<CheckBox> GetSelectedCheckBoxes(ItemCollection items, ItemsControl source)
{
var list = new List<CheckBox>();
foreach (object dataitem in items)
{
UIElement treeviewitem = source.ItemContainerGenerator.ContainerFromItem(dataitem)
UIElement element = GetChildControl(treeviewitem, "chk");
if (element != null)
{
var chk = (CheckBox)element;
if (chk.IsChecked.HasValue && chk.IsChecked.Value)
{
list.Add(chk);
}
}
List<CheckBox> l = GetSelectedCheckBoxes(item.Items, treeviewitem);
list = list.Concat(l).ToList();
}
return list;
}
But I would like to emphasize that this is not a good way of doing things. I can bet that what you are trying to achieve can be achieved in a much simpler, straightforward and maintainable way, so I suggest that you explain what it is first.

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;
}

Silverlight - Get the ItemsControl of a DataTemplate

I have a Silverlight application that is using a DataGrid. Inside of that DataGrid I have a DataTemplate that is defined like the following:
<Grid x:Name="myGrid" Tag="{Binding}" Loaded="myGrid_Loaded">
<ItemsControl ItemsSource="{Binding MyItems}" Tag="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal" Width="138">
<TextBlock Text="{Binding Type}" />
<TextBox x:Name="myTextBox" TextChanged="myTextBox_TextChanged" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
When a user enters text into the TextBox, I have an event (myTextBox_TextChanged) that must be fired at this point. When that event gets fired, I would like to get the ItemsControl element that is the container for this TextBox. How do I get that ItemsControl from my event handler?
Please note: Because the ItemsControl is in the DataTemplate of DataGrid, I don't believe I can just add an x:Name and reference it from my code-behind. Or is there a way to do that?
Thank you!
Using a combination of ItemsControl.ItemsControlFromItemContainer and VisualTreeHelper.GetParent you should be able to find your ItemsControl
var txt = sender as TextBox;
var panel1 = VisualTreeHelper.GetParent(txt);
var panel2 = VisualTreeHelper.GetParent(panel1);
var contentPresenter = VisualTreeHelper.GetParent(panel2);
var ic = ItemsControl.ItemsControlFromItemContainer(contentPresenter);
You may also want search the web for VisualTreeHelper Recursive functions to make some of this easier.
I like to have this little extension method in a static class somewhere in my app:-
public static IEnumerable<DependencyObject> Ancestors(this DependencyObject root)
{
DependencyObject current = VisualTreeHelper.GetParent(root);
while (current != null)
{
yield return current;
current = VisualTreeHelper.GetParent(current);
}
}
With that you should be able to do something like this:-
ItemsControl control = ((DependencyObject)sender).Ancestors()
.TypeOf<ItemsControl>().FirstOrDefault();
Not sure if this applies but this creates a "toggling button bar" using the same principles.
private void UIClassButton_Click(object sender, RoutedEventArgs e){
Button SenderButton = (Button)sender;
ItemsControl SendersItemControl = ItemsControl.ItemsControlFromItemContainer(VisualTreeHelper.GetParent(SenderButton));
IEnumerable<DependencyObject> DependencyObjectCollection = SendersItemControl.GetContainers();
foreach (ContentPresenter item in DependencyObjectCollection) {
ContentPresenter UIClassPresenter = (ContentPresenter)item;
Button UIClassButton = (Button)UIClassPresenter.GetVisualChildren().First();
if (UIClassButton != SenderButton) {
VisualStateManager.GoToState(UIClassButton, "Normal", true);
}
else {
VisualStateManager.GoToState(UIClassButton, "Pressed", true);
}
}
}
Here's an example of capturing a container that houses your ItemsControl's item:
CheckBox checkbox = sender as CheckBox;
foreach (var item in MembersItemsControl.Items)
{
var container = MembersItemsControl.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
UserInformation user = container.DataContext as UserInformation;
bool isMale = true;
if (user.sex == isMale && checkbox.IsChecked.Value == true)
{
container.Visibility = System.Windows.Visibility.Visible;
}
}
I hope that helps.

Resources