我有一个覆盖 OnApplyTemplate 的自定义控件。在其中,我尝试访问子模板的子模板,但它们似乎没有被加载。我希望:当点击 a 的内部时,不会PART_IncreaseButton
关闭,只是让点击对点击做出反应。Popup
xtk:SplitButton
Popup
Button
CustomIntegerUpDown
并且CustomSplitButton
源自 Xceed Extended WPF Toolkit。CustomIntegerUpDown 没有改变样式或模板或代码隐藏,目前它的唯一目的是做我上面所说的,但我只是在它的开始。以下是所有相关来源。
我试过这个:
IncrementButton = Utils.FindChild<RepeatButton>(PartPopup, "PART_IncreaseButton")
之后 IncrementButton 为空,尽管在即时窗口中:
Utils.FindChild<Popup>(this, "PART_Popup")
返回从Popup
获得的GetTemplateChild("PART_Popup")
。
然后
Utils.FindChild<ButtonSpinner>(PartPopup, "PART_Spinner")
返回null
。
Utils.FindChild<CustomIntegerUpDown>(PartPopup, "MyCustomIntegerUpDown")
返回null
。
VisualTreeHelper.GetChildrenCount(PartPopup)
返回0
。
PartPopup.ApplyTemplate()
返回false
。
我也看到了这个,我不确定是否值得尝试这种方式。
FindChild
这是(取自这里):
/// <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;
}
在CustomSplitButton.xaml.cs我有这个:
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;
}
}
可视化树是这样的:
CustomSplitButton的样式如下(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>
在OnApplyTemplate
我希望能够在this
. 但我没有找到办法做到这一点。
我的相关问题在这里。
更新 1
示例的起点已更新(它使用TryFindVisualChildElementByName
BionicCode 答案中的扩展方法):
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;
}
}
上面使用的ApplyTemplateRecursively
扩展方法,有2个版本:
不工作版本
是否有可能使这个版本以某种方式工作?我认为它更有效率。
/// <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);
}
}
}
工作版本
/// <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);
}
}
现在我正在尝试解决实际问题。
更新 2
我已经报告了这个问题。