in the following structure
<Border ...>
<ItemsControl>
<ItemsControl.Template>
<DataTemplate>
<ACustomElement>
<Border MouseLeftButtonDown="method1">
</ACustomElement>
</DataTemplate>
</ItemsControl.Template>
</ItemsControl>
</Border>
I want to call a public method in the ACustomElement class from inside method1().
What I tried so far in method1():
var cr = ((Border)sender).Parent;
cr.method2();
method2 is a public method in my ACustomElement class. But it doesn't seem to recognize the method.
I'm getting the following error:
'DependencyObject' does not contain a definition for 'method2' and no extension method 'method2' accepting a first argument of type 'DependencyObject' could be found (are you missing a using directive or an assembly reference?)
Any suggestions on how to solve this problem?
Certainly I'm just missing a cast or something else...
Edit: The following style will always be applied to ACustomElement:
<Style TargetType="{x:Type c:ACustomElement}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:ACustomElement}">
<ContentPresenter Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You have to cast cr to ACustomElement type
var cr = (ACustomElement)((Border)sender).Parent;
cr.method2();
otherwise, your cr variable refers to DependencyObject type as you see in the exception.
if you are not sure about hierarchy use this method to find a parent of specific type.
private T FindParent<T>(DependencyObject child) where T : DependencyObject {
var parent = VisualTreeHelper.GetParent(child) as T;
if (parent != null)
return parent;
return FindParent<T>(parent);
}
// usage
private void method1(object sender, MouseButtonEventArgs e)
{
var cr = FindParent<ACustomElement>((Border)sender);
}
Also, DateTemplate can be child of ItemsControl.ItemTemplate, but not of ItemsControl.Template (which expects ControlTemplate)
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ACustomElement>
<Border MouseLeftButtonDown="method1" />
</local:ACustomElement>
</DataTemplate>
</ItemsControl.ItemTemplate>
Update
And as I pointed in the comment to the question, add an event handler to your type directly or bind a command. Why do you make it so complicated?
Try to bind the Tag property of the Border to the custom control:
<ACustomElement>
<Border MouseLeftButtonDown="method1" Tag="{Binding RelativeSource={RelativeSource AncestorType=ACustomElement}}">
</ACustomElement>
...and cast the Tag property in the event handler:
var cr = sender as Border;
var ctrl = cr.Tag as ACustomElement;
ctrl.method2();
Related
I have a GridView with a ColumnHeaderTemplate
The template contains a path with the name arrow:
<DataTemplate x:Key="HeaderTemplate">
<DockPanel>
<Path DockPanel.Dock="Right" Margin="5,0,5,0" x:Name="arrow" StrokeThickness="1" Fill="Gray" Data="M 5,5 L 10,10 L 15,5 L 5,5" SnapsToDevicePixels="True"/>
</DockPanel>
</DataTemplate>
The template is assigned in the view like this:
<GridView ColumnHeaderTemplate="{StaticResource HeaderTemplate}">
The GridView is inside a ListView that manages the events
GridViewColumnHeader.Click="ListView_ColumnHeaderClick"
private void ListView_ColumnHeaderClick(object sender, RoutedEventArgs e)
When the event is triggered I want to be able to find the arrow control.
According to my research I should use the Template.FindName method, but so far I have not been able to make this work.
I cant seem to find the correct objects to use with the function and so I never find the control I am looking for.
No, the FindName method you mean would apply to ControlTemplate, not DataTemplate.
How to: Find ControlTemplate-Generated Elements
For DataTemplate you have to iterate the children maually using VisualTreeHelper.
How to: Find DataTemplate-Generated Elements
I do not know how you have attached the column header event handler, so I assume this:
<ListView ItemsSource="{Binding YourItemsSource}">
<ListView.Resources>
<DataTemplate x:Key="HeaderTemplate">
<DockPanel>
<Path DockPanel.Dock="Right" Margin="5,0,5,0" x:Name="arrow" StrokeThickness="1" Fill="Gray" Data="M 5,5 L 10,10 L 15,5 L 5,5" SnapsToDevicePixels="True"/>
</DockPanel>
</DataTemplate>
<Style x:Key="HeaderContainerStyle" TargetType="{x:Type GridViewColumnHeader}" BasedOn="{StaticResource {x:Type GridViewColumnHeader}}">
<EventSetter Event="Click" Handler="ListView_ColumnHeaderClick"/>
</Style>
</ListView.Resources>
<ListView.View>
<GridView ColumnHeaderTemplate="{StaticResource HeaderTemplate}"
ColumnHeaderContainerStyle="{StaticResource HeaderContainerStyle}">
<!-- ...your column definitions. -->
</GridView>
</ListView.View>
</ListView>
You have to create a custom method to recursively traverse the visual tree of the the grid view column header that checks the type and the name of the child elements to get the right one.
public T GetChild<T>(DependencyObject dependencyObject, string name) where T : FrameworkElement
{
if (dependencyObject == null)
return null;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
{
var child = VisualTreeHelper.GetChild(dependencyObject, i);
if (child is T frameworkElement && frameworkElement.Name.Equals(name))
return frameworkElement;
var nextChild = GetChild<T>(child, name);
if (nextChild != null)
return nextChild;
}
return null;
}
Then in the event handler, you can pass the sender, which is the column header.
private void ListView_ColumnHeaderClick(object sender, RoutedEventArgs e)
{
var gridViewColumnHeader = (GridViewColumnHeader)sender;
var arrow = GetChild<Path>(gridViewColumnHeader, "arrow");
// ... do something with arrow.
return;
}
Although this solution works and is a legitimate and officially documented way to solve your issue, you should usually not have to traverse the visual tree this way. In most cases it is not necessary as a lot of issues can be solved more elegantly and easier using data binding.
I the following code
<DataGrid.RowHeaderTemplate >
<DataTemplate>
<CheckBox x:Name="SelectedItemCheckBox"
Margin="5 0 0 0"
IsChecked="{Binding Path=IsSelected,
Mode=TwoWay,
RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type DataGridRow}}}">
</CheckBox>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
or
<DataGrid.RowHeaderStyle>
<Style TargetType="{x:Type DataGridRowHeader}">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridRowHeader}">
<CheckBox x:Name="SelectedItemCheckBox"
Margin="5 0 0 0"
IsChecked="{Binding Path=IsSelected,
Mode=TwoWay,
RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type DataGridRow}}}">
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowHeaderStyle>
How can I access the SelectedItemCheckBox from code behind when row is selected ?
What I have so far:
private CheckBox GetCheckbox(int index)
{
DataGridRow row = (DataGridRow)MyGrid.ItemContainerGenerator.ContainerFromIndex(index);
//how do I get to that checkbox here ?
}
The ItemSource of MyGrid is set in code behind, normally I would access the cell by accessing MyGrid.Columns[] however this is a row header and it's not part of Columns[].
Please note that there are many rows with this checkbox defined depending the ItemSource size.
Also I wold like to know if there is a way of accessing the checkbox without changing the xaml and using it as it is.
If you want to access the row header's checkbox in your code-behind (and not use binding), you can "travel" the visual tree of your selected DataGridRow to find the header.
Add SelectionChanged event handler to the DataGrid:
<DataGrid x:Name="Grid" Loaded="Grid_Loaded" SelectionChanged="Grid_SelectionChanged">
Then in code-behind:
Get the selected row
Use VisualTreeHelper to find the header's checkbox
Do your magic
private void Grid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var item = (DataGridRow)Grid.ItemContainerGenerator.ContainerFromItem(e.AddedItems[0]);
var control = FindChild<CheckBox>(item, "SelectedItemCheckBox");
control.IsChecked = true;
}
For FindChild, there's multiple options available in here: How can I find WPF controls by name or type?
I used the following in this example: How can I find WPF controls by name or type?
public static T FindChild<T>(DependencyObject depObj, string childName)
where T : DependencyObject
{
// Confirm obj is valid.
if (depObj == null) return null;
// success case
if (depObj is T && ((FrameworkElement)depObj).Name == childName)
return depObj as T;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
//DFS
T obj = FindChild<T>(child, childName);
if (obj != null)
return obj;
}
return null;
}
I have a list of orders and when the order status is Cancelled, I want to blink the text. So far, my code works. However, sometimes it will throws exception:
WinRT information: Cannot resolve TargetName lblOrderStatus
For some reason lblOrderStatus can be found. So, I want to use "FindAncestor", but FindAncestor doesn't exists in UWP. Is there any equivalent function to FindAncestor in uwp?
Here is my code:
<ItemsControl x:Name="Orders" Grid.Row="1" Background="Transparent">
...
...
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
...
...
...
<Viewbox Grid.Column="3" StretchDirection="DownOnly" HorizontalAlignment="Right">
<TextBlock x:Name="lblOrderStatus" Text="{Binding Path=OrderItemStatus, Mode=OneWay}" FontSize="18">
<TextBlock.Resources>
<Storyboard x:Name="sbBlinking">
<DoubleAnimation Storyboard.TargetProperty="(FrameworkElement.Opacity)"
Storyboard.TargetName="lblOrderStatus"
From="1" To="0" AutoReverse="True" Duration="0:0:0.5" RepeatBehavior="Forever" />
</Storyboard>
</TextBlock.Resources>
<interactive:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding OrderItemStatus, Converter={StaticResource EnumToStringConverter}}" ComparisonCondition="Equal" Value="Cancelled">
<media:ControlStoryboardAction Storyboard="{StaticResource sbBlinking}" />
</core:DataTriggerBehavior>
</interactive:Interaction.Behaviors>
</TextBlock>
</Viewbox>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Considering all the solutions I've seen, I feel that using ElementName binding is the simplest workaround to UWP not having a RelativeSource AncestorType binding option.
Assuming you've got a Page with its DataContext set to a viewmodel with a command MyCommand, and you want each item in your list to execute it when its button is clicked:
<Page Name="thisPage">
...
<ListView ...>
<ListView.ItemTemplate>
<DataTemplate>
<Button Command="{Binding ElementName=thisPage, Path=DataContext.MyCommand}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Page>
My initial problem with this solution is that you can't extract the DataTemplate out as a resource to use it on multiple screens (or even dialog boxes); thisPage might not exist in each of those places, or it might not be appropriate to name the root element "thisPage".
But if you use a convention where you include a token UI element in every screen that uses that DataTemplate, and refer to it by a consistent name, it will work. By default this element's DataContext will be your ViewModel (assuming your root element does too)
<Rectangle Name="VmDcHelper" Visibility="Collapsed"/>
...then in your standalone resources XAML file you can write your DataTemplate like this:
<DataTemplate x:Key="MyDataTemplate">
<Button Command="{Binding ElementName=VmDcHelper, Path=DataContext.MyCommand}" />
</DataTemplate>
Then, on every page/screen/dialog that you use that template resource, just drop in a copy of that Rectangle (or whatever) and everything will bind correctly at run-time
This is clearly a hack solution, but after thinking about it some more, it doesn't feel like any more of a hack than using WPF's AncestorType in the first place (having to ensure that your ancestor type is always consistent in all the places you use your DataTemplate).
I'm converting an app from WPF to UWP and found this thread. It seems there are no good solutions on the web, so here is my attempt to 'solve' this problem via workaround.
NOTE: The following is UNTESTED in UWP (but works in WPF) as I'm part way through a large non-compiling port, but theoretically it should work...
1 Create a RelativeSourceBinding Attached Property
This class has two properties: AncestorType and Ancestor. When the AncestorType changes, we subscribe to FrameworkElement.Loaded (to handle parent changes) and find the visual parent of type and assign to the Ancestor attached property.
public class RelativeSourceBinding
{
public static readonly DependencyProperty AncestorTypeProperty = DependencyProperty.RegisterAttached("AncestorType", typeof(Type), typeof(RelativeSourceBinding), new PropertyMetadata(default(Type), OnAncestorTypeChanged));
public static void SetAncestorType(DependencyObject element, Type value)
{
element.SetValue(AncestorTypeProperty, value);
}
public static Type GetAncestorType(DependencyObject element)
{
return (Type)element.GetValue(AncestorTypeProperty);
}
private static void OnAncestorTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((FrameworkElement)d).Loaded -= OnFrameworkElementLoaded;
if (e.NewValue != null)
{
((FrameworkElement)d).Loaded += OnFrameworkElementLoaded;
OnFrameworkElementLoaded((FrameworkElement) d, null);
}
}
private static void OnFrameworkElementLoaded(object sender, RoutedEventArgs e)
{
var ancestorType = GetAncestorType((FrameworkElement) sender);
if (ancestorType != null)
{
var findAncestor = ((FrameworkElement) sender).FindVisualParent(ancestorType);
RelativeSourceBinding.SetAncestor(((FrameworkElement)sender), findAncestor);
}
else
{
RelativeSourceBinding.SetAncestor(((FrameworkElement)sender), null);
}
}
public static readonly DependencyProperty AncestorProperty = DependencyProperty.RegisterAttached("Ancestor", typeof(UIElement), typeof(RelativeSourceBinding), new PropertyMetadata(default(FrameworkElement)));
public static void SetAncestor(DependencyObject element, UIElement value)
{
element.SetValue(AncestorProperty, value);
}
public static UIElement GetAncestor(DependencyObject element)
{
return (UIElement)element.GetValue(AncestorProperty);
}
}
Where FindVisualParent is an extension method defined as
public static UIElement FindVisualParent(this UIElement element, Type type)
{
UIElement parent = element;
while (parent != null)
{
if (type.IsAssignableFrom(parent.GetType()))
{
return parent;
}
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
return null;
}
2 Apply the RelativeSourceBinding property in XAML
some BEFORE xaml in WPF would look like this
<Style x:Key="SomeStyle" TargetType="local:AClass">
<Style.Setters>
<Setter Property="SomeProperty" Value="{Binding Foo, RelativeSource={RelativeSource AncestorType=local:AnotherClass}}" />
</Style.Setters>
</Style>
and AFTER xaml
<Style x:Key="SomeStyle" TargetType="local:AClass">
<Style.Setters>
<Setter Property="apc:RelativeSourceBinding.AncestorType" Value="local:AnotherClass"/>
<Setter Property="Foreground" Value="{Binding Path=(apc:RelativeSourceBinding.Ancestor).Foo, RelativeSource={RelativeSource Self}}" />
</Style.Setters>
</Style>
It's a bit messy but in the case where you only have one RelativeSource FindAncestor type to find, it should work.
In XAML
You can try using RelativeSource, it provides a means to specify the source of a binding in terms of a relative relationship in the run-time object graph.
For example using TemplatedParent:
Height="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=Parent.ActualHeight}
or
<Binding RelativeSource="{RelativeSource TemplatedParent}" ></Binding>
In code you try using the VisualTreeHelper.GetParent method.
https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.media.visualtreehelper.getparent
something like the following, here is an example of a utility function
internal static void FindChildren<T>(List<T> results, DependencyObject startNode)
where T : DependencyObject
{
int count = VisualTreeHelper.GetChildrenCount(startNode);
for (int i = 0; i < count; i++)
{
DependencyObject current = VisualTreeHelper.GetChild(startNode, i);
if ((current.GetType()).Equals(typeof(T)) || (current.GetType().GetTypeInfo().IsSubclassOf(typeof(T))))
{
T asType = (T)current;
results.Add(asType);
}
FindChildren<T>(results, current);
}
}
The following example shows code that checks for an element's parent
((StackPanel)LinePane.Parent).ActualWidth;
Also, here is a good blog post showing this class in action. http://blog.jerrynixon.com/2012/09/how-to-access-named-control-inside-xaml.html
<DataTemplate x:Key="dataTempl">
<!--<Border BorderBrush="Coral" BorderThickness="1" Width="Auto" Margin="2">-->
<Button Background="{Binding background}" Name="btn" Tag="{Binding oID}" Click="btn_Click" Style="{StaticResource MetroButton}" Margin="1">
(... rest of items here ...)
</StackPanel>
</Button>
<!--</Border>-->
</DataTemplate>
As you can see, button have Style and background. Style from Resources contain border, background (as gradient) etc.
Now background element from my class:
public Brush background
{
get
{
SolidColorBrush clr = null;
if (backgroundString != "")
{
clr = new SolidColorBrush((Color)ColorConverter.ConvertFromString(backgroundString));
}
return clr;
}
}
But problem is that, it could contains color like #FFFF0000 or just be null.
What I'd like to do is :
if (backgroundString != "") -> apply background
else leave style as it was before.
But with code I show you, if it return null, style does change (there is no borders etc.)
Any idea?
Thanks!
What you want to do is a trigger.
You would like to use the default background, but override it when a given property meet a given condition.
You can do this easily with a trigger.
Simply add a property such as this one to your view model:
public bool OverrideBackground { get { return backgroundString != ""; } }
Then add the following trigger in your DataTemplate:
<DataTemplate>
[...]
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding OverrideBackground}" Value="true">
<Setter Property="Button.Background" Value="{Binding background}" TargetName="btn"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
The DataTrigger will be activated when the OverrideBackground property is true (that is, when backgroundString != ""), and will set the Background property of the Button (that you named btn in your code snippet) to the value of the background property of the bound view model.
I have a TabControl tied to a collection of items where each item is supposed to be represented by a normal TabItem which hosts a user control, like so:
<TabControl x:Name="Items"
ItemsSource="{Binding ElementName=This,Path=Files}">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Path=Name}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type App:MyContext}">
<App:Task x:Name="task" Image="{Binding Path=Image}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
The ItemsSource is bound to an ObservableCollection<MyContext>.
I would like to get to each and every App:Task generated for each of my MyContext instances like so (or similar):
foreach (var file in Files)
{
var container = Items.ItemContainerGenerator.ContainerFromItem(file) as TabItem;
if (container == null) continue;
var task = container.Content as Task;
if (task == null) return;
// ...
}
But the container.Content is MyContext not Task. So I figured I should use:
var task = container.ContentTemplate.FindName("task") as Task;
But this throws an exception because at this point the ContentTemplate does not seem to have been applied yet. How can I force it or get what I want in any other way?
Why do you need the UserControl in the first place?
If you need to access something you haven't bound enough properties on your items to the UserControls.