How to get next sibling of the item in databound items control? - c#

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.

Related

WPF TemplateSelector in custom control - usage in code

In my MVVM project, I had to create a custom control RowGrid inheriting from grid.
This control has an ItemsSource and an ItemsTemplateSelector.
(I am not using an ItemsControl, because I need to set a a relative size for each child, and I am doing it by setting the column-widths to x*)
I am trying to assign the template with the ItemsTemplateSelector in code, but it does not work properly:
Children.Clear();
int i = 0;
foreach (var element in ItemsSource)
{
if (element != null)
{
DataTemplate dataTemplate = ItemTemplateSelector.SelectTemplate(element, null);
ContentControl contentControl = new ContentControl
{
DataContext = element,
ContentTemplate = dataTemplate
};
Children.Add(contentControl);
SetColumn(contentControl, i);
}
i++;
}
the ItemTemplateSelector.SelectTemplate is a simple switch/case where, depending on the type of element, a specific DataTemplate is returned.
A DataTemplate example is the following:
<DataTemplate x:Key="StringTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Name}" Grid.Column="0"></Label>
<customControls:MyStringTextBox MyString="{Binding}" Grid.Column="1"/>
</Grid>
</DataTemplate>
If instead of my RowGrid custom control, I use a ItemsControl, the bindings of the DataTemplate work.
If I use my custom control, they do not.
This means that the ItemsSource is fine, the ItemsTemplateSelector is fine and the DataTemplate is fine.
The issue is how I am putting together DataTemplate and its ViewModel
What am I doing wrong?
What am I missing?
Thank you for any support!
I do not really like it, but I found a solution:
I initialize the contentControl this way:
ContentControl contentControl = MergeTemplateWithContext(dataTemplate, element)
plus
public static ContentControl MergeTemplateWithContext(DataTemplate template, object context)
{
ContentControl contentControl = new ContentControl
{
ContentTemplate = template
};
contentControl.Loaded += (object sender, RoutedEventArgs e) =>
{
if (VisualTreeHelper.GetChildrenCount(contentControl) > 0)
{
DependencyObject child = VisualTreeHelper.GetChild(contentControl, 0);
if (child is ContentPresenter contentPresenter && VisualTreeHelper.GetChildrenCount(contentPresenter) > 0)
{
DependencyObject grandChild = VisualTreeHelper.GetChild(contentPresenter, 0);
if (grandChild is FrameworkElement frameworkElement)
{
frameworkElement.DataContext = context;
}
}
}
} ;
return contentControl;
}

WPF accessing scrollviewer of a listview codebehind

I need to access the scrollviewer of a listview from the codebehind.
here is the definition of my listview
<ListView Grid.Row="1" ItemsSource="{Binding Path=SpecList, UpdateSourceTrigger=PropertyChanged}"
Name="mylistview"
ItemTemplate="{StaticResource SpecElementTemplate}"
Background="{StaticResource EnvLayout}"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ItemContainerStyle="{StaticResource MyStyle}"
BorderBrush="Blue"
BorderThickness="20"
Margin="-2">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
How can I get the scrollviewer?
Thank you
Andrea
There are several ways to get the ScrollViewer. Simplest solution is to get the the first child of the first child of the ListView. This means get the Border and the ScrollViewer inside this Border like described in
this answer:
// Get the border of the listview (first child of a listview)
Decorator border = VisualTreeHelper.GetChild(mylistview, 0) as Decorator;
// Get scrollviewer
ScrollViewer scrollViewer = border.Child as ScrollViewer;
A second way is to scan all childrens recursive to find the ScrollViewer. This is described in the answer by Matt Hamilton in this question. You can simply use this function to get the ScrollViewer.
ScrollViewer scrollViewer = GetChildOfType<ScrollViewer>(mylistview);
This second solution is much more generic and will also work if the template of your ListView was edited.
Use VisualTreeHelper class to access any child control.
Psudeo code to your case:
//Declare a scroll viewer object.
ScrollViewer sViewer = default(ScrollViewer );
//Start looping the child controls of your listview.
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(YOUR_LISTVIEW_OBJECT.VisualParent ); i++)
{
// Retrieve child visual at specified index value.
Visual childVisual = (Visual)VisualTreeHelper.GetChild(YOUR_LISTVIEW_OBJECT.VisualParent , i);
ScrollViewer sViewer = childVisual as ScrollViewer;
//You got your scroll viewer. Stop looping.
if (sViewer != null)
{
break;
}
}
I also suggest using the CollectionChanged event. In this code, the CollectionChanged event handler is added to the codebehind after the view model has been loaded. Then, each time the collection changes we scroll to the bottom of the listview. Here is an important point. The scrollviewer child of the list view might not yet be completely rendered when our events start firing. Hence we will get exceptions if we try to use the VisualTreeHelper.GetChild method. So, we have to first attempt to get the scrollviewer and then ignore its positioning if it is not yet available.
private void ReceivedItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// Make sure the items source property in the viewmodel has some items
if (myViewModel.ReceivedItems.Count > 0)
{
var aScrollViewer = RcvdListView.GetChildOfType<ScrollViewer>();
// Make sure the scrollviewer exists before trying to position it
if (aScrollViewer != null)
{
aScrollViewer.ScrollToBottom();
}
}
}
Listview's ScrollViewer should be accessible after LayoutUpdated. You could hook on LayoutUpdated and then get if from Visual tree
private static void ListView_LayoutUpdated(object sender, EventArgs e)
{
var listview = (ListView)sender;
var viewer = listview.GetFirstChildOfType<ScrollViewer>();
}
public static T GetFirstChildOfType<T>(this DependencyObject dependencyObject) where T : DependencyObject
{
if (dependencyObject == null)
{
return null;
}
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
{
var child = VisualTreeHelper.GetChild(dependencyObject, i);
var result = (child as T) ?? GetFirstChildOfType<T>(child);
if (result != null)
{
return result;
}
}
return null;
}

