I want the parent of a node that is selected as TreeViewItem
I have a Person class with 2 fields. Name(String) and Children(List of string)
This is my xaml code
<Grid x:Name="gridView" Margin="10">
<TreeView Name="treeView1" TreeViewItem.Selected="TreeViewItem_OnItemSelected" ItemsSource="{Binding}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Source=Check, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type loc:Person}" ItemsSource="{Binding Children}" >
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
this is my code behind.
I set the item source to a list of Person objects.
void set()
{
if (treeView1.Items.IndexOf(treeView1.SelectedItem) != -1)
{
//is a parent
//returns -1 for children
Person selected = (Person)treeView1.SelectedItem;
int index = search(selected);
TreeViewItem parent = treeView1.Tag as TreeViewItem;
setSelected(parent,index);
}
else
{
//is a child
TreeViewItem child = treeView1.Tag as TreeViewItem; //returns the selected node
TreeViewItem parent = child.Parent as TreeViewItem; //returns null
}
}
private void TreeViewItem_OnItemSelected(object sender, RoutedEventArgs e)
{
treeView1.Tag = e.OriginalSource;
int ind = 0;
foreach (var _item in treeView1.Items)
{
if (_item == treeView1.SelectedItem)
{
selectedIndex = ind;
break;
}
ind++;
}
}
In the else part, The child.Parent always returns null. I tried other methods but none of them return a TreeViewItem instead they return DependencyObject or ItemsControl.
I also tried ContainerFromItem method but it only works for direct children(parent) and not the children of the parent.
Please help
You could use the VisualTreeHelper.GetParent() method:
https://msdn.microsoft.com/de-de/library/system.windows.media.visualtreehelper.getparent(v=vs.110).aspx
Example Code:
private TreeViewItem FindParentTreeViewItem(object child)
{
try
{
var parent = VisualTreeHelper.GetParent(child as DependencyObject);
while ((parent as TreeViewItem) == null)
{
parent = VisualTreeHelper.GetParent(parent);
}
return parent as TreeViewItem;
}
catch (Exception e)
{
//could not find a parent of type TreeViewItem
return null;
}
}
The while loop is needed, as the visual parent of a tree view item isn't its parent tree view item as can be seen in this WPF Tree Visualizer:
WPF Tree Visualizer showing tree view
ItemsSource is typically a collection of DependencyObjects which you can bind to. It doesn't reflect any information about UI. What you are looking for is actually the ItemContainer of each Item which is a part of WPF UI.
In most cases you can access a UIElement from another without any problem. But in my experience as the UI gets complicated by Styles, Templates and hierarchical items, some UIElements will be hard to find.
I suggest the following way:
Implement a Parent property in your ViewModel (Person Class) with type of Person and initialize it in the constructor of Person, Then you can get both the parent Person and the parent TreeViewItem like this:
var parentPerson = (treeView1.SelectedItem as Person).Parent;
var parentNode = treeView1.ItemContainerGenerator.ContainerFromItem(parentPerson);
Related
I am using Windows.UI.Xaml.Controls.TreeView along w/ Windows.UI.Xaml.Controls.TreeViewItem with Selection="Multiple".
<TreeView Name="DessertTree" SelectionMode="Multiple" ItemsSource="{x:Bind DataSource}" ItemInvoked="{x:Bind OnSelectionChanged, Mode=OneTime}">
<TreeView.ItemTemplate>
<DataTemplate x:DataType="local:Item">
<TreeViewItem ItemsSource="{x:Bind Children}" Content="{x:Bind Name}"/>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I cannot seem to find a way to have an event fired for a state change of the checkbox associated with the TreeViewItem. Does anyone have any information on this? I see that the TreeView in WPF has a trigger for change of the checkbox state, so I would think the same functionality is available.
The only trigger I found that is somewhat similar is ItemInvoked, but that does not register selection events on the checkbox, only if the label is clicked.
You should be able to handle the Tapped event for the TreeViewItem, find the CheckBox in the visual tree and examine its IsChecked property:
private void OnTreeViewItemTapped(object sender, TappedRoutedEventArgs e)
{
TreeViewItem tvi = (TreeViewItem)sender;
CheckBox chk = FindVisualChild<CheckBox>(tvi);
bool? isChecked = chk.IsChecked;
}
private static T FindVisualChild<T>(DependencyObject visual) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(visual, i);
if (child != null)
{
T correctlyTyped = child as T;
if (correctlyTyped != null)
return correctlyTyped;
T descendent = FindVisualChild<T>(child);
if (descendent != null)
return descendent;
}
}
return null;
}
I have defined a label with name and I'm trying to access it but no luck. Let me explain my problem with my code.
<ListView Name="gridListView" ItemsSource="{Binding... }">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Focusable" Value="false"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Border>
<StackPanel Orientation="Vertical">
<Label x:Name="vLabel" Content="{Binding VCValue}"/>
<ListView Name="checkBoxListView" ItemsSource="{Binding CList}">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Margin="5" Click="CheckBox_Click" IsChecked="{Binding SelectedValue, Mode=TwoWay}" Content="{Binding Current, Mode=OneWay }"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the above code, I have two listview, gridListView and checkBoxListView. Here, I want to access the label vLabel which is inside the datatemplate of gridListView when the one of the value in checkbox(which is inside checkBoxListView) is clicked.
I understand it can't be accessed directly as its within datatemplate so i tried below code as suggested in other forums but gridListView.SelectedIndex is always -1 so i know i'm not doing the right thing. When I just hardcoded gridListView.SelectedIndex to index 0, 1 or 2 its giving me the right value of vLabel so the below code will work if gridListView.SelectedIndex is correct.
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
CheckBox chk =(CheckBox)sender;
int index = gridListView.Items.IndexOf(chk.DataContext);
ListViewItem item = gridListView.ItemContainerGenerator.ContainerFromIndex(gridListView.SelectedIndex) as ListViewItem;
if (item!=null)
{
//get the item's template parent
ContentPresenter templateParent = GetFrameworkElementByName<ContentPresenter>(item);
DataTemplate dataTemplate = gridListView.ItemTemplate;
if (dataTemplate != null && templateParent != null)
{
var lab = dataTemplate.FindName("vLabel", templateParent) as Label;
var v = lab.Content;
}
}
private static T GetFrameworkElementByName<T>(FrameworkElement referenceElement) where T : FrameworkElement
{
//I can post this function if need be
....
}
Appreciate any help that will help me access vLabel.
Thanks in advance
you are setting focusable to false, so you cant select the item by clicking. also the checkbox-checked happens before the selection, even if you were to set focusable to true, so selected index would still be -1.
you can find your label simply like this:
public Label FindLabel(CheckBox checkBox)
{
var listView = VisualTreeHelper.GetParent(checkBox);
while (listView.GetType() != typeof(ListView))
{
listView = VisualTreeHelper.GetParent(listView);
}
return (listView as FrameworkElement).FindName("vLabel") as Label;
}
but i suggest you tell us what you want to achieve, because this doesnt seem like a clean solution.
can you perhaps do this on your StackPanel:
<StackPanel CheckBox.Checked="CheckBox_Click" Orientation="Vertical">
and then access your two desired properties as following:
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
var outerItem = (sender as FrameworkElement).DataContext;
var innerItem = (e.OriginalSource as FrameworkElement).DataContext;
}
and then do whatever you want to do?
Thank you for all your guidance. I did get hint from Milan's code to achieve solution for my issue. Here, I'm trying to get reference parent of listviewItem(i.e stackpanel) and then access its child. In my case, stack panels child at index 0 is Label and at index 1 is ListView. So then I go through visualtreehelper to get reference to its child at index 0 which is what i need access to. So here is the code snippet.
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
CheckBox checkBox = (CheckBox)sender;
//to access parent of the checkbox
ListViewItem listViewItem =
GetVisualAncestor<ListViewItem>((DependencyObject)sender);
//to access parent of the listViewItem(which is parent of checkbox)
StackPanel stackPanel =
GetVisualAncestor<StackPanel>((DependencyObject)listViewItem);
int childCount = VisualTreeHelper.GetChildrenCount(stackPanel);
//to access child of stackpanel to access the current vLabel
var vValue = VisualTreeHelper.GetChild(stackPanel, 0) as Label;
}
private static T GetVisualAncestor<T>(DependencyObject o)where T : DependecyObject
{
...
}
I would to know if there is a mean to get the treview parent of a TreeViewItem programmactically or a turn around examples in WPF.
The goal is to have the DataContext of the TreeView in order to execute command from the ViewModel.
Any help would be appreciated.
Here, the xaml code. The AddDisplayProperty is part of the data context of the treeview.
My other problem is how to find the TreeViewItem from the TextBlock of the DataTemplate.
<TreeView ItemsSource="{Binding DisplayProperties, Mode=OneWay}" MinHeight="20" AllowDrop="True">
<i:Interaction.Behaviors>
<local:FrameworkElementCommandDropBehavior DropCommand="{Binding AddDisplayPropertyCommand}" DropType="{x:Type local:SearchProperty}"/>
</i:Interaction.Behaviors>
<TreeView.Resources>
<DataTemplate DataType="{x:Type local:SearchProperty}">
<TextBlock Margin="5.0" Text="{Binding Path=LongDescription, Mode=OneWay}" AllowDrop="True">
<i:Interaction.Behaviors>
<local:FrameworkElementCommandDropBehavior DropCommand="[Binding AddDisplayPropertyCommand}" DropType="{x:Type local:SearchProperty}" DropParameters="{Binding}"/>
</i:Interaction.Behaviors>
</TextBlock>
</DataTemplate>
</TreeView.Resources>
</TreeView>
I have used this code at some point in time, you just have to call ParentofType(treeviewItem) and it will give you the first Treeview it finds up the chain or null.
public T ParentOfType<T>(DependencyObject element) where T : DependencyObject
{
if (element == null)
return default (T);
else
return Enumerable.FirstOrDefault<T>(Enumerable.OfType<T>((IEnumerable) GetParents(element)));
}
public IEnumerable<DependencyObject> GetParents( DependencyObject element)
{
if (element == null)
throw new ArgumentNullException("element");
while ((element = GetParent(element)) != null)
yield return element;
}
private DependencyObject GetParent(DependencyObject element)
{
DependencyObject parent = VisualTreeHelper.GetParent(element);
if (parent == null)
{
FrameworkElement frameworkElement = element as FrameworkElement;
if (frameworkElement != null)
parent = frameworkElement.Parent;
}
return parent;
}
I would check out this link. I think it explains how to do it, from code behind.
The datacontext in the example can be accessed using parent.DataContext
I was wonder if any one knows how to change the visibility of a listbox within a DataTemplate when a sibling is clicked. The DataTemplate is being used on a listbox. The following is an example of the xaml I'm using:
<DataTemplate x:Key="Template1">
<StackPanel Margin="110,0,0,0">
<TextBlock Text="{Binding Name}" />
<TextBlock Name="ShowHide" Text="Hide" Tap="ShowHide_Tap" />
<ListBox Name="Listbox1" ItemsSource="{Binding SecondList}" Visibility="Visible" ItemTemplate="{StaticResource Template2}"/>
</StackPanel>
</DataTemplate>
The following is my attempt but I can't use the FindName
private void ShowHide_Click(object sender, System.Windows.Input.GestureEventArgs e)
{
var item = sender as TextBlock;
ListBox Listbox = null;
if (item != null)
{
ContentPresenter templateParent = GetFrameworkElementByName<ContentPresenter>(item);
DataTemplate dataTemplate = templateParent.ContentTemplate;
if (dataTemplate != null && templateParent != null)
{
Listbox = templateParent.FindName("Listbox1") as ListBox;
}
if (Listbox != null)
{
MessageBox.Show(String.Format("ERROR!"));
}
else
Listbox.Visibility = Visibility.Collapsed;
}
}
private static T GetFrameworkElementByName<T>(FrameworkElement referenceElement) where T : FrameworkElement
{
FrameworkElement child = null;
for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceElement); i++)
{
child = VisualTreeHelper.GetChild(referenceElement, i) as FrameworkElement;
System.Diagnostics.Debug.WriteLine(child);
if (child != null && child.GetType() == typeof(T))
{ break; }
else if (child != null)
{
child = GetFrameworkElementByName<T>(child);
if (child != null && child.GetType() == typeof(T))
{
break;
}
}
}
return child as T;
}
If any one has any insights they would be much appreciated,
Thanks.
It so happens that the Blend SDK provides functionality for this -- you can even use XAML only, no code behind. Just use an EventTrigger (on the "Tap" event) along with a ChangePropertyAction. Here's how it looks:
<TextBlock Name="ShowHide" Text="Hide" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<ei:ChangePropertyAction TargetName="Listbox1"
PropertyName="Visibility" Value="Collapsed" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<ListBox Name="Listbox1" ItemsSource="{Binding SecondList}" Visibility="Visible" />
Note that this requires you to add references to the following Extensions:
System.Windows.Interactivity
Microsoft.Expression.Interactions
Reference them in XAML with the namespaces:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Welcome to StackOverflow!
Generally speaking this is not the way to use WPF, especially if you're using DataTemplates. The purpose of the view in WPF is to display the view model and fire off user events, nothing more. By changing the Data Template at run-time you're effectively storing the state of the view inside the view itself. This runs totally against the grain of how WPF was designed to be used.
To do this properly your view model (i.e. the class with the SecondList property) should have an extra property called something like ListBoxVisibility and you would bind your listbox's Visibility member to that. An even cleaner method is to use a bool and then use a converter in the view to convert it from type bool to type Visibility. Either way the view model should also have a property of type ICommand (e.g. OnButtonPressedCommand) that the button invokes when the user presses it. The handler for OnButtonPressedCommand, which should also be in the view model, then sets ListBoxVisible or whatever to the value you want which then propagates through to the list box. Doing things this way keeps good separation of concerns and means the view model can be created and its visibility-changing behavior unit tested independently without having to create the view itself.
How to get next sibling of the element in visual tree? This elment is dataitem of the databound ItemsSource. My goal is get acces to sibling in code (assume that i have access to the element itself) and then use BringIntoView.
Thanks.
For example, if your ItemsControl is a ListBox, the elements will be ListBoxItem objects. If you have one ListBoxItem and you want the next ListBoxItem in the list, you can use the ItemContainerGenerator API to find it like this:
public static DependencyObject GetNextSibling(ItemsControl itemsControl, DependencyObject sibling)
{
var n = itemsControl.Items.Count;
var foundSibling = false;
for (int i = 0; i < n; i++)
{
var child = itemsControl.ItemContainerGenerator.ContainerFromIndex(i);
if (foundSibling)
return child;
if (child == sibling)
foundSibling = true;
}
return null;
}
Here's some sample XAML:
<Grid>
<ListBox Name="listBox">
<ListBoxItem Name="item1">Item1</ListBoxItem>
<ListBoxItem Name="item2">Item2</ListBoxItem>
</ListBox>
</Grid>
and the code-behind:
void Window_Loaded(object sender, RoutedEventArgs e)
{
var itemsControl = listBox;
var sibling = item1;
var nextSibling = GetNextSibling(itemsControl, sibling) as ListBoxItem;
MessageBox.Show(string.Format("Sibling is {0}", nextSibling.Content));
}
which results in:
This also works if the ItemsControl is data-bound. If you only have the data item (not the corresponding user interface element), you can use the ItemContainerGenerator.ContainerFromItem API to get the initial sibling.