I have a custom control with an override for OnApplyTemplate. In it I tried to access children of children templates but they seem to not be loaded. What I wish: when the PART_IncreaseButton inside the Popup of a xtk:SplitButton is clicked, the Popup will not close but just let the Button react to the click.
CustomIntegerUpDown and CustomSplitButton are derived from the Xceed Extended WPF Toolkit. CustomIntegerUpDown has no changed style or template or code-behind and currently its only purpose is to do what I said above but I am just at the beginning of it. Below is all the relevant source.
I tried this:
IncrementButton = Utils.FindChild<RepeatButton>(PartPopup, "PART_IncreaseButton")
and IncrementButton is null after that, although in the Immediate Window:
Utils.FindChild<Popup>(this, "PART_Popup") returns the Popup as obtained from GetTemplateChild("PART_Popup").
then
Utils.FindChild<ButtonSpinner>(PartPopup, "PART_Spinner") returns null.
Utils.FindChild<CustomIntegerUpDown>(PartPopup, "MyCustomIntegerUpDown") return null.
VisualTreeHelper.GetChildrenCount(PartPopup) returns 0.
PartPopup.ApplyTemplate() returns false.
I've also seen this and I am not sure if it is worth trying that way.
FindChild is this (took from here):
/// <summary>
/// Finds a Child of a given item in the visual tree.
/// </summary>
/// <param name="parent">A direct parent of the queried item.</param>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="childName">x:Name or Name of child. </param>
/// <returns>The first parent item that matches the submitted type parameter.
/// If not matching item can be found,
/// a null parent is being returned.</returns>
public static T FindChild<T>(System.Windows.DependencyObject parent, string childName)
where T : System.Windows.DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null) return null;
T foundChild = null;
int childrenCount = System.Windows.Media.VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = System.Windows.Media.VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as System.Windows.FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
In CustomSplitButton.xaml.cs I have this:
internal Popup PartPopup;
internal Button PartButtonWith1, PartButtonWith5, PartButtonWith10, PartButtonWithCustom;
internal RepeatButton IncrementButton;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
PartPopup = (Popup)GetTemplateChild("PART_Popup");
PartButtonWith1 = (Button)GetTemplateChild("PART_ButtonWith1");
PartButtonWith5 = (Button)GetTemplateChild("PART_ButtonWith5");
PartButtonWith10 = (Button)GetTemplateChild("PART_ButtonWith10");
PartButtonWithCustom = (Button)GetTemplateChild("PART_ButtonWithCustom");
PartPopup.ApplyTemplate();
IncrementButton = Utils.FindChild<RepeatButton>(PartPopup, "PART_IncreaseButton");
if (PartPopup != null)
{
PartPopup.PreviewMouseDown += PART_Popup_PreviewMouseUp;
PartPopup.PreviewMouseUp += PART_Popup_PreviewMouseUp;
}
if (PartButtonWith1 != null)
{
PartButtonWith1.Click += Btns_NewTimer_Click;
}
if (PartButtonWith5 != null)
{
PartButtonWith5.Click += Btns_NewTimer_Click;
}
if (PartButtonWith10 != null)
{
PartButtonWith10.Click += Btns_NewTimer_Click;
}
if (PartButtonWithCustom != null)
{
PartButtonWithCustom.Click += BtnCustom_Click;
}
}
The visual tree is this:
The style of CustomSplitButton is the following (xmlns:xtkThemes="clr-namespace:Xceed.Wpf.Toolkit.Themes;assembly=Xceed.Wpf.Toolkit"):
<Style x:Key="AddCountSplitButtonStyle" TargetType="{x:Type xtk:SplitButton}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Background" Value="{DynamicResource {ComponentResourceKey ResourceId=ButtonNormalBackgroundKey, TypeInTargetAssembly={x:Type xtkThemes:ResourceKeys}}}"/>
<Setter Property="BorderBrush" Value="{DynamicResource {ComponentResourceKey ResourceId=ButtonNormalOuterBorderKey, TypeInTargetAssembly={x:Type xtkThemes:ResourceKeys}}}"/>
<Setter Property="DropDownContentBackground">
<Setter.Value>
<LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFF0F0F0" Offset="0"/>
<GradientStop Color="#FFE5E5E5" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Padding" Value="3"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type xtk:SplitButton}">
<Grid x:Name="MainGrid" SnapsToDevicePixels="True">
<xtk:ButtonChrome x:Name="ControlChrome" BorderThickness="0" Background="{TemplateBinding Background}" RenderEnabled="{TemplateBinding IsEnabled}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button x:Name="PART_ActionButton" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="0" Padding="{TemplateBinding Padding}" Style="{x:Null}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}"/>
</ControlTemplate>
</Button.Template>
<Grid>
<xtk:ButtonChrome x:Name="ActionButtonChrome" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" RenderMouseOver="{Binding IsMouseOver, ElementName=PART_ActionButton}" RenderPressed="{Binding IsPressed, ElementName=PART_ActionButton}" RenderEnabled="{TemplateBinding IsEnabled}">
<xtk:ButtonChrome.BorderThickness>
<Binding ConverterParameter="2" Path="BorderThickness" RelativeSource="{RelativeSource TemplatedParent}">
<Binding.Converter>
<xtk:ThicknessSideRemovalConverter/>
</Binding.Converter>
</Binding>
</xtk:ButtonChrome.BorderThickness>
<ContentPresenter x:Name="ActionButtonContent" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</xtk:ButtonChrome>
</Grid>
</Button>
<ToggleButton x:Name="PART_ToggleButton" Grid.Column="1" IsChecked="{Binding IsOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
<ToggleButton.IsHitTestVisible>
<Binding Path="IsOpen" RelativeSource="{RelativeSource TemplatedParent}">
<Binding.Converter>
<xtk:InverseBoolConverter/>
</Binding.Converter>
</Binding>
</ToggleButton.IsHitTestVisible>
<ToggleButton.Template>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}"/>
</ControlTemplate>
</ToggleButton.Template>
<Grid>
<xtk:ButtonChrome x:Name="ToggleButtonChrome" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="1,0" RenderMouseOver="{Binding IsMouseOver, ElementName=PART_ToggleButton}" RenderPressed="{Binding IsPressed, ElementName=PART_ToggleButton}" RenderChecked="{TemplateBinding IsOpen}" RenderEnabled="{TemplateBinding IsEnabled}">
<Grid x:Name="arrowGlyph" IsHitTestVisible="False" Margin="4,3">
<Path x:Name="Arrow" Data="M0,0L3,0 4.5,1.5 6,0 9,0 4.5,4.5z" Fill="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" Height="5" Margin="0,1,0,0" Width="9"/>
</Grid>
</xtk:ButtonChrome>
</Grid>
</ToggleButton>
</Grid>
</xtk:ButtonChrome>
<Popup x:Name="PART_Popup" AllowsTransparency="True" Focusable="False" HorizontalOffset="1" IsOpen="{Binding IsChecked, ElementName=PART_ToggleButton}" Placement="{TemplateBinding DropDownPosition}" VerticalOffset="1"
StaysOpen="False">
<Border BorderThickness="{DynamicResource DefaultBorderThickness}" Margin="10,0,10,10" Background="{DynamicResource DarkerBaseBrush}" BorderBrush="{DynamicResource PopupBorderBrush}" CornerRadius="{DynamicResource DefaultCornerRadius}">
<Grid MinWidth="100" Name="PART_ContentPresenter">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button x:Name="PART_ButtonWith1" Grid.Row="0" Grid.ColumnSpan="2">
1
</Button>
<Button x:Name="PART_ButtonWith5" Grid.Row="1" Grid.ColumnSpan="2">
5
</Button>
<Button x:Name="PART_ButtonWith10" Grid.Row="2" Grid.ColumnSpan="2">
10
</Button>
<local:CustomIntegerUpDown Grid.Row="3" Value="1"
Increment="1" ClipValueToMinMax="True"
x:Name="MyCustomIntegerUpDown">
</local:CustomIntegerUpDown>
<Button x:Name="PART_ButtonWithCustom" Grid.Row="3" Grid.Column="1" Padding="2,2,2,2">
>
</Button>
</Grid>
<Border.Effect>
<DropShadowEffect ShadowDepth="0" BlurRadius="10" Color="{DynamicResource Base6Color}" />
</Border.Effect>
</Border>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Fill" TargetName="Arrow" Value="#FFAFAFAF"/>
<Setter Property="Foreground" TargetName="ActionButtonChrome" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In the OnApplyTemplate I would expect to be able to access children of templates inside templates inside this. But I did not find a way to do this.
Related question of mine here.
Update 1
The starting point of the example, updated (it uses the TryFindVisualChildElementByName extension method from BionicCode's answer):
internal Popup PartPopup;
internal Button PartButtonWith1, PartButtonWith5, PartButtonWith10, PartButtonWithCustom;
internal RepeatButton IncrementButton;
private void SplitButton_Loaded(object sender, RoutedEventArgs e)
{
PartPopup = (Popup)GetTemplateChild("PART_Popup");
PartButtonWith1 = (Button)GetTemplateChild("PART_ButtonWith1");
PartButtonWith5 = (Button)GetTemplateChild("PART_ButtonWith5");
PartButtonWith10 = (Button)GetTemplateChild("PART_ButtonWith10");
PartButtonWithCustom = (Button)GetTemplateChild("PART_ButtonWithCustom");
if (PartPopup != null)
{
PartPopup.ApplyTemplateRecursively();
if (PartPopup.TryFindVisualChildElementByName("PART_IncreaseButton", out FrameworkElement incButton))
{
IncrementButton = (RepeatButton)incButton;
// do something with IncrementButton here
}
PartPopup.PreviewMouseDown += PART_Popup_PreviewMouseUp;
PartPopup.PreviewMouseUp += PART_Popup_PreviewMouseUp;
}
if (PartButtonWith1 != null)
{
PartButtonWith1.Click += Btns_NewTimer_Click;
}
if (PartButtonWith5 != null)
{
PartButtonWith5.Click += Btns_NewTimer_Click;
}
if (PartButtonWith10 != null)
{
PartButtonWith10.Click += Btns_NewTimer_Click;
}
if (PartButtonWithCustom != null)
{
PartButtonWithCustom.Click += BtnCustom_Click;
}
}
The ApplyTemplateRecursively extension method used above, in 2 versions:
Not working version
Is it possible to make this version work somehow? I think it is more efficient.
/// <summary>
/// Not working because the ApplyTemplate affects the VisualTree and when applying
/// templates recursively it does not see the correct updated visual tree to be able
/// to continue.
/// </summary>
/// <param name="root"></param>
internal static void ApplyTemplateRecursively(this System.Windows.DependencyObject root)
{
if (root is System.Windows.Controls.Primitives.Popup p)
{
p.Child.ApplyTemplateRecursively();
return;
}
if (root is FrameworkElement r)
{
r.ApplyTemplate();
}
foreach (object element in System.Windows.LogicalTreeHelper.GetChildren(root))
{
if (element is System.Windows.DependencyObject el)
{
ApplyTemplateRecursively(el);
}
}
}
Working version
/// <summary>
/// I am not sure if this is sufficiently efficient, because it goes through the entire visual tree.
/// </summary>
/// <param name="root"></param>
internal static void ApplyTemplateRecursively(this System.Windows.DependencyObject root)
{
if (root is System.Windows.Controls.Primitives.Popup p)
{
p.Child.ApplyTemplateRecursively();
return;
}
if (root is FrameworkElement r)
{
r.ApplyTemplate();
}
for (int i = 0; i < System.Windows.Media.VisualTreeHelper.GetChildrenCount(root); ++i)
{
DependencyObject d = VisualTreeHelper.GetChild(root, i);
ApplyTemplateRecursively(d);
}
}
Now I am trying to solve the actual problem.
Update 2
I have reported this issue.

The point is that the content of a
Popupis not directly part of the visual tree. That's why looking for visual children of aPopupwill always returnnull. The content of aPopupis rendered separately and is assigned to thePopup.Childproperty. You need to extract those from theChildproperty before continuing tree traversal inside thePopup.The following is a custom visual tree helper method to return the first child element that matches a given name. This helper properly searches inside a
Popupelement. This method is an Extension Method of typeDependencyObjectand has to be put into astaticclass:It's an Extension Method and it is used like this:
CustomSplitButton.xaml.cs
Remarks
It is very important to search after a parent element was
Loaded.This is true for all elements in the visual tree. Since the
SplitButtonconsists of a drop down that is collapsed by default, not all contents are loaded initially. Once the drop down is opened theSplitButtonmakes its content visible, which will add them to the visual tree. Up to this point theSplitButton.IsLoadedproperty will returnfalse, indicating the button's incomplete visual state. What you need to do is, once you encountered aFrameworkElementwhereFrameworkElement.IsLoadedreturnsfalseyou have to subscribe to theFrameworkElement.Loadedevent. In this handler you can continue the visual tree traversal.Popuplike elements or collapsed controls add complexity to the visual tree traversal.Edit: Keep
Popupopen when content was clickedNow that you have told me that you are using the
SplitButtoninside aToolBarI instantly knew the origin of your problem:Simply remove the focus scope from the
ToolBarto prevent the focus from being removed from thePopupas soon as any of its content was clicked (received logical focus):Edit: Keep
Popupopen when clicked on PART_ToggleButton while thePopupis openTo prevent the
Popupfrom closing and reopening when the PART_ToggleButton is clicked while thePopupis open you need to handle the mouse down event (application wide) and the opening of the Popup yourself.First modify the PART_Popup to make it stay open and remove the binding from the
IsOpenproperty:CustomSplitButton.xaml
Then in your
CustomSplitButtonobserve the mouse device for mouse down events and determine the hit target. I assume that you retrieved the underlying PART_Popup and PART_ToggleButton element and stored it in a property namedPartPopupandPartToggleButton(see first part of this answer on how to do it):CustomSplitButton.xaml.cs
Extension Methods to find the visual parent by type and by name