Iterate through ItemsControl's itemssource after it's been bound

Given the feedback of my last question, I have decided to delete it and ask again with a more detailed description of what I'm trying to accomplish:
Old Question:
I've tried a lot of the event handlers for ItemControl only to find that everytime I tried to iterate the itemControl in it's event, it had not yet been bound by it's itemssource.
Is there a way to fire an event after the ItemControl's ItemsSource has been bound?
It has to be specific for ItemsControl not e.g. ListView.
What I wish to do, is iterate through an ItemsControl's items, that have been bound through itemssource. I wish to do this once and that has to be at some point after it's ItemsSource property has been bound through the ViewModel.
This could easily be done by using a ListView instead and using it's SelectionChanged event, but I wish to use an ItemsControl, as I am having problems using the VisualTreeHelper on a ListView.
A quick example of my code:
<ItemsControl x:Name="BoundToObservableCollection" ItemsSource="{Binding ElementName=CalendarDateItemName, Path=CalendarItemsShow}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="CatchThisControl"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
C#:
// I wish to execute this after the ItemsSoucr of my ItemsControl has been bound.
ItemsControl list = BoundToObservableCollection;
foreach (var item in list.Items)
{
var _Container = list.ItemContainerGenerator.ContainerFromItem(item);
var _Children = AllChildren(_Container);
}
public List<UIElement> AllChildren(DependencyObject parent)
{
var _List = new List<UIElement> { };
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var _Child = VisualTreeHelper.GetChild(parent, i);
if (_Child is UIElement)
_List.Add(_Child as UIElement);
_List.AddRange(AllChildren(_Child));
}
return _List;
}
In all of the events of ItemsControl, the itemssource is yet to be bound. How can I fire the AllChildren, after it's been bound?

Get parent TreeViewItem of a selected node in WPF

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

How can I Access to template controls in datagrid?

I'm trying to access a control which is inside the control template of a datagrid control in code behind.
myxaml.xaml :
<DataGrid >
.
.
.
<DataGridTemplateColumn x:Name="discountGridTextcolumn" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Name="discountText"/>
<ComboBox x:Name="discountType"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
.
.
.
mybehind.cs :
var comboBox = GetTemplateChild("discountType");
I get null reference.
Went through many places and finally made my own solution, it is quite easy:
Let's say we want to get access to a CheckBox named "mycb" in row 0 column 2 of your dataGrid:
ContentPresenter cell = dataGrid.Columns[2].GetCellContent(dataGrid.Items[0]) as ContentPresenter;
CheckBox cb = (CheckBox)cell.ContentTemplate.FindName("mycb", cell);
If It return null,you must place them in the OnApplyTemplate() method:
for example
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var comboBox = GetTemplateChild("discountType") as ComboBox;
}
Also try this How to access Control Template parts from Code Behind
Updated
From How to: Find DataTemplate-Generated Elements:
DataGridRow row = (DataGridRow)(yourgrid.ItemContainerGenerator.ContainerFromItem(yourgrid.SelectedItem));
DataGridDetailsPresenter presenter = FindVisualChild<DataGridDetailsPresenter>(row);
DataTemplate template = presenter.ContentTemplate;
ComboBox Com= (ComboBox)template.FindName("discountType", presenter);
FindVisualChild Function:
private childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
return (childItem)child;
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
Another solution How to access objects (comboBox, TextBox...) in DataTemplate

Categories