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;
}
Related
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;
}
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);
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.
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
I have a datatemplate which contains a grid and inside the grid I have a combobox.
<DataTemplate x:Key="ShowAsExpanded">
<Grid>
<ComboBox Name ="myCombo" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="5"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource MyItems}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
</Grid>
</DataTemplate>
I then I have a grid that refers to that template through styling.
<Grid>
<ContentPresenter Name="_contentPresenter" Style="{DynamicResource StyleWithCollapse}" Content="{Binding}" />
</Grid>
How can I access through code behing the myCombo to basically set its DataContext?
Three ways which I know of.
1.Use FindName
ComboBox myCombo =
_contentPresenter.ContentTemplate.FindName("myCombo",
_contentPresenter) as ComboBox;
2.Add the Loaded event to the ComboBox and access it from there
<ComboBox Name ="myCombo" Loaded="myCombo_Loaded" ...
private void myCombo_Loaded(object sender, RoutedEventArgs e)
{
ComboBox myCombo = sender as ComboBox;
// Do things..
}
3.Find it in the Visual Tree
private void SomeMethod()
{
ComboBox myCombo = GetVisualChild<ComboBox>(_contentPresenter);
}
private T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
First of all, I can't even find the relation between the Resource (ShowAsExpanded) and the usage inside the ContentPresenter. But for the moment, let's assume that the DynamicResource should point to ShowAsExpanded.
You can't and shouldn't access the combobox via code. You should bind the datacontext to the grid that uses the style. If you don't want to do that, you will have to find the content at runtime and search for the child combobox.
you need to use FindName. check out http://msdn.microsoft.com/en-us/library/system.windows.frameworktemplate.findname.aspx