Finding Canvas within the visual tree - c#

I have a FlipView controll which in its data template got a scrollviewer, which then got a canvas with the controls. My problem is that I need to access the canvas inside the eventhandler for the FlipView.SelectionChanged event.
The Xaml for the FlipView looks like this.
<FlipView Grid.Row="1"
d:DataContext="{d:DesignInstance model:PageContent}"
SelectionChanged="FlipView_SelectionChanged"
ItemsSource="{Binding TiffPages}"
x:Name="flBillImage">
<FlipView.ItemTemplate>
<DataTemplate>
<ScrollViewer x:Name="scrollBill"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
ZoomMode="Enabled"
DataContextChanged="scrollBill_DataContextChanged">
<Canvas x:Name="cvBill"
DataContextChanged="cvBill_DataContextChanged"
Loaded="cvBill_Loaded"
HorizontalAlignment="Left"
VerticalAlignment="Top"
FlowDirection="LeftToRight" >
<Image x:Name="imgBill"
Loaded="imgBill_Loaded"
DataContextChanged="imgBill_DataContextChanged"
Canvas.ZIndex="0"
Source="{Binding BillImage}"
Visibility="{Binding IsFrameExtracted, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Canvas>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
And the C# code for parsing the visual tree looks like this:
public static List<Control> AllChildren(DependencyObject parent)
{
var _List = new List<Control>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var _Child = VisualTreeHelper.GetChild(parent, i);
if (_Child is Control)
{
_List.Add(_Child as Control);
}
_List.AddRange(AllChildren(_Child));
}
return _List;
}
Which is used like:
var ctrls = AllChildren(flBillImage);
Checking the returned list I can find the ScrollViewer but I can't find the Canvas. I have also tried to supply the scrollviewer returned as argument to the AllChildren function but I still can't seem to find the Canvas control.
Am I doing this all wrong?

I faced a similar type of problem quite some time ago. This solution was used to access the child elements of a tree in the code-behind. Much straight-forward.
Hope this helps you.

Related

How to find a DataTemplate control from inside the Click event

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.

Button Declared in XAML can't be referenced in Class

I have a button I declare within a Stack Panel as I've written below. I want to access the button in my class so I can change the visibility such as myButton.Visibility = Visibility.Hidden but it just says myButton does not exist. It seems private to the XAML stack panel and I don't know why.
XAML
<ItemsControl x:Name="ic">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" Foreground="White" TextWrapping="Wrap" FontSize="12" Margin="0, 0, 0, 0" Width="100" VerticalAlignment="Center" Padding="0"/>
<Button x:Name="myButton" Content="X" Foreground="Red" Width="15" Height="15" Background="Transparent" VerticalAlignment="Top" BorderThickness="0" Padding="0" Click="Remove_Click"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Class
myButton.Visibility = Visibility.Hidden; //myButton doesn't exist in current context
Due to your button being declared within a DataTemplate, you cannot access it directly as with objects declared outside of it. (The DataTemplate provides the information to template your objects when added to the ItemsControl)
If you expect to only have a single , you can remove the whole object around it and gain access to your Button that way.
If you're planning on having an array of s in your , then you'll have to look into making a search logic like the one from this website:
https://dzone.com/articles/how-access-named-control
This generic extension method will search recursively for child elements of the desired type:
public static T GetChildOfType<T>(this DependencyObject depObj)
where T : DependencyObject
{
if (depObj == null)
return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
So using that you can use like this ic.GetChildOfType<Button>();

How can I have a ControlTemplate that only creates a container for the default, unaltered visual tree of a control?

I'm trying to figure out how to change a control's template to something that will make it held inside a Grid, like this:
<ControlTemplate x:Key="containedTemplate">
<Grid>
<!-- place templated control here -->
</Grid>
</ControlTemplate>
I of course want any of the inner control's properties to be synced automatically with the templated control.
Can this be done at all?
Here's an hypothetical example for a TextBox template:
<ControlTemplate x:Key="textTemplate" TargetType="{x:Type TextBox}">
<Grid Background="Red">
<TextBox Name="InnerTextBox" Margin="5,5,5,5"/>
</Grid>
</ControlTemplate>
Now if I did apply the template on a TextBox instance like this:
<TextBox Text="{Binding MyTextProperty}" Template="{StaticResource textTemplate}"/>
... then the control would magically be a Grid, containing a TextBox with a few margins and whose Text's property would be bound to MyTextProperty of whatever DataContext instance has been set:
<!-- runtime visual tree I'd like to be produced by the above XAML -->
<Grid Background="Red">
<TextBox Text="{Binding MyTextProperty}" Margin="5,5,5,5"/>
</Grid>
If I had the following code:
<StackPanel>
<TextBox Text="{Binding MyTextProperty}" Template="{StaticResource textTemplate}"/>
<TextBox Text="{Binding MyOtherTextProperty}" Template="{StaticResource textTemplate}"/>
<TextBox Text="{Binding YetAnotherTextProperty}" Template="{StaticResource textTemplate}"/>
</StackPanel>
The resulting tree would be this:
<!-- runtime visual tree I'd like to be produced by the above XAML -->
<StackPanel>
<Grid Background="Red">
<TextBox Text="{Binding MyTextProperty}" Margin="5,5,5,5"/>
</Grid>
<Grid Background="Red">
<TextBox Text="{Binding MyOtherTextProperty}" Margin="5,5,5,5"/>
</Grid>
<Grid Background="Red">
<TextBox Text="{Binding YetAnotherTextProperty}" Margin="5,5,5,5"/>
</Grid>
</StackPanel>
In these examples you can see that the TextBox's Text property is correctly propagated down to the "inner" TextBox instance. The control's default visual tree is also preserved (borders, typing area, etc.).
I'm aware of template parts but as I said I'm trying to find a global approach here, and I DO NOT want to change the control's appearance; only put it inside a container.
frankly, this question exhausted me, i have this only answer but not convince me a lot.
first you should create multi ControlTemplates for each control that you want to set your template then create this class
public class ControlTemplateConverter
{
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(ControlTemplateConverter), new UIPropertyMetadata(false, IsEnabledChanged));
private static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ControlTemplate t;
if (d == null) return;
if (d is TextBlock)
t = App.Current.FindResource("TextBoxTemplate") as ControlTemplate;
else if (d is CheckBox)
t = App.Current.FindResource("CheckBoxTemplate") as ControlTemplate;
// and So On
(d as Control).Template = t;
}
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
}
and your control should like this:
<TextBox local:ControlTemplateConverter.IsEnabled="True"></TextBox>
<CheckBox local:ControlTemplateConverter.IsEnabled="True"></CheckBox>

Access Page resource element

How can I access page resource element in C# coding? I have the following piece of code in my XAML. I want to access the image element in my C# Code, but it is not accessible.
<Page.Resources>
<DataTemplate x:Key="Standard250x250ItemTemplate">
<Grid HorizontalAlignment="Left" Width="150" Height="150">
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Image x:Name="image" Source="{Binding Image}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}" />
</Border>
</Grid>
</DataTemplate>
It is not accessible because a DataTemplate resource does not get instantiated until it is loaded. You would need to do something like this to load it first:
var dataTemplate = (DataTemplate)this.Resources["Standard250x250ItemTemplate"];
var grid = dataTemplate.LoadContent();
and then traverse the element tree to get to the Image.
A better approach in many scenarios is to define an attached dependency property or attached behavior that you can attach to your Image in XAML and write code related to the associated Image.
It depends on when you are trying to access it. If trying to access the image control of elements that have already been rendered then you can use ItemContainerGenerator like such:
//assumes using a ListView
var item = (ListViewItem)listView.ItemContainerGenerator.ContainerFromItem(myModel);
// traverse children
var image = GetChildOfType<Image>(item);
// use the image!
private T GetChildOfType<T>(DependencyObject obj)
{
for(int i = 0; i< VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if(child is T) return child as T;
T item = GetChildOfType<T>(child);
if(item != null) return item;
}
return null;
}
If you need to change properties of the image, then that can be accomplished through binding as well.

How to put few custom controls dynamicaly inside a datatemplete on Mouse behavior

I have a Datatemplete for List-box Item in which I have a Grid with two columns using WPF. In the first column I want to put few customized controls(Buttons) dynamically using C# in code behind. I don't know how to start and from where should I start, can anybody please help me with some great inputs and examples. Any answer will be greatly appreciate.
Thanks in advance.
XAML code:
<ListBox x:Name="ListBoxItem"
Grid.Row="1"
SelectionMode="Extended"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
FocusVisualStyle="{x:Null}"
KeyboardNavigation.IsTabStop="False"
Background="DarkGray"
ItemsSource="{Binding}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel x:Name="ListContent"
IsItemsHost="True"
Width="500"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel LastChildFill="True"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel DockPanel.Dock="Left"
Width="30"
Height="{Binding Height}">
<--Here I want to put few customize buttons in code behind-->
</StackPanel>
<Image x:Name="MainPage"
Stretch="UniformToFill"
Source="{Binding ImagePath}"
Height="{Binding Height}"
Width="{Binding Width}"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
You specified wanting to use code behind, so it would look something like this:
XAML:
<StackPanel Initialized="StackPanel_Initialized" .. />
Code behind:
using MyNamespace;
private void StackPanel_Initialized(object sender, EventArgs e)
{
MyControl newItem = new MyControl();
// Set any other properties
StackPanel parent = sender as StackPanel;
parent.Children.Add(newItem);
}
If you are looking for adding Controls inside a the First column of your grid then put a Panel inside the first column and in code behind add controls as child to that Panel. So as you mentioned in above that you are using DataTemplete then I would like to say that you can access that Panel something like:
Put the below codes inside the event where you wnt to add the controls.
ListBoxItem item = (ListBoxItem)(this.lst.ItemContainerGenerator.ContainerFromIndex(i));
ContentPresenter presenter = FindVisualChild<ContentPresenter>(item);
DataTemplate template = presenter.ContentTemplate;
StackPanel stack = (StackPanel)template.FindName("FirstColumn Panel Name", presenter);
and then call the below method:
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;
}

Categories

Resources